From 505844d4c54da6bcc62851080f212b6cf0703cd9 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 17 Jul 2024 17:22:12 +0200 Subject: [PATCH 001/640] fix: implement auto linking on register --- .../(login)/idp/[provider]/success/page.tsx | 141 +++++------------- apps/login/src/lib/zitadel.ts | 108 ++++++++++++++ 2 files changed, 148 insertions(+), 101 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index ddcd987e1c..714c3dc690 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -1,94 +1,13 @@ import { ProviderSlug } from "@/lib/demos"; -import { getBrandingSettings, userService } from "@/lib/zitadel"; +import { + addIDPLink, + createUser, + getBrandingSettings, + retrieveIDPIntent, +} from "@/lib/zitadel"; import Alert, { AlertType } from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; import IdpSignin from "@/ui/IdpSignin"; -import { AddHumanUserRequest } from "@zitadel/proto/zitadel/user/v2beta/user_service_pb"; -import { - IDPInformation, - IDPLink, -} from "@zitadel/proto/zitadel/user/v2beta/idp_pb"; -import { PartialMessage } from "@zitadel/client2"; - -const PROVIDER_MAPPING: { - [provider: string]: ( - rI: IDPInformation, - ) => PartialMessage; -} = { - [ProviderSlug.GOOGLE]: (idp: IDPInformation) => { - const rawInfo = idp.rawInformation?.toJson() as { - User: { - email: string; - name?: string; - given_name?: string; - family_name?: string; - }; - }; - - const idpLink: PartialMessage = { - idpId: idp.idpId, - userId: idp.userId, - userName: idp.userName, - }; - - const req: PartialMessage = { - username: idp.userName, - email: { - email: rawInfo.User?.email, - verification: { case: "isVerified", value: true }, - }, - // organisation: Organisation | undefined; - profile: { - displayName: rawInfo.User?.name ?? "", - givenName: rawInfo.User?.given_name ?? "", - familyName: rawInfo.User?.family_name ?? "", - }, - idpLinks: [idpLink], - }; - return req; - }, - [ProviderSlug.GITHUB]: (idp: IDPInformation) => { - const rawInfo = idp.rawInformation?.toJson() as { - email: string; - name: string; - }; - const idpLink: PartialMessage = { - idpId: idp.idpId, - userId: idp.userId, - userName: idp.userName, - }; - const req: PartialMessage = { - username: idp.userName, - email: { - email: rawInfo?.email, - verification: { case: "isVerified", value: true }, - }, - // organisation: Organisation | undefined; - profile: { - displayName: rawInfo?.name ?? "", - givenName: rawInfo?.name ?? "", - familyName: rawInfo?.name ?? "", - }, - idpLinks: [idpLink], - }; - return req; - }, -}; - -function retrieveIDPIntent(id: string, token: string) { - return userService.retrieveIdentityProviderIntent( - { idpIntentId: id, idpIntentToken: token }, - {}, - ); -} - -function createUser( - provider: ProviderSlug, - info: IDPInformation, -): Promise { - const userData = PROVIDER_MAPPING[provider](info); - return userService.addHumanUser(userData, {}).then((resp) => resp.userId); -} export default async function Page({ searchParams, @@ -101,11 +20,11 @@ export default async function Page({ const { provider } = params; const branding = await getBrandingSettings(organization); - if (provider && id && token) { return retrieveIDPIntent(id, token) .then((resp) => { const { idpInformation, userId } = resp; + console.log("provider", provider, idpInformation, "userId", userId); if (idpInformation) { // handle login @@ -138,20 +57,40 @@ export default async function Page({ ); }) .catch((error) => { - return ( - -
-

Register failed

-
- { - - {JSON.stringify(error.message)} - - } + if (error.code === 6) { + return addIDPLink( + { + id: idpInformation.idpId, + userId: idpInformation.userId, + userName: idpInformation.userName, + }, + userId, + ).then(() => { + return ( + +
+

Account successfully linked

+
Your account has successfully been linked!
+
+
+ ); + }); + } else { + return ( + +
+

Register failed

+
+ { + + {JSON.stringify(error.message)} + + } +
-
- - ); + + ); + } }); } } else { diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 423edadd3d..c7222630a6 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -17,6 +17,13 @@ import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2beta/oidc_s import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2beta/object_pb"; import type { RedirectURLs } from "@zitadel/proto/zitadel/user/v2beta/idp_pb"; import { PlainMessage } from "@zitadel/client2"; +import { ProviderSlug } from "./demos"; +import { AddHumanUserRequest } from "@zitadel/proto/zitadel/user/v2beta/user_service_pb"; +import { + IDPInformation, + IDPLink, +} from "@zitadel/proto/zitadel/user/v2beta/idp_pb"; +import { PartialMessage } from "@zitadel/client2"; const SESSION_LIFETIME_S = 3000; @@ -354,6 +361,107 @@ export async function resendEmailCode(userId: string) { ); } +export function retrieveIDPIntent(id: string, token: string) { + return userService.retrieveIdentityProviderIntent( + { idpIntentId: id, idpIntentToken: token }, + {}, + ); +} + +export function addIDPLink( + idp: { + id: string; + userId: string; + userName: string; + }, + userId: string, +) { + return userService.addIDPLink( + { + idpLink: { + userId: idp.userId, + idpId: idp.id, + userName: idp.userName, + }, + userId, + }, + {}, + ); +} + +const PROVIDER_MAPPING: { + [provider: string]: ( + rI: IDPInformation, + ) => PartialMessage; +} = { + [ProviderSlug.GOOGLE]: (idp: IDPInformation) => { + const rawInfo = idp.rawInformation?.toJson() as { + User: { + email: string; + name?: string; + given_name?: string; + family_name?: string; + }; + }; + + const idpLink: PartialMessage = { + idpId: idp.idpId, + userId: idp.userId, + userName: idp.userName, + }; + + const req: PartialMessage = { + username: idp.userName, + email: { + email: rawInfo.User?.email, + verification: { case: "isVerified", value: true }, + }, + // organisation: Organisation | undefined; + profile: { + displayName: rawInfo.User?.name ?? "", + givenName: rawInfo.User?.given_name ?? "", + familyName: rawInfo.User?.family_name ?? "", + }, + idpLinks: [idpLink], + }; + return req; + }, + [ProviderSlug.GITHUB]: (idp: IDPInformation) => { + const rawInfo = idp.rawInformation?.toJson() as { + email: string; + name: string; + }; + const idpLink: PartialMessage = { + idpId: idp.idpId, + userId: idp.userId, + userName: idp.userName, + }; + const req: PartialMessage = { + username: idp.userName, + email: { + email: rawInfo?.email, + verification: { case: "isVerified", value: true }, + }, + // organisation: Organisation | undefined; + profile: { + displayName: rawInfo?.name ?? "", + givenName: rawInfo?.name ?? "", + familyName: rawInfo?.name ?? "", + }, + idpLinks: [idpLink], + }; + return req; + }, +}; + +export function createUser( + provider: ProviderSlug, + info: IDPInformation, +): Promise { + const userData = PROVIDER_MAPPING[provider](info); + return userService.addHumanUser(userData, {}).then((resp) => resp.userId); +} + /** * * @param userId the id of the user where the email should be set From 82c57ae6093f7400a0f3f14239641702e8e2b804 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 23 Jul 2024 13:58:43 +0200 Subject: [PATCH 002/640] idp linking --- apps/login/src/app/(login)/idp/[provider]/success/page.tsx | 3 --- apps/login/src/lib/zitadel.ts | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 714c3dc690..1608c605d9 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -24,10 +24,8 @@ export default async function Page({ return retrieveIDPIntent(id, token) .then((resp) => { const { idpInformation, userId } = resp; - console.log("provider", provider, idpInformation, "userId", userId); if (idpInformation) { - // handle login if (userId) { return ( @@ -44,7 +42,6 @@ export default async function Page({ ); } else { - // handle register return createUser(provider, idpInformation) .then((userId) => { return ( diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index c7222630a6..ee276bb0e2 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -39,6 +39,7 @@ export const sessionService = createSessionServiceClient(transport); export const managementService = createManagementServiceClient(transport); export const userService = createUserServiceClient(transport); export const oidcService = createOIDCServiceClient(transport); + export const settingsService = createSettingsServiceClient(transport); export async function getBrandingSettings(organization?: string) { From a58ccbf671db7b61f376fcaa2360e859447416a7 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 7 Aug 2024 15:53:55 +0200 Subject: [PATCH 003/640] move cookie utils to @zitadel/next package --- apps/login/package.json | 1 + .../src/app/(login)/otp/[method]/page.tsx | 21 +- .../src/app/(login)/otp/[method]/set/page.tsx | 14 +- .../src/app/(login)/passkey/add/page.tsx | 14 +- apps/login/src/app/(login)/password/page.tsx | 15 +- apps/login/src/app/(login)/u2f/set/page.tsx | 14 +- apps/login/src/middleware.ts | 5 +- apps/login/turbo.json | 12 +- packages/zitadel-next/package.json | 3 + packages/zitadel-next/src/index.tsx | 8 +- packages/zitadel-next/src/utils/cookies.ts | 237 + packages/zitadel-next/src/utils/session.ts | 22 + packages/zitadel-next/turbo.json | 16 +- pnpm-lock.yaml | 9711 +++++++---------- 14 files changed, 4512 insertions(+), 5581 deletions(-) create mode 100644 packages/zitadel-next/src/utils/cookies.ts create mode 100644 packages/zitadel-next/src/utils/session.ts diff --git a/apps/login/package.json b/apps/login/package.json index aca25b506c..9afbf349a0 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -40,6 +40,7 @@ "@zitadel/node": "workspace:*", "@zitadel/proto": "workspace:*", "@zitadel/react": "workspace:*", + "@zitadel/next": "workspace:*", "clsx": "1.2.1", "copy-to-clipboard": "^3.3.3", "moment": "^2.29.4", diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index fc1641da5f..e25c30af81 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -1,13 +1,9 @@ -import { - getBrandingSettings, - getLoginSettings, - getSession, -} from "@/lib/zitadel"; +import { getBrandingSettings } from "@/lib/zitadel"; import Alert from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; import LoginOTP from "@/ui/LoginOTP"; import UserAvatar from "@/ui/UserAvatar"; -import { getMostRecentCookieWithLoginname } from "@/utils/cookies"; +import { loadMostRecentSession } from "@zitadel/next"; export default async function Page({ searchParams, @@ -21,21 +17,10 @@ export default async function Page({ const { method } = params; - const { session, token } = await loadSession(loginName, organization); + const session = await loadMostRecentSession(loginName, organization); const branding = await getBrandingSettings(organization); - async function loadSession(loginName?: string, organization?: string) { - const recent = await getMostRecentCookieWithLoginname( - loginName, - organization, - ); - - return getSession(recent.id, recent.token).then((response) => { - return { session: response?.session, token: recent.token }; - }); - } - return (
diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index fb8ce9b545..23f5dcb199 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -15,6 +15,7 @@ import UserAvatar from "@/ui/UserAvatar"; import { getMostRecentCookieWithLoginname } from "@/utils/cookies"; import Link from "next/link"; import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { loadMostRecentSession } from "@zitadel/next"; export default async function Page({ searchParams, @@ -28,7 +29,7 @@ export default async function Page({ const { method } = params; const branding = await getBrandingSettings(organization); - const { session, token } = await loadSession(loginName, organization); + const session = await loadMostRecentSession(loginName, organization); let totpResponse: RegisterTOTPResponse | undefined, totpError: Error | undefined; @@ -56,17 +57,6 @@ export default async function Page({ throw new Error("No session found"); } - async function loadSession(loginName?: string, organization?: string) { - const recent = await getMostRecentCookieWithLoginname( - loginName, - organization, - ); - - return getSession(recent.id, recent.token).then((response) => { - return { session: response?.session, token: recent.token }; - }); - } - const paramsToContinue = new URLSearchParams({}); let urlToContinue = "/accounts"; diff --git a/apps/login/src/app/(login)/passkey/add/page.tsx b/apps/login/src/app/(login)/passkey/add/page.tsx index ac8656342a..52a3fe3a2a 100644 --- a/apps/login/src/app/(login)/passkey/add/page.tsx +++ b/apps/login/src/app/(login)/passkey/add/page.tsx @@ -4,6 +4,7 @@ import DynamicTheme from "@/ui/DynamicTheme"; import RegisterPasskey from "@/ui/RegisterPasskey"; import UserAvatar from "@/ui/UserAvatar"; import { getMostRecentCookieWithLoginname } from "@/utils/cookies"; +import { loadMostRecentSession } from "@zitadel/next"; export default async function Page({ searchParams, @@ -13,19 +14,8 @@ export default async function Page({ const { loginName, promptPasswordless, organization, authRequestId } = searchParams; - const sessionFactors = await loadSession(loginName); + const sessionFactors = await loadMostRecentSession(loginName, organization); - async function loadSession(loginName?: string) { - const recent = await getMostRecentCookieWithLoginname( - loginName, - organization, - ); - return getSession(recent.id, recent.token).then((response) => { - if (response?.session) { - return response.session; - } - }); - } const title = !!promptPasswordless ? "Authenticate with a passkey" : "Use your passkey to confirm it's really you"; diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index c0fa08bd4a..5601784fe7 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -8,6 +8,7 @@ import DynamicTheme from "@/ui/DynamicTheme"; import PasswordForm from "@/ui/PasswordForm"; import UserAvatar from "@/ui/UserAvatar"; import { getMostRecentCookieWithLoginname } from "@/utils/cookies"; +import { loadMostRecentSession } from "@zitadel/next"; export default async function Page({ searchParams, @@ -16,20 +17,8 @@ export default async function Page({ }) { const { loginName, organization, promptPasswordless, authRequestId, alt } = searchParams; - const sessionFactors = await loadSession(loginName, organization); - async function loadSession(loginName?: string, organization?: string) { - const recent = await getMostRecentCookieWithLoginname( - loginName, - organization, - ); - - return getSession(recent.id, recent.token).then((response) => { - if (response?.session) { - return response.session; - } - }); - } + const sessionFactors = await loadMostRecentSession(loginName, organization); const branding = await getBrandingSettings(organization); const loginSettings = await getLoginSettings(organization); diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index 2b3caf3d7f..33de8a775f 100644 --- a/apps/login/src/app/(login)/u2f/set/page.tsx +++ b/apps/login/src/app/(login)/u2f/set/page.tsx @@ -5,6 +5,7 @@ import RegisterPasskey from "@/ui/RegisterPasskey"; import RegisterU2F from "@/ui/RegisterU2F"; import UserAvatar from "@/ui/UserAvatar"; import { getMostRecentCookieWithLoginname } from "@/utils/cookies"; +import { loadMostRecentSession } from "@zitadel/next"; export default async function Page({ searchParams, @@ -13,19 +14,8 @@ export default async function Page({ }) { const { loginName, organization, authRequestId } = searchParams; - const sessionFactors = await loadSession(loginName); + const sessionFactors = await loadMostRecentSession(loginName, organization); - async function loadSession(loginName?: string) { - const recent = await getMostRecentCookieWithLoginname( - loginName, - organization, - ); - return getSession(recent.id, recent.token).then((response) => { - if (response?.session) { - return response.session; - } - }); - } const title = "Use your passkey to confirm it's really you"; const description = "Your device will ask for your fingerprint, face, or screen lock"; diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index ab98c0f05f..15f102d070 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -18,7 +18,10 @@ export function middleware(request: NextRequest) { requestHeaders.set("x-zitadel-login-client", SERVICE_USER_ID); // this is a workaround for the next.js server not forwarding the host header - requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`); + requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`); + + // this is a workaround for the next.js server not forwarding the host header + requestHeaders.set("x-zitadel-instance-host", `${INSTANCE}`); const responseHeaders = new Headers(); responseHeaders.set("Access-Control-Allow-Origin", "*"); diff --git a/apps/login/turbo.json b/apps/login/turbo.json index 0e271d4003..20ad9b2492 100644 --- a/apps/login/turbo.json +++ b/apps/login/turbo.json @@ -9,28 +9,32 @@ "dependsOn": [ "@zitadel/node#build", "@zitadel/client#build", - "@zitadel/react#build" + "@zitadel/react#build", + "@zitadel/next#build" ] }, "test:integration": { "dependsOn": [ "@zitadel/node#build", "@zitadel/client#build", - "@zitadel/react#build" + "@zitadel/react#build", + "@zitadel/next#build" ] }, "test:unit": { "dependsOn": [ "@zitadel/node#build", "@zitadel/client#build", - "@zitadel/react#build" + "@zitadel/react#build", + "@zitadel/next#build" ] }, "test:watch": { "dependsOn": [ "@zitadel/node#build", "@zitadel/client#build", - "@zitadel/react#build" + "@zitadel/react#build", + "@zitadel/next#build" ] } } diff --git a/packages/zitadel-next/package.json b/packages/zitadel-next/package.json index 9d1da0d519..5b4ef1519f 100644 --- a/packages/zitadel-next/package.json +++ b/packages/zitadel-next/package.json @@ -6,6 +6,7 @@ "types": "./dist/index.d.ts", "sideEffects": false, "license": "MIT", + "type": "module", "files": [ "dist/**" ], @@ -25,6 +26,8 @@ "peerDependencies": { "@zitadel/node": "workspace:*", "@zitadel/react": "workspace:*", + "@zitadel/client": "workspace:*", + "@zitadel/proto": "workspace:*", "next": "^14.2.3", "react": "18.2.0" }, diff --git a/packages/zitadel-next/src/index.tsx b/packages/zitadel-next/src/index.tsx index 1754b0ee48..d218d51870 100644 --- a/packages/zitadel-next/src/index.tsx +++ b/packages/zitadel-next/src/index.tsx @@ -1,6 +1,4 @@ -import "./styles.css"; +// import "./styles.css"; -export { - ZitadelNextProvider, - type ZitadelNextProps, -} from "./components/ZitadelNextProvider"; +export { ZitadelNextProvider, type ZitadelNextProps } from "./components/ZitadelNextProvider"; +export { loadMostRecentSession } from "./utils/session"; diff --git a/packages/zitadel-next/src/utils/cookies.ts b/packages/zitadel-next/src/utils/cookies.ts new file mode 100644 index 0000000000..e16a48602e --- /dev/null +++ b/packages/zitadel-next/src/utils/cookies.ts @@ -0,0 +1,237 @@ +"use server"; + +import { cookies } from "next/headers"; + +export type Cookie = { + id: string; + token: string; + loginName: string; + organization?: string; + creationDate: string; + expirationDate: string; + changeDate: string; + authRequestId?: string; // if its linked to an OIDC flow +}; + +type SessionCookie = Cookie & T; + +function setSessionHttpOnlyCookie(sessions: SessionCookie[]) { + const cookiesList = cookies(); + // @ts-ignore + return cookiesList.set({ + name: "sessions", + value: JSON.stringify(sessions), + httpOnly: true, + path: "/", + }); +} + +export async function addSessionToCookie(session: SessionCookie, cleanup: boolean = false): Promise { + const cookiesList = cookies(); + const stringifiedCookie = cookiesList.get("sessions"); + + let currentSessions: SessionCookie[] = stringifiedCookie?.value ? JSON.parse(stringifiedCookie?.value) : []; + + const index = currentSessions.findIndex((s) => s.loginName === session.loginName); + + if (index > -1) { + currentSessions[index] = session; + } else { + currentSessions = [...currentSessions, session]; + } + + if (cleanup) { + const now = new Date(); + const filteredSessions = currentSessions.filter((session) => + session.expirationDate ? new Date(session.expirationDate) > now : true, + ); + return setSessionHttpOnlyCookie(filteredSessions); + } else { + return setSessionHttpOnlyCookie(currentSessions); + } +} + +export async function updateSessionCookie(id: string, session: SessionCookie, cleanup: boolean = false): Promise { + const cookiesList = cookies(); + const stringifiedCookie = cookiesList.get("sessions"); + + const sessions: SessionCookie[] = stringifiedCookie?.value ? JSON.parse(stringifiedCookie?.value) : [session]; + + const foundIndex = sessions.findIndex((session) => session.id === id); + + if (foundIndex > -1) { + sessions[foundIndex] = session; + if (cleanup) { + const now = new Date(); + const filteredSessions = sessions.filter((session) => + session.expirationDate ? new Date(session.expirationDate) > now : true, + ); + return setSessionHttpOnlyCookie(filteredSessions); + } else { + return setSessionHttpOnlyCookie(sessions); + } + } else { + throw "updateSessionCookie: session id now found"; + } +} + +export async function removeSessionFromCookie(session: SessionCookie, cleanup: boolean = false): Promise { + const cookiesList = cookies(); + const stringifiedCookie = cookiesList.get("sessions"); + + const sessions: SessionCookie[] = stringifiedCookie?.value ? JSON.parse(stringifiedCookie?.value) : [session]; + + const reducedSessions = sessions.filter((s) => s.id !== session.id); + if (cleanup) { + const now = new Date(); + const filteredSessions = reducedSessions.filter((session) => + session.expirationDate ? new Date(session.expirationDate) > now : true, + ); + return setSessionHttpOnlyCookie(filteredSessions); + } else { + return setSessionHttpOnlyCookie(reducedSessions); + } +} + +export async function getMostRecentSessionCookie(): Promise { + const cookiesList = cookies(); + const stringifiedCookie = cookiesList.get("sessions"); + + if (stringifiedCookie?.value) { + const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); + + const latest = sessions.reduce((prev, current) => { + return new Date(prev.changeDate).getTime() > new Date(current.changeDate).getTime() ? prev : current; + }); + + return latest; + } else { + return Promise.reject("no session cookie found"); + } +} + +export async function getSessionCookieById(id: string, organization?: string): Promise> { + const cookiesList = cookies(); + const stringifiedCookie = cookiesList.get("sessions"); + + if (stringifiedCookie?.value) { + const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); + + const found = sessions.find((s) => (organization ? s.organization === organization && s.id === id : s.id === id)); + if (found) { + return found; + } else { + return Promise.reject(); + } + } else { + return Promise.reject(); + } +} + +export async function getSessionCookieByLoginName(loginName: string, organization?: string): Promise> { + const cookiesList = cookies(); + const stringifiedCookie = cookiesList.get("sessions"); + + if (stringifiedCookie?.value) { + const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); + const found = sessions.find((s) => + organization ? s.organization === organization && s.loginName === loginName : s.loginName === loginName, + ); + if (found) { + return found; + } else { + return Promise.reject("no cookie found with loginName: " + loginName); + } + } else { + return Promise.reject("no session cookie found"); + } +} + +/** + * + * @param cleanup when true, removes all expired sessions, default true + * @returns Session Cookies + */ +export async function getAllSessionCookieIds(cleanup: boolean = false): Promise { + const cookiesList = cookies(); + const stringifiedCookie = cookiesList.get("sessions"); + + if (stringifiedCookie?.value) { + const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); + + if (cleanup) { + const now = new Date(); + return sessions + .filter((session) => (session.expirationDate ? new Date(session.expirationDate) > now : true)) + .map((session) => session.id); + } else { + return sessions.map((session) => session.id); + } + } else { + return []; + } +} + +/** + * + * @param cleanup when true, removes all expired sessions, default true + * @returns Session Cookies + */ +export async function getAllSessions(cleanup: boolean = false): Promise[]> { + const cookiesList = cookies(); + const stringifiedCookie = cookiesList.get("sessions"); + + if (stringifiedCookie?.value) { + const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); + + if (cleanup) { + const now = new Date(); + return sessions.filter((session) => (session.expirationDate ? new Date(session.expirationDate) > now : true)); + } else { + return sessions; + } + } else { + return []; + } +} + +/** + * Returns most recent session filtered by optinal loginName + * @param loginName + * @returns most recent session + */ +export async function getMostRecentCookieWithLoginname(loginName?: string, organization?: string): Promise { + const cookiesList = cookies(); + const stringifiedCookie = cookiesList.get("sessions"); + + if (stringifiedCookie?.value) { + const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); + let filtered = sessions.filter((cookie) => { + return !!loginName ? cookie.loginName === loginName : true; + }); + + if (organization) { + filtered = filtered.filter((cookie) => { + return cookie.organization === organization; + }); + } + + const latest = + filtered && filtered.length + ? filtered.reduce((prev, current) => { + return new Date(prev.changeDate).getTime() > new Date(current.changeDate).getTime() ? prev : current; + }) + : undefined; + + if (latest) { + return latest; + } else { + console.error("sessions", sessions, loginName, organization); + return Promise.reject("Could not get the context or retrieve a session"); + } + } else { + return Promise.reject("Could not read session cookie"); + } +} + +export async function clearSessions() {} diff --git a/packages/zitadel-next/src/utils/session.ts b/packages/zitadel-next/src/utils/session.ts new file mode 100644 index 0000000000..4e8c6c9f94 --- /dev/null +++ b/packages/zitadel-next/src/utils/session.ts @@ -0,0 +1,22 @@ +import { createSessionServiceClient } from "@zitadel/client/v2"; +import { createServerTransport } from "@zitadel/node"; +import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { getMostRecentCookieWithLoginname } from "./cookies"; + +const SESSION_LIFETIME_S = 3000; + +const transport = createServerTransport(process.env.ZITADEL_SERVICE_USER_TOKEN!, { + baseUrl: process.env.ZITADEL_API_URL!, + httpVersion: "2", +}); + +const sessionService = createSessionServiceClient(transport); + +export async function loadMostRecentSession(loginName?: string, organization?: string): Promise { + const recent = await getMostRecentCookieWithLoginname(loginName, organization); + return getMostRecentSession(recent.id, recent.token); +} + +async function getMostRecentSession(sessionId: string, sessionToken: string) { + return sessionService.getSession({ sessionId, sessionToken }, {}).then((resp) => resp.session); +} diff --git a/packages/zitadel-next/turbo.json b/packages/zitadel-next/turbo.json index 277108232b..5a1a7d4995 100644 --- a/packages/zitadel-next/turbo.json +++ b/packages/zitadel-next/turbo.json @@ -1,20 +1,12 @@ { - "extends": [ - "//" - ], + "extends": ["//"], "tasks": { "dev": { - "dependsOn": [ - "@zitadel/react#build" - ] + "dependsOn": ["@zitadel/client#build"] }, "build": { - "outputs": [ - "dist/**" - ], - "dependsOn": [ - "@zitadel/react#build" - ] + "outputs": ["dist/**"], + "dependsOn": ["@zitadel/client#build", "@zitadel/react#build"] } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28da5fff99..aa9b14cb02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '9.0' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -16,7 +16,7 @@ importers: version: 2.27.7 '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.3.1(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + version: 4.3.1(vite@5.3.5) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -31,7 +31,7 @@ importers: version: 4.0.0(prettier@3.3.3)(typescript@5.5.4) tsup: specifier: ^8.0.2 - version: 8.2.3(postcss@8.4.40)(typescript@5.5.4)(yaml@2.5.0) + version: 8.2.4(typescript@5.5.4) turbo: specifier: 2.0.9 version: 2.0.9 @@ -40,28 +40,31 @@ importers: version: 5.5.4 vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + version: 4.3.2(typescript@5.5.4)(vite@5.3.5) vitest: specifier: ^1.6.0 - version: 1.6.0(@types/node@20.14.13)(jsdom@24.1.1)(sass@1.77.8) + version: 1.6.0 apps/login: dependencies: '@headlessui/react': specifier: ^1.7.18 - version: 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.7.19(react-dom@18.3.1)(react@18.3.1) '@heroicons/react': specifier: 2.1.3 version: 2.1.3(react@18.3.1) '@tailwindcss/forms': specifier: 0.5.7 - version: 0.5.7(tailwindcss@3.2.4(postcss@8.4.21)) + version: 0.5.7(tailwindcss@3.2.4) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@14.2.3(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1) + version: 1.3.1(next@14.2.3)(react@18.3.1) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client + '@zitadel/next': + specifier: workspace:* + version: link:../../packages/zitadel-next '@zitadel/node': specifier: workspace:* version: link:../../packages/zitadel-node @@ -82,10 +85,10 @@ importers: version: 2.30.1 next: specifier: 14.2.3 - version: 14.2.3(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) + version: 14.2.3(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@14.2.3(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.2.1(next@14.2.3)(react-dom@18.3.1)(react@18.3.1) nice-grpc: specifier: 2.0.1 version: 2.0.1 @@ -110,13 +113,13 @@ importers: devDependencies: '@bufbuild/buf': specifier: ^1.35.1 - version: 1.35.1 + version: 1.36.0 '@testing-library/jest-dom': specifier: ^6.4.5 version: 6.4.8 '@testing-library/react': specifier: ^15.0.7 - version: 15.0.7(@types/react@18.2.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 15.0.7(@types/react@18.2.8)(react-dom@18.3.1)(react@18.3.1) '@types/ms': specifier: 0.7.31 version: 0.7.31 @@ -152,7 +155,7 @@ importers: version: 8.2.2 cypress: specifier: ^13.9.0 - version: 13.13.1 + version: 13.13.2 del-cli: specifier: 5.0.0 version: 5.0.0 @@ -167,7 +170,7 @@ importers: version: 1.11.3 lint-staged: specifier: 13.0.3 - version: 13.0.3(enquirer@2.4.1) + version: 13.0.3 make-dir-cli: specifier: 3.0.0 version: 3.0.0 @@ -212,7 +215,7 @@ importers: version: 9.1.0(eslint@8.57.0) eslint-config-turbo: specifier: ^2.0.9 - version: 2.0.9(eslint@8.57.0) + version: 2.0.12(eslint@8.57.0) eslint-plugin-react: specifier: ^7.34.1 version: 7.35.0(eslint@8.57.0) @@ -238,15 +241,21 @@ importers: packages/zitadel-next: dependencies: + '@zitadel/client': + specifier: workspace:* + version: link:../zitadel-client '@zitadel/node': specifier: workspace:* version: link:../zitadel-node + '@zitadel/proto': + specifier: workspace:* + version: link:../zitadel-proto '@zitadel/react': specifier: workspace:* version: link:../zitadel-react next: specifier: ^14.2.5 - version: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.2.0))(react@18.2.0)(sass@1.77.8) + version: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.2.0) react: specifier: 18.2.0 version: 18.2.0 @@ -274,17 +283,17 @@ importers: dependencies: '@connectrpc/connect-node': specifier: ^1.4.0 - version: 1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0)) + version: 1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0) '@connectrpc/connect-web': specifier: ^1.4.0 - version: 1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0)) + version: 1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0) jose: specifier: ^5.3.0 version: 5.6.3 devDependencies: '@types/node': specifier: ^20.14.2 - version: 20.14.13 + version: 20.14.14 '@zitadel/client': specifier: workspace:* version: link:../zitadel-client @@ -305,7 +314,7 @@ importers: devDependencies: '@bufbuild/buf': specifier: ^1.35.1 - version: 1.35.1 + version: 1.36.0 packages/zitadel-react: dependencies: @@ -318,7 +327,7 @@ importers: version: 6.4.8 '@testing-library/react': specifier: ^14.0.0 - version: 14.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.3.1(react-dom@18.3.1)(react@18.3.1) '@types/react': specifier: ^18.2.17 version: 18.3.3 @@ -354,4289 +363,40 @@ importers: devDependencies: '@tailwindcss/forms': specifier: 0.5.3 - version: 0.5.3(tailwindcss@3.2.4(postcss@8.4.40)) + version: 0.5.3(tailwindcss@3.2.4) tailwindcss: specifier: ^3.2.4 - version: 3.2.4(postcss@8.4.40) + version: 3.2.4(postcss@8.4.21) packages/zitadel-tsconfig: {} packages: - '@adobe/css-tools@4.4.0': + /@adobe/css-tools@4.4.0: resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} + dev: true - '@ampproject/remapping@2.3.0': + /@ampproject/remapping@2.3.0: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - - '@babel/code-frame@7.24.7': - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.25.2': - resolution: {integrity: sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.25.2': - resolution: {integrity: sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.25.0': - resolution: {integrity: sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.25.2': - resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.24.7': - resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.25.2': - resolution: {integrity: sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.24.8': - resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-simple-access@7.24.7': - resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.24.8': - resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.24.8': - resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.25.0': - resolution: {integrity: sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==} - engines: {node: '>=6.9.0'} - - '@babel/highlight@7.24.7': - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.25.0': - resolution: {integrity: sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-transform-react-jsx-self@7.24.7': - resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.24.7': - resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/runtime@7.25.0': - resolution: {integrity: sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.25.0': - resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.25.2': - resolution: {integrity: sha512-s4/r+a7xTnny2O6FcZzqgT6nE4/GHEdcqj4qAeglbUOh0TeglEfmNJFAd/OLoVtGd6ZhAO8GCVvCNUO5t/VJVQ==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.25.2': - resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==} - engines: {node: '>=6.9.0'} - - '@bufbuild/buf-darwin-arm64@1.35.1': - resolution: {integrity: sha512-Yy+sk+8sg3LDvMSZLGUIoMCkZajkQSZkdxO96mpqJagKlEYPLGTtakVFCVNX9KgK/sv1bd9sU55iMGXE3+eIYw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@bufbuild/buf-darwin-x64@1.35.1': - resolution: {integrity: sha512-LcscoNTCHFeb5y9sitw4w6HWZtJ4Ja/MDBCUU9A8/OGHJSESV0JjhbvVHGNOIsKUbPq5p/SVjYA/Ab/wlmmpaA==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@bufbuild/buf-linux-aarch64@1.35.1': - resolution: {integrity: sha512-bPeiSURl8WFxCdawtJjAjUOMqknVTw763NLIDcbYSH1/wTiUbM5QeXCORRlHKXtMGM89SYU5AatcY9UhQ+sn9g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@bufbuild/buf-linux-x64@1.35.1': - resolution: {integrity: sha512-n6ziazYjNH9H1JjHiacGi20rIyZuKnsHjF8qWisO8KGajhnS/7tpq0VzYdorqqWyJ1TcnLBWHj+dWYuGay9Nag==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@bufbuild/buf-win32-arm64@1.35.1': - resolution: {integrity: sha512-3B65+iA1i/LDjJBseEpAvrkEI7VJqrvW39PyYVkIXSHHT917O+n95g74pn38A0XkggN5lEibLEkipBMDUfwMew==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@bufbuild/buf-win32-x64@1.35.1': - resolution: {integrity: sha512-iafrcs+1FMlD+3ZjI1kVBHGOluT6YcoAUETrGMbQjRha6dL5s2Ldr0G7zCKLIT13yEKG5QTyP8z8gVEpk8C8wg==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@bufbuild/buf@1.35.1': - resolution: {integrity: sha512-POtbb4wRhvgCmmClnuaQTpkHL4ukhFItuS/AaD7QDY0kamn4ExNJz4XlHG5jeJODaQ1Wq3f9qn7UIgUr6CUODw==} - engines: {node: '>=12'} - hasBin: true - - '@bufbuild/protobuf@1.10.0': - resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==} - - '@changesets/apply-release-plan@7.0.4': - resolution: {integrity: sha512-HLFwhKWayKinWAul0Vj+76jVx1Pc2v55MGPVjZ924Y/ROeSsBMFutv9heHmCUj48lJyRfOTJG5+ar+29FUky/A==} - - '@changesets/assemble-release-plan@6.0.3': - resolution: {integrity: sha512-bLNh9/Lgl1VwkjWZTq8JmRqH+hj7/Yzfz0jsQ/zJJ+FTmVqmqPj3szeKOri8O/hEM8JmHW019vh2gTO9iq5Cuw==} - - '@changesets/changelog-git@0.2.0': - resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} - - '@changesets/cli@2.27.7': - resolution: {integrity: sha512-6lr8JltiiXPIjDeYg4iM2MeePP6VN/JkmqBsVA5XRiy01hGS3y629LtSDvKcycj/w/5Eur1rEwby/MjcYS+e2A==} - hasBin: true - - '@changesets/config@3.0.2': - resolution: {integrity: sha512-cdEhS4t8woKCX2M8AotcV2BOWnBp09sqICxKapgLHf9m5KdENpWjyrFNMjkLqGJtUys9U+w93OxWT0czorVDfw==} - - '@changesets/errors@0.2.0': - resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} - - '@changesets/get-dependents-graph@2.1.1': - resolution: {integrity: sha512-LRFjjvigBSzfnPU2n/AhFsuWR5DK++1x47aq6qZ8dzYsPtS/I5mNhIGAS68IAxh1xjO9BTtz55FwefhANZ+FCA==} - - '@changesets/get-release-plan@4.0.3': - resolution: {integrity: sha512-6PLgvOIwTSdJPTtpdcr3sLtGatT+Jr22+cQwEBJBy6wP0rjB4yJ9lv583J9fVpn1bfQlBkDa8JxbS2g/n9lIyA==} - - '@changesets/get-version-range-type@0.4.0': - resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} - - '@changesets/git@3.0.0': - resolution: {integrity: sha512-vvhnZDHe2eiBNRFHEgMiGd2CT+164dfYyrJDhwwxTVD/OW0FUD6G7+4DIx1dNwkwjHyzisxGAU96q0sVNBns0w==} - - '@changesets/logger@0.1.0': - resolution: {integrity: sha512-pBrJm4CQm9VqFVwWnSqKEfsS2ESnwqwH+xR7jETxIErZcfd1u2zBSqrHbRHR7xjhSgep9x2PSKFKY//FAshA3g==} - - '@changesets/parse@0.4.0': - resolution: {integrity: sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==} - - '@changesets/pre@2.0.0': - resolution: {integrity: sha512-HLTNYX/A4jZxc+Sq8D1AMBsv+1qD6rmmJtjsCJa/9MSRybdxh0mjbTvE6JYZQ/ZiQ0mMlDOlGPXTm9KLTU3jyw==} - - '@changesets/read@0.6.0': - resolution: {integrity: sha512-ZypqX8+/im1Fm98K4YcZtmLKgjs1kDQ5zHpc2U1qdtNBmZZfo/IBiG162RoP0CUF05tvp2y4IspH11PLnPxuuw==} - - '@changesets/should-skip-package@0.1.0': - resolution: {integrity: sha512-FxG6Mhjw7yFStlSM7Z0Gmg3RiyQ98d/9VpQAZ3Fzr59dCOM9G6ZdYbjiSAt0XtFr9JR5U2tBaJWPjrkGGc618g==} - - '@changesets/types@4.1.0': - resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} - - '@changesets/types@6.0.0': - resolution: {integrity: sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==} - - '@changesets/write@0.3.1': - resolution: {integrity: sha512-SyGtMXzH3qFqlHKcvFY2eX+6b0NGiFcNav8AFsYwy5l8hejOeoeTDemu5Yjmke2V5jpzY+pBvM0vCCQ3gdZpfw==} - - '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - - '@connectrpc/connect-node@1.4.0': - resolution: {integrity: sha512-0ANnrr6SvsjevsWEgdzHy7BaHkluZyS6s4xNoVt7RBHFR5V/kT9lPokoIbYUOU9JHzdRgTaS3x5595mwUsu15g==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@bufbuild/protobuf': ^1.4.2 - '@connectrpc/connect': 1.4.0 - - '@connectrpc/connect-web@1.4.0': - resolution: {integrity: sha512-13aO4psFbbm7rdOFGV0De2Za64DY/acMspgloDlcOKzLPPs0yZkhp1OOzAQeiAIr7BM/VOHIA3p8mF0inxCYTA==} - peerDependencies: - '@bufbuild/protobuf': ^1.4.2 - '@connectrpc/connect': 1.4.0 - - '@connectrpc/connect@1.4.0': - resolution: {integrity: sha512-vZeOkKaAjyV4+RH3+rJZIfDFJAfr+7fyYr6sLDKbYX3uuTVszhFe9/YKf5DNqrDb5cKdKVlYkGn6DTDqMitAnA==} - peerDependencies: - '@bufbuild/protobuf': ^1.4.2 - - '@cypress/request@3.0.1': - resolution: {integrity: sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==} - engines: {node: '>= 6'} - - '@cypress/xvfb@1.2.4': - resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} - - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.23.0': - resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.23.0': - resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.23.0': - resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.23.0': - resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.23.0': - resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.23.0': - resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.23.0': - resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.23.0': - resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.23.0': - resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.23.0': - resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.23.0': - resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.23.0': - resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.23.0': - resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.23.0': - resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.23.0': - resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.23.0': - resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.23.0': - resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.23.0': - resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.23.0': - resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.23.0': - resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.23.0': - resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.23.0': - resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.23.0': - resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.23.0': - resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@eslint-community/eslint-utils@4.4.0': - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.11.0': - resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@eslint/js@8.57.0': - resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@fastify/busboy@2.1.1': - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} - - '@grpc/grpc-js@1.11.1': - resolution: {integrity: sha512-gyt/WayZrVPH2w/UTLansS7F9Nwld472JxxaETamrM8HNlsa+jSLNyKAZmhxI2Me4c3mQHFiS1wWHDY1g1Kthw==} - engines: {node: '>=12.10.0'} - - '@grpc/proto-loader@0.7.13': - resolution: {integrity: sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==} - engines: {node: '>=6'} - hasBin: true - - '@hapi/hoek@9.3.0': - resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} - - '@hapi/topo@5.1.0': - resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} - - '@headlessui/react@1.7.19': - resolution: {integrity: sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==} - engines: {node: '>=10'} - peerDependencies: - react: ^16 || ^17 || ^18 - react-dom: ^16 || ^17 || ^18 - - '@heroicons/react@2.1.3': - resolution: {integrity: sha512-fEcPfo4oN345SoqdlCDdSa4ivjaKbk0jTd+oubcgNxnNgAfzysfwWfQUr+51wigiWHQQRiZNd1Ao0M5Y3M2EGg==} - peerDependencies: - react: '>= 16' - - '@humanwhocodes/config-array@0.11.14': - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@js-sdsl/ordered-map@4.4.2': - resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} - - '@manypkg/find-root@1.1.0': - resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} - - '@manypkg/get-packages@1.1.3': - resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - - '@mapbox/node-pre-gyp@1.0.11': - resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} - hasBin: true - - '@next/env@14.2.3': - resolution: {integrity: sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==} - - '@next/env@14.2.5': - resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} - - '@next/eslint-plugin-next@14.2.5': - resolution: {integrity: sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==} - - '@next/swc-darwin-arm64@14.2.3': - resolution: {integrity: sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@next/swc-darwin-arm64@14.2.5': - resolution: {integrity: sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@next/swc-darwin-x64@14.2.3': - resolution: {integrity: sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@next/swc-darwin-x64@14.2.5': - resolution: {integrity: sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@next/swc-linux-arm64-gnu@14.2.3': - resolution: {integrity: sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@next/swc-linux-arm64-gnu@14.2.5': - resolution: {integrity: sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@next/swc-linux-arm64-musl@14.2.3': - resolution: {integrity: sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@next/swc-linux-arm64-musl@14.2.5': - resolution: {integrity: sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@next/swc-linux-x64-gnu@14.2.3': - resolution: {integrity: sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@next/swc-linux-x64-gnu@14.2.5': - resolution: {integrity: sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@next/swc-linux-x64-musl@14.2.3': - resolution: {integrity: sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@next/swc-linux-x64-musl@14.2.5': - resolution: {integrity: sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@next/swc-win32-arm64-msvc@14.2.3': - resolution: {integrity: sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@next/swc-win32-arm64-msvc@14.2.5': - resolution: {integrity: sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@next/swc-win32-ia32-msvc@14.2.3': - resolution: {integrity: sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@next/swc-win32-ia32-msvc@14.2.5': - resolution: {integrity: sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@next/swc-win32-x64-msvc@14.2.3': - resolution: {integrity: sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@next/swc-win32-x64-msvc@14.2.5': - resolution: {integrity: sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@protobufjs/aspromise@1.1.2': - resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - - '@protobufjs/base64@1.1.2': - resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - - '@protobufjs/codegen@2.0.4': - resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} - - '@protobufjs/eventemitter@1.1.0': - resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} - - '@protobufjs/fetch@1.1.0': - resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} - - '@protobufjs/float@1.0.2': - resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - - '@protobufjs/inquire@1.1.0': - resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} - - '@protobufjs/path@1.1.2': - resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} - - '@protobufjs/pool@1.1.0': - resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - - '@protobufjs/utf8@1.1.0': - resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - - '@rollup/rollup-android-arm-eabi@4.19.1': - resolution: {integrity: sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.19.1': - resolution: {integrity: sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.19.1': - resolution: {integrity: sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.19.1': - resolution: {integrity: sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-linux-arm-gnueabihf@4.19.1': - resolution: {integrity: sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.19.1': - resolution: {integrity: sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.19.1': - resolution: {integrity: sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.19.1': - resolution: {integrity: sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.19.1': - resolution: {integrity: sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.19.1': - resolution: {integrity: sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.19.1': - resolution: {integrity: sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.19.1': - resolution: {integrity: sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.19.1': - resolution: {integrity: sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.19.1': - resolution: {integrity: sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.19.1': - resolution: {integrity: sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.19.1': - resolution: {integrity: sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==} - cpu: [x64] - os: [win32] - - '@rushstack/eslint-patch@1.10.4': - resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==} - - '@sideway/address@4.1.5': - resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} - - '@sideway/formula@3.0.1': - resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} - - '@sideway/pinpoint@2.0.0': - resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} - - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - - '@swc/counter@0.1.3': - resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - - '@swc/helpers@0.5.5': - resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} - - '@tailwindcss/forms@0.5.3': - resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==} - peerDependencies: - tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' - - '@tailwindcss/forms@0.5.7': - resolution: {integrity: sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==} - peerDependencies: - tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' - - '@tanstack/react-virtual@3.8.4': - resolution: {integrity: sha512-Dq0VQr3QlTS2qL35g360QaJWBt7tCn/0xw4uZ0dHXPLO1Ak4Z4nVX4vuj1Npg1b/jqNMDToRtR5OIxM2NXRBWg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - - '@tanstack/virtual-core@3.8.4': - resolution: {integrity: sha512-iO5Ujgw3O1yIxWDe9FgUPNkGjyT657b1WNX52u+Wv1DyBFEpdCdGkuVaky0M3hHFqNWjAmHWTn4wgj9rTr7ZQg==} - - '@testing-library/dom@10.4.0': - resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} - engines: {node: '>=18'} - - '@testing-library/dom@9.3.4': - resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} - engines: {node: '>=14'} - - '@testing-library/jest-dom@6.4.8': - resolution: {integrity: sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==} - engines: {node: '>=14', npm: '>=6', yarn: '>=1'} - - '@testing-library/react@14.3.1': - resolution: {integrity: sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==} - engines: {node: '>=14'} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - - '@testing-library/react@15.0.7': - resolution: {integrity: sha512-cg0RvEdD1TIhhkm1IeYMQxrzy0MtUNfa3minv4MjbgcYzJAZ7yD0i0lwoPOTPr+INtiXFezt2o8xMSnyHhEn2Q==} - engines: {node: '>=18'} - peerDependencies: - '@types/react': ^18.0.0 - react: ^18.0.0 - react-dom: ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - '@types/aria-query@5.0.4': - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - - '@types/json5@0.0.29': - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - - '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - - '@types/ms@0.7.31': - resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} - - '@types/node@12.20.55': - resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - - '@types/node@18.11.9': - resolution: {integrity: sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==} - - '@types/node@20.14.13': - resolution: {integrity: sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==} - - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - - '@types/prop-types@15.7.12': - resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - - '@types/react-dom@18.0.9': - resolution: {integrity: sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg==} - - '@types/react-dom@18.3.0': - resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} - - '@types/react@17.0.80': - resolution: {integrity: sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==} - - '@types/react@18.2.8': - resolution: {integrity: sha512-lTyWUNrd8ntVkqycEEplasWy2OxNlShj3zqS0LuB1ENUGis5HodmhM7DtCoUGbxj3VW/WsGA0DUhpG6XrM7gPA==} - - '@types/react@18.3.3': - resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} - - '@types/scheduler@0.16.8': - resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} - - '@types/scheduler@0.23.0': - resolution: {integrity: sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==} - - '@types/semver@7.5.8': - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - - '@types/sinonjs__fake-timers@8.1.1': - resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==} - - '@types/sizzle@2.3.8': - resolution: {integrity: sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==} - - '@types/tinycolor2@1.4.3': - resolution: {integrity: sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==} - - '@types/uuid@9.0.8': - resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} - - '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - - '@typescript-eslint/parser@7.18.0': - resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/scope-manager@7.18.0': - resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@typescript-eslint/types@7.18.0': - resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@typescript-eslint/typescript-estree@7.18.0': - resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/visitor-keys@7.18.0': - resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - - '@vercel/analytics@1.3.1': - resolution: {integrity: sha512-xhSlYgAuJ6Q4WQGkzYTLmXwhYl39sWjoMA3nHxfkvG+WdBT25c563a7QhwwKivEOZtPJXifYHR1m2ihoisbWyA==} - peerDependencies: - next: '>= 13' - react: ^18 || ^19 - peerDependenciesMeta: - next: - optional: true - react: - optional: true - - '@vercel/git-hooks@1.0.0': - resolution: {integrity: sha512-OxDFAAdyiJ/H0b8zR9rFCu3BIb78LekBXOphOYG3snV4ULhKFX387pBPpqZ9HLiRTejBWBxYEahkw79tuIgdAA==} - - '@vitejs/plugin-react@4.3.1': - resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 - - '@vitest/expect@1.6.0': - resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} - - '@vitest/runner@1.6.0': - resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} - - '@vitest/snapshot@1.6.0': - resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} - - '@vitest/spy@1.6.0': - resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} - - '@vitest/utils@1.6.0': - resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} - - abbrev@1.1.1: - resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} - - abort-controller-x@0.4.3: - resolution: {integrity: sha512-VtUwTNU8fpMwvWGn4xE93ywbogTYsuT+AUxAXOeelbXuQVIwNmC5YLeho9sH4vZ4ITW8414TTAOG1nW6uIVHCA==} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn-node@1.8.2: - resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==} - - acorn-walk@7.2.0: - resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} - engines: {node: '>=0.4.0'} - - acorn-walk@8.3.3: - resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} - engines: {node: '>=0.4.0'} - - acorn@7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true - - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} - engines: {node: '>=0.4.0'} - hasBin: true - - agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - - agent-base@7.1.1: - resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} - engines: {node: '>= 14'} - - aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} - - aggregate-error@4.0.1: - resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} - engines: {node: '>=12'} - - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} - engines: {node: '>=12'} - - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - aproba@2.0.0: - resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} - - arch@2.2.0: - resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} - - are-we-there-yet@2.0.0: - resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} - engines: {node: '>=10'} - deprecated: This package is no longer supported. - - arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - aria-query@5.1.3: - resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} - - aria-query@5.3.0: - resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - - array-buffer-byte-length@1.0.1: - resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} - engines: {node: '>= 0.4'} - - array-includes@3.1.8: - resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} - engines: {node: '>= 0.4'} - - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - - array.prototype.findlast@1.2.5: - resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} - engines: {node: '>= 0.4'} - - array.prototype.findlastindex@1.2.5: - resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} - engines: {node: '>= 0.4'} - - array.prototype.flat@1.3.2: - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} - engines: {node: '>= 0.4'} - - array.prototype.flatmap@1.3.2: - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} - engines: {node: '>= 0.4'} - - array.prototype.tosorted@1.1.4: - resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} - engines: {node: '>= 0.4'} - - arraybuffer.prototype.slice@1.0.3: - resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} - engines: {node: '>= 0.4'} - - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - - asn1@0.2.6: - resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} - - assert-plus@1.0.0: - resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} - engines: {node: '>=0.8'} - - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - - ast-types-flow@0.0.8: - resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} - - astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - - async@3.2.5: - resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - at-least-node@1.0.0: - resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} - engines: {node: '>= 4.0.0'} - - autoprefixer@10.4.13: - resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - - aws-sign2@0.7.0: - resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} - - aws4@1.13.0: - resolution: {integrity: sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==} - - axe-core@4.10.0: - resolution: {integrity: sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==} - engines: {node: '>=4'} - - axios@1.7.2: - resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} - - axobject-query@3.1.1: - resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - bcrypt-pbkdf@1.0.2: - resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - - better-path-resolve@1.0.0: - resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} - engines: {node: '>=4'} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - blob-util@2.0.2: - resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==} - - bluebird@3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.23.2: - resolution: {integrity: sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - - bundle-require@5.0.0: - resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.18' - - busboy@1.6.0: - resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} - engines: {node: '>=10.16.0'} - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - cachedir@2.4.0: - resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} - engines: {node: '>=6'} - - call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} - engines: {node: '>= 0.4'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camelcase-css@2.0.1: - resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} - engines: {node: '>= 6'} - - camelcase-keys@7.0.2: - resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==} - engines: {node: '>=12'} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - caniuse-lite@1.0.30001644: - resolution: {integrity: sha512-YGvlOZB4QhZuiis+ETS0VXR+MExbFf4fZYYeMTEE0aTQd/RdIjkTyZjLrbYVKnHzppDvnOhritRVv+i7Go6mHw==} - - case-anything@2.1.13: - resolution: {integrity: sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==} - engines: {node: '>=12.13'} - - caseless@0.12.0: - resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} - - chai@4.5.0: - resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} - engines: {node: '>=4'} - - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - - chalk@3.0.0: - resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} - engines: {node: '>=8'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - - check-more-types@2.24.0: - resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} - engines: {node: '>= 0.8.0'} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - - clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - - clean-stack@4.2.0: - resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} - engines: {node: '>=12'} - - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} - - cli-table3@0.6.5: - resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} - engines: {node: 10.* || >= 12.*} - - cli-truncate@2.1.0: - resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} - engines: {node: '>=8'} - - cli-truncate@3.1.0: - resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - clsx@1.2.1: - resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} - engines: {node: '>=6'} - - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} - hasBin: true - - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - commander@6.2.1: - resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} - engines: {node: '>= 6'} - - commander@9.5.0: - resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} - engines: {node: ^12.20.0 || >=14} - - common-tags@1.8.2: - resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} - engines: {node: '>=4.0.0'} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - concurrently@8.2.2: - resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} - engines: {node: ^14.13.0 || >=16.0.0} - hasBin: true - - confbox@0.1.7: - resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} - - consola@3.2.3: - resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} - engines: {node: ^14.18.0 || >=16.10.0} - - console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - copy-to-clipboard@3.3.3: - resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} - - core-util-is@1.0.2: - resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} - - cross-spawn@5.1.0: - resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} - - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - - css.escape@1.5.1: - resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - cssstyle@4.0.1: - resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} - engines: {node: '>=18'} - - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - - cypress@13.13.1: - resolution: {integrity: sha512-8F9UjL5MDUdgC/S5hr8CGLHbS5gGht5UOV184qc2pFny43fnkoaKxlzH/U6//zmGu/xRTaKimNfjknLT8+UDFg==} - engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} - hasBin: true - - damerau-levenshtein@1.0.8: - resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - - dashdash@1.14.1: - resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} - engines: {node: '>=0.10'} - - data-urls@5.0.0: - resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} - engines: {node: '>=18'} - - data-view-buffer@1.0.1: - resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} - engines: {node: '>= 0.4'} - - data-view-byte-length@1.0.1: - resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} - engines: {node: '>= 0.4'} - - data-view-byte-offset@1.0.0: - resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} - engines: {node: '>= 0.4'} - - date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} - engines: {node: '>=0.11'} - - dayjs@1.11.12: - resolution: {integrity: sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==} - - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - - decamelize@5.0.1: - resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} - engines: {node: '>=10'} - - decimal.js@10.4.3: - resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} - - deep-eql@4.1.4: - resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} - engines: {node: '>=6'} - - deep-equal@2.2.3: - resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} - engines: {node: '>= 0.4'} - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - - defined@1.0.1: - resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} - - del-cli@5.0.0: - resolution: {integrity: sha512-rENFhUaYcjoMODwFhhlON+ogN7DoG+4+GFN+bsA1XeDt4w2OKQnQadFP1thHSAlK9FAtl88qgP66wOV+eFZZiQ==} - engines: {node: '>=14.16'} - hasBin: true - - del@7.1.0: - resolution: {integrity: sha512-v2KyNk7efxhlyHpjEvfyxaAihKKK0nWCuf6ZtqZcFFpQRG0bJ12Qsr0RpvsICMjAAZ8DOVCxrlqpxISlMHC4Kg==} - engines: {node: '>=14.16'} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - detect-indent@6.1.0: - resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} - engines: {node: '>=8'} - - detect-libc@1.0.3: - resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} - engines: {node: '>=0.10'} - hasBin: true - - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} - engines: {node: '>=8'} - - detective@5.2.1: - resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} - engines: {node: '>=0.8.0'} - hasBin: true - - didyoumean@1.2.2: - resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - - dlv@1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - - doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - - dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - - dom-accessibility-api@0.6.3: - resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} - - dotenv@16.0.3: - resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} - engines: {node: '>=12'} - - dprint-node@1.0.8: - resolution: {integrity: sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==} - - duplexer@0.1.2: - resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - ecc-jsbn@0.1.2: - resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} - - electron-to-chromium@1.5.3: - resolution: {integrity: sha512-QNdYSS5i8D9axWp/6XIezRObRHqaav/ur9z1VzCDUCH1XIFOr9WQk5xmgunhsTpjjgDy3oLxO/WMOVZlpUQrlA==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - - enhanced-resolve@5.17.1: - resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} - engines: {node: '>=10.13.0'} - - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - - env-cmd@10.1.0: - resolution: {integrity: sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==} - engines: {node: '>=8.0.0'} - hasBin: true - - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - - es-abstract@1.23.3: - resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} - engines: {node: '>= 0.4'} - - es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-get-iterator@1.1.3: - resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - - es-iterator-helpers@1.0.19: - resolution: {integrity: sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.0.0: - resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} - engines: {node: '>= 0.4'} - - es-shim-unscopables@1.0.2: - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} - - es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} - - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.23.0: - resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} - engines: {node: '>=6'} - - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - - eslint-config-next@14.2.5: - resolution: {integrity: sha512-zogs9zlOiZ7ka+wgUnmcM0KBEDjo4Jis7kxN1jvC0N4wynQ2MIx/KBkg4mVF63J5EK4W0QMCn7xO3vNisjaAoA==} - peerDependencies: - eslint: ^7.23.0 || ^8.0.0 - typescript: '>=3.3.1' - peerDependenciesMeta: - typescript: - optional: true - - eslint-config-prettier@9.1.0: - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - - eslint-config-turbo@2.0.9: - resolution: {integrity: sha512-FoIMElI8md/dR5DxjB5Om52KJfi7Qf7RInXeE+PGU6lN388rumppwyqEJsZ7vnR5GhGa9cLPt0vNZwEK9iXtKg==} - peerDependencies: - eslint: '>6.6.0' - - eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - - eslint-import-resolver-typescript@3.6.1: - resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' - - eslint-module-utils@2.8.1: - resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - - eslint-plugin-import@2.29.1: - resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - - eslint-plugin-jsx-a11y@6.9.0: - resolution: {integrity: sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g==} - engines: {node: '>=4.0'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - - eslint-plugin-react-hooks@4.6.2: - resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - - eslint-plugin-react@7.35.0: - resolution: {integrity: sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - - eslint-plugin-turbo@2.0.9: - resolution: {integrity: sha512-q4s4mg6JcXzz5zK4LC3c6FcWehGAWjGj7kIM76ZvG0KiR9Ks0znzjnAKW0NoiDP4s/gt3r4YPOpI357qWt167Q==} - peerDependencies: - eslint: '>6.6.0' - - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - event-stream@3.3.4: - resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} - - eventemitter2@6.4.7: - resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==} - - execa@4.1.0: - resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} - engines: {node: '>=10'} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - execa@6.1.0: - resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} - - executable@4.1.1: - resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} - engines: {node: '>=4'} - - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - extendable-error@0.1.7: - resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} - - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} - - extract-zip@2.0.1: - resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} - engines: {node: '>= 10.17.0'} - hasBin: true - - extsprintf@1.3.0: - resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} - engines: {'0': node >=0.6.0} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - - fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - - figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} - - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - find-yarn-workspace-root2@1.2.16: - resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} - - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - - follow-redirects@1.15.6: - resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - - foreground-child@3.2.1: - resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} - engines: {node: '>=14'} - - forever-agent@0.6.1: - resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} - - form-data@2.3.3: - resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} - engines: {node: '>= 0.12'} - - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - - from@0.1.7: - resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} - - fs-extra@7.0.1: - resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} - engines: {node: '>=6 <7 || >=8'} - - fs-extra@8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} - - fs-extra@9.1.0: - resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} - engines: {node: '>=10'} - - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} - engines: {node: '>= 0.4'} - - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - gauge@3.0.2: - resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} - engines: {node: '>=10'} - deprecated: This package is no longer supported. - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - - get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} - engines: {node: '>= 0.4'} - - get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - - get-symbol-description@1.0.2: - resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} - engines: {node: '>= 0.4'} - - get-tsconfig@4.7.6: - resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==} - - getos@3.2.1: - resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==} - - getpass@0.1.7: - resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob@10.3.10: - resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - - global-dirs@3.0.1: - resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} - engines: {node: '>=10'} - - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - globby@13.2.2: - resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - - gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - grpc-tools@1.11.3: - resolution: {integrity: sha512-cRSK2uhDKHtZ9hLRM35HxaMAMxyh/L7C96Ojt58DhQBdwTOQlV1VIJHSK6X/pDeSQKhaQqWMFfebt8tIcvRfjQ==} - hasBin: true - - hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - - has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-proto@1.0.3: - resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} - engines: {node: '>= 0.4'} - - has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - - html-encoding-sniffer@4.0.0: - resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} - engines: {node: '>=18'} - - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - - http-signature@1.3.6: - resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==} - engines: {node: '>=0.10'} - - https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - - https-proxy-agent@7.0.5: - resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} - engines: {node: '>= 14'} - - human-id@1.0.2: - resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} - - human-signals@1.1.1: - resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} - engines: {node: '>=8.12.0'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - human-signals@3.0.1: - resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} - engines: {node: '>=12.20.0'} - - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - ignore-by-default@1.0.1: - resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} - - ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} - engines: {node: '>= 4'} - - immutable@4.3.7: - resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} - - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ini@2.0.0: - resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} - engines: {node: '>=10'} - - internal-slot@1.0.7: - resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} - engines: {node: '>= 0.4'} - - is-arguments@1.1.1: - resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} - engines: {node: '>= 0.4'} - - is-array-buffer@3.0.4: - resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} - engines: {node: '>= 0.4'} - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-async-function@2.0.0: - resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} - engines: {node: '>= 0.4'} - - is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} - - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - - is-ci@3.0.1: - resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} - hasBin: true - - is-core-module@2.15.0: - resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==} - engines: {node: '>= 0.4'} - - is-data-view@1.0.1: - resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} - engines: {node: '>= 0.4'} - - is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-finalizationregistry@1.0.2: - resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} - - is-generator-function@1.0.10: - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} - engines: {node: '>= 0.4'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-installed-globally@0.4.0: - resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} - engines: {node: '>=10'} - - is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-path-cwd@3.0.0: - resolution: {integrity: sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - - is-path-inside@4.0.0: - resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} - engines: {node: '>=12'} - - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - - is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - - is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} - - is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} - - is-shared-array-buffer@1.0.3: - resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} - engines: {node: '>= 0.4'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} - - is-subdir@1.2.0: - resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} - engines: {node: '>=4'} - - is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.13: - resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} - engines: {node: '>= 0.4'} - - is-typedarray@1.0.0: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - - is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - - is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - - is-weakset@2.0.3: - resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} - engines: {node: '>= 0.4'} - - is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - isstream@0.1.2: - resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} - - iterator.prototype@1.1.2: - resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} - - jackspeak@2.3.6: - resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} - engines: {node: '>=14'} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - joi@17.13.3: - resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} - - jose@5.6.3: - resolution: {integrity: sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==} - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-tokens@9.0.0: - resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} - - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - - jsbn@0.1.1: - resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} - - jsdom@24.1.1: - resolution: {integrity: sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==} - engines: {node: '>=18'} - peerDependencies: - canvas: ^2.11.2 - peerDependenciesMeta: - canvas: - optional: true - - jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - - json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonfile@4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - - jsprim@2.0.2: - resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==} - engines: {'0': node >=0.6.0} - - jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - - language-subtag-registry@0.3.23: - resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} - - language-tags@1.0.9: - resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} - engines: {node: '>=0.10'} - - lazy-ass@1.6.0: - resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==} - engines: {node: '> 0.8'} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - lilconfig@2.0.5: - resolution: {integrity: sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==} - engines: {node: '>=10'} - - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - - lilconfig@3.1.2: - resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - lint-staged@13.0.3: - resolution: {integrity: sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug==} - engines: {node: ^14.13.1 || >=16.0.0} - hasBin: true - - listr2@3.14.0: - resolution: {integrity: sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==} - engines: {node: '>=10.0.0'} - peerDependencies: - enquirer: '>= 2.3.0 < 3' - peerDependenciesMeta: - enquirer: - optional: true - - listr2@4.0.5: - resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==} - engines: {node: '>=12'} - peerDependencies: - enquirer: '>= 2.3.0 < 3' - peerDependenciesMeta: - enquirer: - optional: true - - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - load-yaml-file@0.2.0: - resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} - engines: {node: '>=6'} - - local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} - engines: {node: '>=14'} - - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.camelcase@4.3.0: - resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - - lodash.sortby@4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - - lodash.startcase@4.4.0: - resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - - log-update@4.0.0: - resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} - engines: {node: '>=10'} - - long@5.2.3: - resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} - - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@4.1.5: - resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - - lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} - hasBin: true - - magic-string@0.30.11: - resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} - - make-dir-cli@3.0.0: - resolution: {integrity: sha512-8yCjIAOQ8tezWRJWUG3tbvN2I19hiVr8K5DPDVl8fECS3qz0ZbeL194ZGRdf8K3LgvbjDCTadge6NrN/I4XrNw==} - engines: {node: '>=12.17'} - hasBin: true - - make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - - map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - - map-stream@0.1.0: - resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} - - meow@10.1.5: - resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} - engines: {node: '>=8.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - - mini-svg-data-uri@1.4.4: - resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} - hasBin: true - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - - mlly@1.7.1: - resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} - - moment@2.30.1: - resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - - mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} - - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - next-themes@0.2.1: - resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} - peerDependencies: - next: '*' - react: '*' - react-dom: '*' - - next@14.2.3: - resolution: {integrity: sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==} - engines: {node: '>=18.17.0'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.41.2 - react: ^18.2.0 - react-dom: ^18.2.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@playwright/test': - optional: true - sass: - optional: true - - next@14.2.5: - resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} - engines: {node: '>=18.17.0'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.41.2 - react: ^18.2.0 - react-dom: ^18.2.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@playwright/test': - optional: true - sass: - optional: true - - nice-grpc-common@2.0.2: - resolution: {integrity: sha512-7RNWbls5kAL1QVUOXvBsv1uO0wPQK3lHv+cY1gwkTzirnG1Nop4cBJZubpgziNbaVc/bl9QJcyvsf/NQxa3rjQ==} - - nice-grpc@2.0.1: - resolution: {integrity: sha512-Q5CGXO08STsv+HAkXeFgRayANT62X1LnIDhNXdCf+LP0XaP7EiHM0Cr3QefnoFjDZAx/Kxq+qiQfY66BrtKcNQ==} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-releases@2.0.18: - resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - - nodemon@2.0.22: - resolution: {integrity: sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==} - engines: {node: '>=8.10.0'} - hasBin: true - - nopt@5.0.0: - resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} - engines: {node: '>=6'} - hasBin: true - - normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - npmlog@5.0.1: - resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} - deprecated: This package is no longer supported. - - nwsapi@2.2.12: - resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} - engines: {node: '>= 6'} - - object-inspect@1.13.2: - resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} - engines: {node: '>= 0.4'} - - object-is@1.1.6: - resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} - engines: {node: '>= 0.4'} - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} - engines: {node: '>= 0.4'} - - object.entries@1.1.8: - resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} - engines: {node: '>= 0.4'} - - object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} - - object.groupby@1.0.3: - resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} - engines: {node: '>= 0.4'} - - object.values@1.2.0: - resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} - engines: {node: '>= 0.4'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - - ospath@1.2.2: - resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==} - - outdent@0.5.0: - resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} - - p-filter@2.1.0: - resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} - engines: {node: '>=8'} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - p-map@2.1.0: - resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} - engines: {node: '>=6'} - - p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} - - p-map@5.5.0: - resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} - engines: {node: '>=12'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - package-json-from-dist@1.0.0: - resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - parse5@7.1.2: - resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - - pause-stream@0.0.11: - resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} - - pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - - performance-now@2.1.0: - resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - - picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} - hasBin: true - - pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - - pify@4.0.1: - resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} - engines: {node: '>=6'} - - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - - pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} - - pkg-types@1.1.3: - resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} - - possible-typed-array-names@1.0.0: - resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} - engines: {node: '>= 0.4'} - - postcss-import@14.1.0: - resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} - engines: {node: '>=10.0.0'} - peerDependencies: - postcss: ^8.0.0 - - postcss-js@4.0.1: - resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: ^8.4.21 - - postcss-load-config@3.1.4: - resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} - engines: {node: '>= 10'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: '>=8.0.9' - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true - - postcss-nested@6.0.0: - resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 - - postcss-selector-parser@6.1.1: - resolution: {integrity: sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==} - engines: {node: '>=4'} - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.4.21: - resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.4.40: - resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} - engines: {node: ^10 || ^12 || >=14} - - preferred-pm@3.1.4: - resolution: {integrity: sha512-lEHd+yEm22jXdCphDrkvIJQU66EuLojPPtvZkpKIkiD+l0DMThF/niqZKJSoU8Vl7iuvtmzyMhir9LdVy5WMnA==} - engines: {node: '>=10'} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier-plugin-organize-imports@4.0.0: - resolution: {integrity: sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==} - peerDependencies: - '@vue/language-plugin-pug': ^2.0.24 - prettier: '>=2.0' - typescript: '>=2.9' - vue-tsc: ^2.0.24 - peerDependenciesMeta: - '@vue/language-plugin-pug': - optional: true - vue-tsc: - optional: true - - prettier-plugin-tailwindcss@0.1.13: - resolution: {integrity: sha512-/EKQURUrxLu66CMUg4+1LwGdxnz8of7IDvrSLqEtDqhLH61SAlNNUSr90UTvZaemujgl3OH/VHg+fyGltrNixw==} - engines: {node: '>=12.17.0'} - peerDependencies: - prettier: '>=2.2.0' - - prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - - prettier@3.3.3: - resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} - engines: {node: '>=14'} - hasBin: true - - pretty-bytes@5.6.0: - resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} - engines: {node: '>=6'} - - pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - - protobufjs@7.3.2: - resolution: {integrity: sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==} - engines: {node: '>=12.0.0'} - - proxy-from-env@1.0.0: - resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} - - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - - ps-tree@1.2.0: - resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} - engines: {node: '>= 0.10'} - hasBin: true - - pseudomap@1.0.2: - resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} - - psl@1.9.0: - resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} - - pstree.remy@1.1.8: - resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} - - pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - qrcode.react@3.1.0: - resolution: {integrity: sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - - qs@6.10.4: - resolution: {integrity: sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==} - engines: {node: '>=0.6'} - - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} - peerDependencies: - react: ^18.3.1 - - react-hook-form@7.39.5: - resolution: {integrity: sha512-OE0HKyz5IPc6svN2wd+e+evidZrw4O4WZWAWYzQVZuHi+hYnHFSLnxOq0ddjbdmaLIsLHut/ab7j72y2QT3+KA==} - engines: {node: '>=12.22.0'} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 - - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - - react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} - engines: {node: '>=0.10.0'} - - react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} - engines: {node: '>=0.10.0'} - - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} - engines: {node: '>=0.10.0'} - - read-cache@1.0.0: - resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - - read-pkg-up@8.0.0: - resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} - engines: {node: '>=12'} - - read-pkg@6.0.0: - resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==} - engines: {node: '>=12'} - - read-yaml-file@1.1.0: - resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} - engines: {node: '>=6'} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} - - redent@4.0.0: - resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} - engines: {node: '>=12'} - - reflect.getprototypeof@1.0.6: - resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} - engines: {node: '>= 0.4'} - - regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - - regexp.prototype.flags@1.5.2: - resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} - engines: {node: '>= 0.4'} - - request-progress@3.0.0: - resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true - - resolve@2.0.0-next.5: - resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} - hasBin: true - - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - rollup@4.19.1: - resolution: {integrity: sha512-K5vziVlg7hTpYfFBI+91zHBEMo6jafYXpkMlqZjg7/zhIG9iHqazBf4xz9AVdjS9BruRn280ROqLI7G3OFRIlw==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - rrweb-cssom@0.6.0: - resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} - - rrweb-cssom@0.7.1: - resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - - safe-array-concat@1.1.2: - resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} - engines: {node: '>=0.4'} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safe-regex-test@1.0.3: - resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} - engines: {node: '>= 0.4'} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - sass@1.77.8: - resolution: {integrity: sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==} - engines: {node: '>=14.0.0'} - hasBin: true - - saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} - - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.0.0: - resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==} - hasBin: true - - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} - engines: {node: '>=10'} - hasBin: true - - server-only@0.0.1: - resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} - - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - - shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} - engines: {node: '>=0.10.0'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - shell-quote@1.8.1: - resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} - - side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} - engines: {node: '>= 0.4'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - simple-update-notifier@1.1.0: - resolution: {integrity: sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==} - engines: {node: '>=8.10.0'} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - slash@4.0.0: - resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} - engines: {node: '>=12'} - - slice-ansi@3.0.0: - resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} - engines: {node: '>=8'} - - slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - - slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} - - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} - engines: {node: '>=0.10.0'} - - source-map@0.8.0-beta.0: - resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} - engines: {node: '>= 8'} - - spawn-command@0.0.2: - resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} - - spawndamnit@2.0.0: - resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} - - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.18: - resolution: {integrity: sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==} - - split@0.3.3: - resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - sshpk@1.18.0: - resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} - engines: {node: '>=0.10.0'} - hasBin: true - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - start-server-and-test@2.0.5: - resolution: {integrity: sha512-2CV4pz69NJVJKQmJeSr+O+SPtOreu0yxvhPmSXclzmAKkPREuMabyMh+Txpzemjx0RDzXOcG2XkhiUuxjztSQw==} - engines: {node: '>=16'} - hasBin: true - - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - - stop-iteration-iterator@1.0.0: - resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} - engines: {node: '>= 0.4'} - - stream-combiner@0.0.4: - resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} - - streamsearch@1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} - - string-argv@0.3.2: - resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} - engines: {node: '>=0.6.19'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string.prototype.includes@2.0.0: - resolution: {integrity: sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==} - - string.prototype.matchall@4.0.11: - resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} - engines: {node: '>= 0.4'} - - string.prototype.repeat@1.0.0: - resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - - string.prototype.trim@1.2.9: - resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.8: - resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} - - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - - strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - - strip-indent@4.0.0: - resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} - engines: {node: '>=12'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - strip-literal@2.1.0: - resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} - - styled-jsx@5.1.1: - resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' - peerDependenciesMeta: - '@babel/core': - optional: true - babel-plugin-macros: - optional: true - - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - swr@2.2.5: - resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==} - peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 - - symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - - tailwindcss@3.2.4: - resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==} - engines: {node: '>=12.13.0'} - hasBin: true - peerDependencies: - postcss: ^8.0.9 - - tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} - engines: {node: '>=6'} - - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - - term-size@2.2.1: - resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} - engines: {node: '>=8'} - - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - throttleit@1.0.1: - resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - - tinybench@2.8.0: - resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} - - tinycolor2@1.4.2: - resolution: {integrity: sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==} - - tinypool@0.8.4: - resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} - engines: {node: '>=14.0.0'} - - tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} - engines: {node: '>=14.0.0'} - - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - - tmp@0.2.3: - resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} - engines: {node: '>=14.14'} - - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - toggle-selection@1.0.6: - resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} - - touch@3.1.1: - resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} - hasBin: true - - tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} - engines: {node: '>=6'} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - tr46@1.0.1: - resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} - - tr46@5.0.0: - resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} - engines: {node: '>=18'} - - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - trim-newlines@4.1.1: - resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==} - engines: {node: '>=12'} - - ts-api-utils@1.3.0: - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - - ts-error@1.0.6: - resolution: {integrity: sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==} - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - ts-poet@6.9.0: - resolution: {integrity: sha512-roe6W6MeZmCjRmppyfOURklO5tQFQ6Sg7swURKkwYJvV7dbGCrK28um5+51iW3twdPRKtwarqFAVMU6G1mvnuQ==} - - ts-proto-descriptors@1.16.0: - resolution: {integrity: sha512-3yKuzMLpltdpcyQji1PJZRfoo4OJjNieKTYkQY8pF7xGKsYz/RHe3aEe4KiRxcinoBmnEhmuI+yJTxLb922ULA==} - - ts-proto@1.181.1: - resolution: {integrity: sha512-lNmd/KEgqWtwDG9mIM3EpcxBx+URRVHkDP/EEJBgQJaQwmZFTk6VjHg56HNQswd114yXGfF+8pKQvJ2iH9KfWw==} - hasBin: true - - tsconfck@3.1.1: - resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - - tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - - tslib@2.6.3: - resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} - - tsup@8.2.3: - resolution: {integrity: sha512-6YNT44oUfXRbZuSMNmN36GzwPPIlD2wBccY7looM2fkTcxkf2NEmwr3OZuDZoySklnrIG4hoEtzy8yUXYOqNcg==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - - turbo-darwin-64@2.0.9: - resolution: {integrity: sha512-owlGsOaExuVGBUfrnJwjkL1BWlvefjSKczEAcpLx4BI7Oh6ttakOi+JyomkPkFlYElRpjbvlR2gP8WIn6M/+xQ==} - cpu: [x64] - os: [darwin] - - turbo-darwin-arm64@2.0.9: - resolution: {integrity: sha512-XAXkKkePth5ZPPE/9G9tTnPQx0C8UTkGWmNGYkpmGgRr8NedW+HrPsi9N0HcjzzIH9A4TpNYvtiV+WcwdaEjKA==} - cpu: [arm64] - os: [darwin] - - turbo-linux-64@2.0.9: - resolution: {integrity: sha512-l9wSgEjrCFM1aG16zItBsZ206ZlhSSx1owB8Cgskfv0XyIXRGHRkluihiaxkp+UeU5WoEfz4EN5toc+ICA0q0w==} - cpu: [x64] - os: [linux] - - turbo-linux-arm64@2.0.9: - resolution: {integrity: sha512-gRnjxXRne18B27SwxXMqL3fJu7jw/8kBrOBTBNRSmZZiG1Uu3nbnP7b4lgrA/bCku6C0Wligwqurvtpq6+nFHA==} - cpu: [arm64] - os: [linux] - - turbo-windows-64@2.0.9: - resolution: {integrity: sha512-ZVo0apxUvaRq4Vm1qhsfqKKhtRgReYlBVf9MQvVU1O9AoyydEQvLDO1ryqpXDZWpcHoFxHAQc9msjAMtE5K2lA==} - cpu: [x64] - os: [win32] - - turbo-windows-arm64@2.0.9: - resolution: {integrity: sha512-sGRz7c5Pey6y7y9OKi8ypbWNuIRPF9y8xcMqL56OZifSUSo+X2EOsOleR9MKxQXVaqHPGOUKWsE6y8hxBi9pag==} - cpu: [arm64] - os: [win32] - - turbo@2.0.9: - resolution: {integrity: sha512-QaLaUL1CqblSKKPgLrFW3lZWkWG4pGBQNW+q1ScJB5v1D/nFWtsrD/yZljW/bdawg90ihi4/ftQJ3h6fz1FamA==} - hasBin: true - - tweetnacl@0.14.5: - resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-detect@4.1.0: - resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} - engines: {node: '>=4'} - - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - type-fest@1.4.0: - resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} - engines: {node: '>=10'} - - typed-array-buffer@1.0.2: - resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} - engines: {node: '>= 0.4'} - - typed-array-byte-length@1.0.1: - resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.2: - resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.6: - resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} - engines: {node: '>= 0.4'} - - typescript@5.5.4: - resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.5.4: - resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} - - unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - - undefsafe@2.0.5: - resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} - - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - - undici@5.28.4: - resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} - engines: {node: '>=14.0'} - - universalify@0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} - - universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - untildify@4.0.0: - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} - engines: {node: '>=8'} - - update-browserslist-db@1.1.0: - resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - - use-sync-external-store@1.2.2: - resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - - verror@1.10.0: - resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} - engines: {'0': node >=0.6.0} - - vite-node@1.6.0: - resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - - vite-tsconfig-paths@4.3.2: - resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true - - vite@5.3.5: - resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - - vitest@1.6.0: - resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.6.0 - '@vitest/ui': 1.6.0 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - w3c-xmlserializer@5.0.0: - resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} - engines: {node: '>=18'} - - wait-on@7.2.0: - resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} - engines: {node: '>=12.0.0'} - hasBin: true - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - webidl-conversions@4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - - webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - - whatwg-encoding@3.1.1: - resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} - engines: {node: '>=18'} - - whatwg-mimetype@4.0.0: - resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} - engines: {node: '>=18'} - - whatwg-url@14.0.0: - resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} - engines: {node: '>=18'} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - whatwg-url@7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - - which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - - which-builtin-type@1.1.4: - resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==} - engines: {node: '>= 0.4'} - - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - - which-pm@2.2.0: - resolution: {integrity: sha512-MOiaDbA5ZZgUjkeMWM5EkJp4loW5ZRoa5bc3/aeMox/PJelMhE6t7S/mLuiY43DBupyxH+S0U1bTui9kWUlmsw==} - engines: {node: '>=8.15'} - - which-typed-array@1.1.15: - resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} - engines: {node: '>= 0.4'} - - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xml-name-validator@5.0.0: - resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} - engines: {node: '>=18'} - - xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yallist@2.1.2: - resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - - yaml@2.5.0: - resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==} - engines: {node: '>= 14'} - hasBin: true - - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - yocto-queue@1.1.1: - resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} - engines: {node: '>=12.20'} - -snapshots: - - '@adobe/css-tools@4.4.0': {} - - '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - '@babel/code-frame@7.24.7': + /@babel/code-frame@7.24.7: + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.24.7 picocolors: 1.0.1 - '@babel/compat-data@7.25.2': {} + /@babel/compat-data@7.25.2: + resolution: {integrity: sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==} + engines: {node: '>=6.9.0'} - '@babel/core@7.25.2': + /@babel/core@7.25.2: + resolution: {integrity: sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==} + engines: {node: '>=6.9.0'} dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.24.7 @@ -4644,9 +404,9 @@ snapshots: '@babel/helper-compilation-targets': 7.25.2 '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) '@babel/helpers': 7.25.0 - '@babel/parser': 7.25.0 + '@babel/parser': 7.25.3 '@babel/template': 7.25.0 - '@babel/traverse': 7.25.2 + '@babel/traverse': 7.25.3 '@babel/types': 7.25.2 convert-source-map: 2.0.0 debug: 4.3.6(supports-color@8.1.1) @@ -4656,94 +416,139 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.25.0': + /@babel/generator@7.25.0: + resolution: {integrity: sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==} + engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.25.2 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 - '@babel/helper-compilation-targets@7.25.2': + /@babel/helper-compilation-targets@7.25.2: + resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==} + engines: {node: '>=6.9.0'} dependencies: '@babel/compat-data': 7.25.2 '@babel/helper-validator-option': 7.24.8 - browserslist: 4.23.2 + browserslist: 4.23.3 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-module-imports@7.24.7': + /@babel/helper-module-imports@7.24.7: + resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.2 + '@babel/traverse': 7.25.3 '@babel/types': 7.25.2 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2)': + /@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2): + resolution: {integrity: sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.25.2 '@babel/helper-module-imports': 7.24.7 '@babel/helper-simple-access': 7.24.7 '@babel/helper-validator-identifier': 7.24.7 - '@babel/traverse': 7.25.2 + '@babel/traverse': 7.25.3 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.24.8': {} + /@babel/helper-plugin-utils@7.24.8: + resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==} + engines: {node: '>=6.9.0'} + dev: true - '@babel/helper-simple-access@7.24.7': + /@babel/helper-simple-access@7.24.7: + resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.2 + '@babel/traverse': 7.25.3 '@babel/types': 7.25.2 transitivePeerDependencies: - supports-color - '@babel/helper-string-parser@7.24.8': {} + /@babel/helper-string-parser@7.24.8: + resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} + engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.7': {} + /@babel/helper-validator-identifier@7.24.7: + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.24.8': {} + /@babel/helper-validator-option@7.24.8: + resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} + engines: {node: '>=6.9.0'} - '@babel/helpers@7.25.0': + /@babel/helpers@7.25.0: + resolution: {integrity: sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==} + engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.25.0 '@babel/types': 7.25.2 - '@babel/highlight@7.24.7': + /@babel/highlight@7.24.7: + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} dependencies: '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 picocolors: 1.0.1 - '@babel/parser@7.25.0': + /@babel/parser@7.25.3: + resolution: {integrity: sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==} + engines: {node: '>=6.0.0'} + hasBin: true dependencies: '@babel/types': 7.25.2 - '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.2)': + /@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.2): + resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + dev: true - '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.2)': + /@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.2): + resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + dev: true - '@babel/runtime@7.25.0': + /@babel/runtime@7.25.0: + resolution: {integrity: sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==} + engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 + dev: true - '@babel/template@7.25.0': + /@babel/template@7.25.0: + resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} + engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.24.7 - '@babel/parser': 7.25.0 + '@babel/parser': 7.25.3 '@babel/types': 7.25.2 - '@babel/traverse@7.25.2': + /@babel/traverse@7.25.3: + resolution: {integrity: sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==} + engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.24.7 '@babel/generator': 7.25.0 - '@babel/parser': 7.25.0 + '@babel/parser': 7.25.3 '@babel/template': 7.25.0 '@babel/types': 7.25.2 debug: 4.3.6(supports-color@8.1.1) @@ -4751,42 +556,88 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.25.2': + /@babel/types@7.25.2: + resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==} + engines: {node: '>=6.9.0'} dependencies: '@babel/helper-string-parser': 7.24.8 '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - '@bufbuild/buf-darwin-arm64@1.35.1': + /@bufbuild/buf-darwin-arm64@1.36.0: + resolution: {integrity: sha512-0sXbTAedj9KY7NSHbf4536SLQKjV4yhlREw44f9mV9kGFI3Jr2Dj1z8UDzB80Ti3YFgpVu2BbcOMXIDzQ6dleA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@bufbuild/buf-darwin-x64@1.35.1': + /@bufbuild/buf-darwin-x64@1.36.0: + resolution: {integrity: sha512-zKuE+awq1Bslv7WcJ5Rq2npCxAGeEFnQJ4tAMD0RjY8MZ9SjJRETmngU0xGeFdh/FM91+A8f8JehXtUX4D5/3g==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@bufbuild/buf-linux-aarch64@1.35.1': + /@bufbuild/buf-linux-aarch64@1.36.0: + resolution: {integrity: sha512-GaDGNezX2i2P2bpCwMwjhhoJcu0CKnFzK3c3loS7ErP9e+y3D+AubHkB+Y5MVnmzlV65GD7HcwWnygeUI1rBSg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@bufbuild/buf-linux-x64@1.35.1': + /@bufbuild/buf-linux-x64@1.36.0: + resolution: {integrity: sha512-gF2itk9vZrEYvHX1TobPdwDxh16DGTbfN4W5X66BUDKTZmdgctWBDRrj15UfAZO9oSjEc3oMQM+HkoWkOtsCxw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@bufbuild/buf-win32-arm64@1.35.1': + /@bufbuild/buf-win32-arm64@1.36.0: + resolution: {integrity: sha512-YQDM6hnk4gj4KNrC/rJGLeplD9Lntf2J6b9E1xIlJ7YfMLkXKt72iXT351lLUygN4MNjVhdWTP+GU+tdqMrfPQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true optional: true - '@bufbuild/buf-win32-x64@1.35.1': + /@bufbuild/buf-win32-x64@1.36.0: + resolution: {integrity: sha512-qp+TuO7mHDBpJf/CtMZmIM77txcoUhGJpbjSkW+4WGpcPtwmNtQqciTYqLHXwlBVDSq9GhxpNTyx/W+kXCJS/g==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true optional: true - '@bufbuild/buf@1.35.1': + /@bufbuild/buf@1.36.0: + resolution: {integrity: sha512-IhUx4aSlCGGD9wJQMsccs+jYsLowdbVPLtJnjJJvLWjxvatKV224iuKn6jP930k0rvejt/c6n/0sSWVu12zksQ==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true optionalDependencies: - '@bufbuild/buf-darwin-arm64': 1.35.1 - '@bufbuild/buf-darwin-x64': 1.35.1 - '@bufbuild/buf-linux-aarch64': 1.35.1 - '@bufbuild/buf-linux-x64': 1.35.1 - '@bufbuild/buf-win32-arm64': 1.35.1 - '@bufbuild/buf-win32-x64': 1.35.1 + '@bufbuild/buf-darwin-arm64': 1.36.0 + '@bufbuild/buf-darwin-x64': 1.36.0 + '@bufbuild/buf-linux-aarch64': 1.36.0 + '@bufbuild/buf-linux-x64': 1.36.0 + '@bufbuild/buf-win32-arm64': 1.36.0 + '@bufbuild/buf-win32-x64': 1.36.0 + dev: true - '@bufbuild/protobuf@1.10.0': {} + /@bufbuild/protobuf@1.10.0: + resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==} + dev: false - '@changesets/apply-release-plan@7.0.4': + /@changesets/apply-release-plan@7.0.4: + resolution: {integrity: sha512-HLFwhKWayKinWAul0Vj+76jVx1Pc2v55MGPVjZ924Y/ROeSsBMFutv9heHmCUj48lJyRfOTJG5+ar+29FUky/A==} dependencies: '@babel/runtime': 7.25.0 '@changesets/config': 3.0.2 @@ -4802,8 +653,10 @@ snapshots: prettier: 2.8.8 resolve-from: 5.0.0 semver: 7.6.3 + dev: true - '@changesets/assemble-release-plan@6.0.3': + /@changesets/assemble-release-plan@6.0.3: + resolution: {integrity: sha512-bLNh9/Lgl1VwkjWZTq8JmRqH+hj7/Yzfz0jsQ/zJJ+FTmVqmqPj3szeKOri8O/hEM8JmHW019vh2gTO9iq5Cuw==} dependencies: '@babel/runtime': 7.25.0 '@changesets/errors': 0.2.0 @@ -4812,12 +665,17 @@ snapshots: '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 semver: 7.6.3 + dev: true - '@changesets/changelog-git@0.2.0': + /@changesets/changelog-git@0.2.0: + resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} dependencies: '@changesets/types': 6.0.0 + dev: true - '@changesets/cli@2.27.7': + /@changesets/cli@2.27.7: + resolution: {integrity: sha512-6lr8JltiiXPIjDeYg4iM2MeePP6VN/JkmqBsVA5XRiy01hGS3y629LtSDvKcycj/w/5Eur1rEwby/MjcYS+e2A==} + hasBin: true dependencies: '@babel/runtime': 7.25.0 '@changesets/apply-release-plan': 7.0.4 @@ -4851,8 +709,10 @@ snapshots: semver: 7.6.3 spawndamnit: 2.0.0 term-size: 2.2.1 + dev: true - '@changesets/config@3.0.2': + /@changesets/config@3.0.2: + resolution: {integrity: sha512-cdEhS4t8woKCX2M8AotcV2BOWnBp09sqICxKapgLHf9m5KdENpWjyrFNMjkLqGJtUys9U+w93OxWT0czorVDfw==} dependencies: '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.1.1 @@ -4861,20 +721,26 @@ snapshots: '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 micromatch: 4.0.7 + dev: true - '@changesets/errors@0.2.0': + /@changesets/errors@0.2.0: + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} dependencies: extendable-error: 0.1.7 + dev: true - '@changesets/get-dependents-graph@2.1.1': + /@changesets/get-dependents-graph@2.1.1: + resolution: {integrity: sha512-LRFjjvigBSzfnPU2n/AhFsuWR5DK++1x47aq6qZ8dzYsPtS/I5mNhIGAS68IAxh1xjO9BTtz55FwefhANZ+FCA==} dependencies: '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 chalk: 2.4.2 fs-extra: 7.0.1 semver: 7.6.3 + dev: true - '@changesets/get-release-plan@4.0.3': + /@changesets/get-release-plan@4.0.3: + resolution: {integrity: sha512-6PLgvOIwTSdJPTtpdcr3sLtGatT+Jr22+cQwEBJBy6wP0rjB4yJ9lv583J9fVpn1bfQlBkDa8JxbS2g/n9lIyA==} dependencies: '@babel/runtime': 7.25.0 '@changesets/assemble-release-plan': 6.0.3 @@ -4883,10 +749,14 @@ snapshots: '@changesets/read': 0.6.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 + dev: true - '@changesets/get-version-range-type@0.4.0': {} + /@changesets/get-version-range-type@0.4.0: + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + dev: true - '@changesets/git@3.0.0': + /@changesets/git@3.0.0: + resolution: {integrity: sha512-vvhnZDHe2eiBNRFHEgMiGd2CT+164dfYyrJDhwwxTVD/OW0FUD6G7+4DIx1dNwkwjHyzisxGAU96q0sVNBns0w==} dependencies: '@babel/runtime': 7.25.0 '@changesets/errors': 0.2.0 @@ -4895,25 +765,33 @@ snapshots: is-subdir: 1.2.0 micromatch: 4.0.7 spawndamnit: 2.0.0 + dev: true - '@changesets/logger@0.1.0': + /@changesets/logger@0.1.0: + resolution: {integrity: sha512-pBrJm4CQm9VqFVwWnSqKEfsS2ESnwqwH+xR7jETxIErZcfd1u2zBSqrHbRHR7xjhSgep9x2PSKFKY//FAshA3g==} dependencies: chalk: 2.4.2 + dev: true - '@changesets/parse@0.4.0': + /@changesets/parse@0.4.0: + resolution: {integrity: sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==} dependencies: '@changesets/types': 6.0.0 js-yaml: 3.14.1 + dev: true - '@changesets/pre@2.0.0': + /@changesets/pre@2.0.0: + resolution: {integrity: sha512-HLTNYX/A4jZxc+Sq8D1AMBsv+1qD6rmmJtjsCJa/9MSRybdxh0mjbTvE6JYZQ/ZiQ0mMlDOlGPXTm9KLTU3jyw==} dependencies: '@babel/runtime': 7.25.0 '@changesets/errors': 0.2.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 + dev: true - '@changesets/read@0.6.0': + /@changesets/read@0.6.0: + resolution: {integrity: sha512-ZypqX8+/im1Fm98K4YcZtmLKgjs1kDQ5zHpc2U1qdtNBmZZfo/IBiG162RoP0CUF05tvp2y4IspH11PLnPxuuw==} dependencies: '@babel/runtime': 7.25.0 '@changesets/git': 3.0.0 @@ -4923,47 +801,77 @@ snapshots: chalk: 2.4.2 fs-extra: 7.0.1 p-filter: 2.1.0 + dev: true - '@changesets/should-skip-package@0.1.0': + /@changesets/should-skip-package@0.1.0: + resolution: {integrity: sha512-FxG6Mhjw7yFStlSM7Z0Gmg3RiyQ98d/9VpQAZ3Fzr59dCOM9G6ZdYbjiSAt0XtFr9JR5U2tBaJWPjrkGGc618g==} dependencies: '@babel/runtime': 7.25.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 + dev: true - '@changesets/types@4.1.0': {} + /@changesets/types@4.1.0: + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + dev: true - '@changesets/types@6.0.0': {} + /@changesets/types@6.0.0: + resolution: {integrity: sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==} + dev: true - '@changesets/write@0.3.1': + /@changesets/write@0.3.1: + resolution: {integrity: sha512-SyGtMXzH3qFqlHKcvFY2eX+6b0NGiFcNav8AFsYwy5l8hejOeoeTDemu5Yjmke2V5jpzY+pBvM0vCCQ3gdZpfw==} dependencies: '@babel/runtime': 7.25.0 '@changesets/types': 6.0.0 fs-extra: 7.0.1 human-id: 1.0.2 prettier: 2.8.8 + dev: true - '@colors/colors@1.5.0': + /@colors/colors@1.5.0: + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + requiresBuild: true + dev: true optional: true - '@connectrpc/connect-node@1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0))': + /@connectrpc/connect-node@1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0): + resolution: {integrity: sha512-0ANnrr6SvsjevsWEgdzHy7BaHkluZyS6s4xNoVt7RBHFR5V/kT9lPokoIbYUOU9JHzdRgTaS3x5595mwUsu15g==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@bufbuild/protobuf': ^1.4.2 + '@connectrpc/connect': 1.4.0 dependencies: '@bufbuild/protobuf': 1.10.0 '@connectrpc/connect': 1.4.0(@bufbuild/protobuf@1.10.0) undici: 5.28.4 + dev: false - '@connectrpc/connect-web@1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0))': + /@connectrpc/connect-web@1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0): + resolution: {integrity: sha512-13aO4psFbbm7rdOFGV0De2Za64DY/acMspgloDlcOKzLPPs0yZkhp1OOzAQeiAIr7BM/VOHIA3p8mF0inxCYTA==} + peerDependencies: + '@bufbuild/protobuf': ^1.4.2 + '@connectrpc/connect': 1.4.0 dependencies: '@bufbuild/protobuf': 1.10.0 '@connectrpc/connect': 1.4.0(@bufbuild/protobuf@1.10.0) + dev: false - '@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0)': + /@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0): + resolution: {integrity: sha512-vZeOkKaAjyV4+RH3+rJZIfDFJAfr+7fyYr6sLDKbYX3uuTVszhFe9/YKf5DNqrDb5cKdKVlYkGn6DTDqMitAnA==} + peerDependencies: + '@bufbuild/protobuf': ^1.4.2 dependencies: '@bufbuild/protobuf': 1.10.0 + dev: false - '@cypress/request@3.0.1': + /@cypress/request@3.0.1: + resolution: {integrity: sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==} + engines: {node: '>= 6'} dependencies: aws-sign2: 0.7.0 - aws4: 1.13.0 + aws4: 1.13.1 caseless: 0.12.0 combined-stream: 1.0.8 extend: 3.0.2 @@ -4980,163 +888,456 @@ snapshots: tough-cookie: 4.1.4 tunnel-agent: 0.6.0 uuid: 8.3.2 + dev: true - '@cypress/xvfb@1.2.4(supports-color@8.1.1)': + /@cypress/xvfb@1.2.4(supports-color@8.1.1): + resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} dependencies: debug: 3.2.7(supports-color@8.1.1) lodash.once: 4.1.1 transitivePeerDependencies: - supports-color + dev: true - '@esbuild/aix-ppc64@0.21.5': + /@esbuild/aix-ppc64@0.21.5: + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true optional: true - '@esbuild/aix-ppc64@0.23.0': + /@esbuild/aix-ppc64@0.23.0: + resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true optional: true - '@esbuild/android-arm64@0.21.5': + /@esbuild/android-arm64@0.21.5: + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true optional: true - '@esbuild/android-arm64@0.23.0': + /@esbuild/android-arm64@0.23.0: + resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true optional: true - '@esbuild/android-arm@0.21.5': + /@esbuild/android-arm@0.21.5: + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true optional: true - '@esbuild/android-arm@0.23.0': + /@esbuild/android-arm@0.23.0: + resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true optional: true - '@esbuild/android-x64@0.21.5': + /@esbuild/android-x64@0.21.5: + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true optional: true - '@esbuild/android-x64@0.23.0': + /@esbuild/android-x64@0.23.0: + resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true optional: true - '@esbuild/darwin-arm64@0.21.5': + /@esbuild/darwin-arm64@0.21.5: + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@esbuild/darwin-arm64@0.23.0': + /@esbuild/darwin-arm64@0.23.0: + resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@esbuild/darwin-x64@0.21.5': + /@esbuild/darwin-x64@0.21.5: + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@esbuild/darwin-x64@0.23.0': + /@esbuild/darwin-x64@0.23.0: + resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@esbuild/freebsd-arm64@0.21.5': + /@esbuild/freebsd-arm64@0.21.5: + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true optional: true - '@esbuild/freebsd-arm64@0.23.0': + /@esbuild/freebsd-arm64@0.23.0: + resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true optional: true - '@esbuild/freebsd-x64@0.21.5': + /@esbuild/freebsd-x64@0.21.5: + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true optional: true - '@esbuild/freebsd-x64@0.23.0': + /@esbuild/freebsd-x64@0.23.0: + resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-arm64@0.21.5': + /@esbuild/linux-arm64@0.21.5: + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-arm64@0.23.0': + /@esbuild/linux-arm64@0.23.0: + resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-arm@0.21.5': + /@esbuild/linux-arm@0.21.5: + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-arm@0.23.0': + /@esbuild/linux-arm@0.23.0: + resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-ia32@0.21.5': + /@esbuild/linux-ia32@0.21.5: + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-ia32@0.23.0': + /@esbuild/linux-ia32@0.23.0: + resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-loong64@0.21.5': + /@esbuild/linux-loong64@0.21.5: + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-loong64@0.23.0': + /@esbuild/linux-loong64@0.23.0: + resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-mips64el@0.21.5': + /@esbuild/linux-mips64el@0.21.5: + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-mips64el@0.23.0': + /@esbuild/linux-mips64el@0.23.0: + resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-ppc64@0.21.5': + /@esbuild/linux-ppc64@0.21.5: + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-ppc64@0.23.0': + /@esbuild/linux-ppc64@0.23.0: + resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-riscv64@0.21.5': + /@esbuild/linux-riscv64@0.21.5: + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-riscv64@0.23.0': + /@esbuild/linux-riscv64@0.23.0: + resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-s390x@0.21.5': + /@esbuild/linux-s390x@0.21.5: + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-s390x@0.23.0': + /@esbuild/linux-s390x@0.23.0: + resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-x64@0.21.5': + /@esbuild/linux-x64@0.21.5: + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/linux-x64@0.23.0': + /@esbuild/linux-x64@0.23.0: + resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@esbuild/netbsd-x64@0.21.5': + /@esbuild/netbsd-x64@0.21.5: + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true optional: true - '@esbuild/netbsd-x64@0.23.0': + /@esbuild/netbsd-x64@0.23.0: + resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true optional: true - '@esbuild/openbsd-arm64@0.23.0': + /@esbuild/openbsd-arm64@0.23.0: + resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + requiresBuild: true + dev: true optional: true - '@esbuild/openbsd-x64@0.21.5': + /@esbuild/openbsd-x64@0.21.5: + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true optional: true - '@esbuild/openbsd-x64@0.23.0': + /@esbuild/openbsd-x64@0.23.0: + resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true optional: true - '@esbuild/sunos-x64@0.21.5': + /@esbuild/sunos-x64@0.21.5: + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true optional: true - '@esbuild/sunos-x64@0.23.0': + /@esbuild/sunos-x64@0.23.0: + resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true optional: true - '@esbuild/win32-arm64@0.21.5': + /@esbuild/win32-arm64@0.21.5: + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true optional: true - '@esbuild/win32-arm64@0.23.0': + /@esbuild/win32-arm64@0.23.0: + resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true optional: true - '@esbuild/win32-ia32@0.21.5': + /@esbuild/win32-ia32@0.21.5: + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true optional: true - '@esbuild/win32-ia32@0.23.0': + /@esbuild/win32-ia32@0.23.0: + resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true optional: true - '@esbuild/win32-x64@0.21.5': + /@esbuild/win32-x64@0.21.5: + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true optional: true - '@esbuild/win32-x64@0.23.0': + /@esbuild/win32-x64@0.23.0: + resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: eslint: 8.57.0 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.11.0': {} + /@eslint-community/regexpp@4.11.0: + resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/eslintrc@2.1.4': + /@eslint/eslintrc@2.1.4: + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.6(supports-color@8.1.1) @@ -5150,40 +1351,69 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@8.57.0': {} + /@eslint/js@8.57.0: + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@fastify/busboy@2.1.1': {} + /@fastify/busboy@2.1.1: + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + dev: false - '@grpc/grpc-js@1.11.1': + /@grpc/grpc-js@1.11.1: + resolution: {integrity: sha512-gyt/WayZrVPH2w/UTLansS7F9Nwld472JxxaETamrM8HNlsa+jSLNyKAZmhxI2Me4c3mQHFiS1wWHDY1g1Kthw==} + engines: {node: '>=12.10.0'} dependencies: '@grpc/proto-loader': 0.7.13 '@js-sdsl/ordered-map': 4.4.2 + dev: false - '@grpc/proto-loader@0.7.13': + /@grpc/proto-loader@0.7.13: + resolution: {integrity: sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==} + engines: {node: '>=6'} + hasBin: true dependencies: lodash.camelcase: 4.3.0 long: 5.2.3 protobufjs: 7.3.2 yargs: 17.7.2 + dev: false - '@hapi/hoek@9.3.0': {} + /@hapi/hoek@9.3.0: + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + dev: true - '@hapi/topo@5.1.0': + /@hapi/topo@5.1.0: + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} dependencies: '@hapi/hoek': 9.3.0 + dev: true - '@headlessui/react@1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + /@headlessui/react@1.7.19(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==} + engines: {node: '>=10'} + peerDependencies: + react: ^16 || ^17 || ^18 + react-dom: ^16 || ^17 || ^18 dependencies: - '@tanstack/react-virtual': 3.8.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-virtual': 3.8.4(react-dom@18.3.1)(react@18.3.1) client-only: 0.0.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + dev: false - '@heroicons/react@2.1.3(react@18.3.1)': + /@heroicons/react@2.1.3(react@18.3.1): + resolution: {integrity: sha512-fEcPfo4oN345SoqdlCDdSa4ivjaKbk0jTd+oubcgNxnNgAfzysfwWfQUr+51wigiWHQQRiZNd1Ao0M5Y3M2EGg==} + peerDependencies: + react: '>= 16' dependencies: react: 18.3.1 + dev: false - '@humanwhocodes/config-array@0.11.14': + /@humanwhocodes/config-array@0.11.14: + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead dependencies: '@humanwhocodes/object-schema': 2.0.3 debug: 4.3.6(supports-color@8.1.1) @@ -5191,50 +1421,72 @@ snapshots: transitivePeerDependencies: - supports-color - '@humanwhocodes/module-importer@1.0.1': {} + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} - '@humanwhocodes/object-schema@2.0.3': {} + /@humanwhocodes/object-schema@2.0.3: + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead - '@isaacs/cliui@8.0.2': + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} dependencies: string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 + string-width-cjs: /string-width@4.2.3 strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 + strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 - '@jest/schemas@29.6.3': + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@sinclair/typebox': 0.27.8 + dev: true - '@jridgewell/gen-mapping@0.3.5': + /@jridgewell/gen-mapping@0.3.5: + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 - '@jridgewell/resolve-uri@3.1.2': {} + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': {} + /@jridgewell/set-array@1.2.1: + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.0': {} + /@jridgewell/sourcemap-codec@1.5.0: + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - '@jridgewell/trace-mapping@0.3.25': + /@jridgewell/trace-mapping@0.3.25: + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@js-sdsl/ordered-map@4.4.2': {} + /@js-sdsl/ordered-map@4.4.2: + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + dev: false - '@manypkg/find-root@1.1.0': + /@manypkg/find-root@1.1.0: + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} dependencies: '@babel/runtime': 7.25.0 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 + dev: true - '@manypkg/get-packages@1.1.3': + /@manypkg/get-packages@1.1.3: + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} dependencies: '@babel/runtime': 7.25.0 '@changesets/types': 4.1.0 @@ -5242,8 +1494,11 @@ snapshots: fs-extra: 8.1.0 globby: 11.1.0 read-yaml-file: 1.1.0 + dev: true - '@mapbox/node-pre-gyp@1.0.11': + /@mapbox/node-pre-gyp@1.0.11: + resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} + hasBin: true dependencies: detect-libc: 2.0.3 https-proxy-agent: 5.0.1 @@ -5257,193 +1512,438 @@ snapshots: transitivePeerDependencies: - encoding - supports-color + dev: true - '@next/env@14.2.3': {} + /@next/env@14.2.3: + resolution: {integrity: sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==} + dev: false - '@next/env@14.2.5': {} + /@next/env@14.2.5: + resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} + dev: false - '@next/eslint-plugin-next@14.2.5': + /@next/eslint-plugin-next@14.2.5: + resolution: {integrity: sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==} dependencies: glob: 10.3.10 + dev: false - '@next/swc-darwin-arm64@14.2.3': + /@next/swc-darwin-arm64@14.2.3: + resolution: {integrity: sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false optional: true - '@next/swc-darwin-arm64@14.2.5': + /@next/swc-darwin-arm64@14.2.5: + resolution: {integrity: sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false optional: true - '@next/swc-darwin-x64@14.2.3': + /@next/swc-darwin-x64@14.2.3: + resolution: {integrity: sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false optional: true - '@next/swc-darwin-x64@14.2.5': + /@next/swc-darwin-x64@14.2.5: + resolution: {integrity: sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false optional: true - '@next/swc-linux-arm64-gnu@14.2.3': + /@next/swc-linux-arm64-gnu@14.2.3: + resolution: {integrity: sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@next/swc-linux-arm64-gnu@14.2.5': + /@next/swc-linux-arm64-gnu@14.2.5: + resolution: {integrity: sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@next/swc-linux-arm64-musl@14.2.3': + /@next/swc-linux-arm64-musl@14.2.3: + resolution: {integrity: sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@next/swc-linux-arm64-musl@14.2.5': + /@next/swc-linux-arm64-musl@14.2.5: + resolution: {integrity: sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@next/swc-linux-x64-gnu@14.2.3': + /@next/swc-linux-x64-gnu@14.2.3: + resolution: {integrity: sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@next/swc-linux-x64-gnu@14.2.5': + /@next/swc-linux-x64-gnu@14.2.5: + resolution: {integrity: sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@next/swc-linux-x64-musl@14.2.3': + /@next/swc-linux-x64-musl@14.2.3: + resolution: {integrity: sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@next/swc-linux-x64-musl@14.2.5': + /@next/swc-linux-x64-musl@14.2.5: + resolution: {integrity: sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@next/swc-win32-arm64-msvc@14.2.3': + /@next/swc-win32-arm64-msvc@14.2.3: + resolution: {integrity: sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false optional: true - '@next/swc-win32-arm64-msvc@14.2.5': + /@next/swc-win32-arm64-msvc@14.2.5: + resolution: {integrity: sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false optional: true - '@next/swc-win32-ia32-msvc@14.2.3': + /@next/swc-win32-ia32-msvc@14.2.3: + resolution: {integrity: sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false optional: true - '@next/swc-win32-ia32-msvc@14.2.5': + /@next/swc-win32-ia32-msvc@14.2.5: + resolution: {integrity: sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false optional: true - '@next/swc-win32-x64-msvc@14.2.3': + /@next/swc-win32-x64-msvc@14.2.3: + resolution: {integrity: sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false optional: true - '@next/swc-win32-x64-msvc@14.2.5': + /@next/swc-win32-x64-msvc@14.2.5: + resolution: {integrity: sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false optional: true - '@nodelib/fs.scandir@2.1.5': + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - '@nodelib/fs.stat@2.0.5': {} + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} - '@nodelib/fs.walk@1.2.8': + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@pkgjs/parseargs@0.11.0': + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true optional: true - '@protobufjs/aspromise@1.1.2': {} + /@protobufjs/aspromise@1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - '@protobufjs/base64@1.1.2': {} + /@protobufjs/base64@1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - '@protobufjs/codegen@2.0.4': {} + /@protobufjs/codegen@2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} - '@protobufjs/eventemitter@1.1.0': {} + /@protobufjs/eventemitter@1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} - '@protobufjs/fetch@1.1.0': + /@protobufjs/fetch@1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/inquire': 1.1.0 - '@protobufjs/float@1.0.2': {} + /@protobufjs/float@1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - '@protobufjs/inquire@1.1.0': {} + /@protobufjs/inquire@1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} - '@protobufjs/path@1.1.2': {} + /@protobufjs/path@1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} - '@protobufjs/pool@1.1.0': {} + /@protobufjs/pool@1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - '@protobufjs/utf8@1.1.0': {} + /@protobufjs/utf8@1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - '@rollup/rollup-android-arm-eabi@4.19.1': + /@rollup/rollup-android-arm-eabi@4.20.0: + resolution: {integrity: sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-android-arm64@4.19.1': + /@rollup/rollup-android-arm64@4.20.0: + resolution: {integrity: sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-darwin-arm64@4.19.1': + /@rollup/rollup-darwin-arm64@4.20.0: + resolution: {integrity: sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-darwin-x64@4.19.1': + /@rollup/rollup-darwin-x64@4.20.0: + resolution: {integrity: sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.19.1': + /@rollup/rollup-linux-arm-gnueabihf@4.20.0: + resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-arm-musleabihf@4.19.1': + /@rollup/rollup-linux-arm-musleabihf@4.20.0: + resolution: {integrity: sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-arm64-gnu@4.19.1': + /@rollup/rollup-linux-arm64-gnu@4.20.0: + resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-arm64-musl@4.19.1': + /@rollup/rollup-linux-arm64-musl@4.20.0: + resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.19.1': + /@rollup/rollup-linux-powerpc64le-gnu@4.20.0: + resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-riscv64-gnu@4.19.1': + /@rollup/rollup-linux-riscv64-gnu@4.20.0: + resolution: {integrity: sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-s390x-gnu@4.19.1': + /@rollup/rollup-linux-s390x-gnu@4.20.0: + resolution: {integrity: sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-x64-gnu@4.19.1': + /@rollup/rollup-linux-x64-gnu@4.20.0: + resolution: {integrity: sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-x64-musl@4.19.1': + /@rollup/rollup-linux-x64-musl@4.20.0: + resolution: {integrity: sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-win32-arm64-msvc@4.19.1': + /@rollup/rollup-win32-arm64-msvc@4.20.0: + resolution: {integrity: sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-win32-ia32-msvc@4.19.1': + /@rollup/rollup-win32-ia32-msvc@4.20.0: + resolution: {integrity: sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-win32-x64-msvc@4.19.1': + /@rollup/rollup-win32-x64-msvc@4.20.0: + resolution: {integrity: sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true optional: true - '@rushstack/eslint-patch@1.10.4': {} + /@rushstack/eslint-patch@1.10.4: + resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==} + dev: false - '@sideway/address@4.1.5': + /@sideway/address@4.1.5: + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} dependencies: '@hapi/hoek': 9.3.0 + dev: true - '@sideway/formula@3.0.1': {} + /@sideway/formula@3.0.1: + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + dev: true - '@sideway/pinpoint@2.0.0': {} + /@sideway/pinpoint@2.0.0: + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + dev: true - '@sinclair/typebox@0.27.8': {} + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true - '@swc/counter@0.1.3': {} + /@swc/counter@0.1.3: + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + dev: false - '@swc/helpers@0.5.5': + /@swc/helpers@0.5.5: + resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} dependencies: '@swc/counter': 0.1.3 tslib: 2.6.3 + dev: false - '@tailwindcss/forms@0.5.3(tailwindcss@3.2.4(postcss@8.4.40))': - dependencies: - mini-svg-data-uri: 1.4.4 - tailwindcss: 3.2.4(postcss@8.4.40) - - '@tailwindcss/forms@0.5.7(tailwindcss@3.2.4(postcss@8.4.21))': + /@tailwindcss/forms@0.5.3(tailwindcss@3.2.4): + resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==} + peerDependencies: + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' dependencies: mini-svg-data-uri: 1.4.4 tailwindcss: 3.2.4(postcss@8.4.21) + dev: true - '@tanstack/react-virtual@3.8.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + /@tailwindcss/forms@0.5.7(tailwindcss@3.2.4): + resolution: {integrity: sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==} + peerDependencies: + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 3.2.4(postcss@8.4.21) + dev: false + + /@tanstack/react-virtual@3.8.4(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-Dq0VQr3QlTS2qL35g360QaJWBt7tCn/0xw4uZ0dHXPLO1Ak4Z4nVX4vuj1Npg1b/jqNMDToRtR5OIxM2NXRBWg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: '@tanstack/virtual-core': 3.8.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + dev: false - '@tanstack/virtual-core@3.8.4': {} + /@tanstack/virtual-core@3.8.4: + resolution: {integrity: sha512-iO5Ujgw3O1yIxWDe9FgUPNkGjyT657b1WNX52u+Wv1DyBFEpdCdGkuVaky0M3hHFqNWjAmHWTn4wgj9rTr7ZQg==} + dev: false - '@testing-library/dom@10.4.0': + /@testing-library/dom@10.4.0: + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + engines: {node: '>=18'} dependencies: '@babel/code-frame': 7.24.7 '@babel/runtime': 7.25.0 @@ -5453,8 +1953,11 @@ snapshots: dom-accessibility-api: 0.5.16 lz-string: 1.5.0 pretty-format: 27.5.1 + dev: true - '@testing-library/dom@9.3.4': + /@testing-library/dom@9.3.4: + resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} + engines: {node: '>=14'} dependencies: '@babel/code-frame': 7.24.7 '@babel/runtime': 7.25.0 @@ -5464,8 +1967,11 @@ snapshots: dom-accessibility-api: 0.5.16 lz-string: 1.5.0 pretty-format: 27.5.1 + dev: true - '@testing-library/jest-dom@6.4.8': + /@testing-library/jest-dom@6.4.8: + resolution: {integrity: sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} dependencies: '@adobe/css-tools': 4.4.0 '@babel/runtime': 7.25.0 @@ -5475,113 +1981,191 @@ snapshots: dom-accessibility-api: 0.6.3 lodash: 4.17.21 redent: 3.0.0 + dev: true - '@testing-library/react@14.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + /@testing-library/react@14.3.1(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==} + engines: {node: '>=14'} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 dependencies: '@babel/runtime': 7.25.0 '@testing-library/dom': 9.3.4 '@types/react-dom': 18.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + dev: true - '@testing-library/react@15.0.7(@types/react@18.2.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + /@testing-library/react@15.0.7(@types/react@18.2.8)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-cg0RvEdD1TIhhkm1IeYMQxrzy0MtUNfa3minv4MjbgcYzJAZ7yD0i0lwoPOTPr+INtiXFezt2o8xMSnyHhEn2Q==} + engines: {node: '>=18'} + peerDependencies: + '@types/react': ^18.0.0 + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@babel/runtime': 7.25.0 '@testing-library/dom': 10.4.0 + '@types/react': 18.2.8 '@types/react-dom': 18.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.2.8 + dev: true - '@types/aria-query@5.0.4': {} + /@types/aria-query@5.0.4: + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + dev: true - '@types/babel__core@7.20.5': + /@types/babel__core@7.20.5: + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} dependencies: - '@babel/parser': 7.25.0 + '@babel/parser': 7.25.3 '@babel/types': 7.25.2 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 + dev: true - '@types/babel__generator@7.6.8': + /@types/babel__generator@7.6.8: + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} dependencies: '@babel/types': 7.25.2 + dev: true - '@types/babel__template@7.4.4': + /@types/babel__template@7.4.4: + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} dependencies: - '@babel/parser': 7.25.0 + '@babel/parser': 7.25.3 '@babel/types': 7.25.2 + dev: true - '@types/babel__traverse@7.20.6': + /@types/babel__traverse@7.20.6: + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} dependencies: '@babel/types': 7.25.2 + dev: true - '@types/estree@1.0.5': {} + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: true - '@types/json5@0.0.29': {} + /@types/json5@0.0.29: + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + dev: false - '@types/minimist@1.2.5': {} + /@types/minimist@1.2.5: + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + dev: true - '@types/ms@0.7.31': {} + /@types/ms@0.7.31: + resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} + dev: true - '@types/node@12.20.55': {} + /@types/node@12.20.55: + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + dev: true - '@types/node@18.11.9': {} + /@types/node@18.11.9: + resolution: {integrity: sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==} + dev: true - '@types/node@20.14.13': + /@types/node@20.14.14: + resolution: {integrity: sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==} dependencies: undici-types: 5.26.5 - '@types/normalize-package-data@2.4.4': {} + /@types/normalize-package-data@2.4.4: + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + dev: true - '@types/prop-types@15.7.12': {} + /@types/prop-types@15.7.12: + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + dev: true - '@types/react-dom@18.0.9': + /@types/react-dom@18.0.9: + resolution: {integrity: sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg==} dependencies: '@types/react': 18.3.3 + dev: true - '@types/react-dom@18.3.0': + /@types/react-dom@18.3.0: + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} dependencies: '@types/react': 18.3.3 + dev: true - '@types/react@17.0.80': + /@types/react@17.0.80: + resolution: {integrity: sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==} dependencies: '@types/prop-types': 15.7.12 '@types/scheduler': 0.16.8 csstype: 3.1.3 + dev: true - '@types/react@18.2.8': + /@types/react@18.2.8: + resolution: {integrity: sha512-lTyWUNrd8ntVkqycEEplasWy2OxNlShj3zqS0LuB1ENUGis5HodmhM7DtCoUGbxj3VW/WsGA0DUhpG6XrM7gPA==} dependencies: '@types/prop-types': 15.7.12 '@types/scheduler': 0.23.0 csstype: 3.1.3 + dev: true - '@types/react@18.3.3': + /@types/react@18.3.3: + resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} dependencies: '@types/prop-types': 15.7.12 csstype: 3.1.3 + dev: true - '@types/scheduler@0.16.8': {} + /@types/scheduler@0.16.8: + resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} + dev: true - '@types/scheduler@0.23.0': {} + /@types/scheduler@0.23.0: + resolution: {integrity: sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==} + dev: true - '@types/semver@7.5.8': {} + /@types/semver@7.5.8: + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + dev: true - '@types/sinonjs__fake-timers@8.1.1': {} + /@types/sinonjs__fake-timers@8.1.1: + resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==} + dev: true - '@types/sizzle@2.3.8': {} + /@types/sizzle@2.3.8: + resolution: {integrity: sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==} + dev: true - '@types/tinycolor2@1.4.3': {} + /@types/tinycolor2@1.4.3: + resolution: {integrity: sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==} + dev: true - '@types/uuid@9.0.8': {} + /@types/uuid@9.0.8: + resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} + dev: true - '@types/yauzl@2.10.3': + /@types/yauzl@2.10.3: + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + requiresBuild: true dependencies: - '@types/node': 20.14.13 + '@types/node': 20.14.14 + dev: true optional: true - '@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4)': + /@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4): + resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 @@ -5589,19 +2173,32 @@ snapshots: '@typescript-eslint/visitor-keys': 7.18.0 debug: 4.3.6(supports-color@8.1.1) eslint: 8.57.0 - optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - supports-color + dev: false - '@typescript-eslint/scope-manager@7.18.0': + /@typescript-eslint/scope-manager@7.18.0: + resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} + engines: {node: ^18.18.0 || >=20.0.0} dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 + dev: false - '@typescript-eslint/types@7.18.0': {} + /@typescript-eslint/types@7.18.0: + resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} + engines: {node: ^18.18.0 || >=20.0.0} + dev: false - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.5.4)': + /@typescript-eslint/typescript-estree@7.18.0(typescript@5.5.4): + resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 @@ -5611,180 +2208,285 @@ snapshots: minimatch: 9.0.5 semver: 7.6.3 ts-api-utils: 1.3.0(typescript@5.5.4) - optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - supports-color + dev: false - '@typescript-eslint/visitor-keys@7.18.0': + /@typescript-eslint/visitor-keys@7.18.0: + resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} + engines: {node: ^18.18.0 || >=20.0.0} dependencies: '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 + dev: false - '@ungap/structured-clone@1.2.0': {} + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - '@vercel/analytics@1.3.1(next@14.2.3(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)': + /@vercel/analytics@1.3.1(next@14.2.3)(react@18.3.1): + resolution: {integrity: sha512-xhSlYgAuJ6Q4WQGkzYTLmXwhYl39sWjoMA3nHxfkvG+WdBT25c563a7QhwwKivEOZtPJXifYHR1m2ihoisbWyA==} + peerDependencies: + next: '>= 13' + react: ^18 || ^19 + peerDependenciesMeta: + next: + optional: true + react: + optional: true dependencies: - server-only: 0.0.1 - optionalDependencies: - next: 14.2.3(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) + next: 14.2.3(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8) react: 18.3.1 + server-only: 0.0.1 + dev: false - '@vercel/git-hooks@1.0.0': {} + /@vercel/git-hooks@1.0.0: + resolution: {integrity: sha512-OxDFAAdyiJ/H0b8zR9rFCu3BIb78LekBXOphOYG3snV4ULhKFX387pBPpqZ9HLiRTejBWBxYEahkw79tuIgdAA==} + requiresBuild: true + dev: true - '@vitejs/plugin-react@4.3.1(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))': + /@vitejs/plugin-react@4.3.1(vite@5.3.5): + resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 dependencies: '@babel/core': 7.25.2 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vite: 5.3.5 transitivePeerDependencies: - supports-color + dev: true - '@vitest/expect@1.6.0': + /@vitest/expect@1.6.0: + resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} dependencies: '@vitest/spy': 1.6.0 '@vitest/utils': 1.6.0 chai: 4.5.0 + dev: true - '@vitest/runner@1.6.0': + /@vitest/runner@1.6.0: + resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} dependencies: '@vitest/utils': 1.6.0 p-limit: 5.0.0 pathe: 1.1.2 + dev: true - '@vitest/snapshot@1.6.0': + /@vitest/snapshot@1.6.0: + resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} dependencies: magic-string: 0.30.11 pathe: 1.1.2 pretty-format: 29.7.0 + dev: true - '@vitest/spy@1.6.0': + /@vitest/spy@1.6.0: + resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} dependencies: tinyspy: 2.2.1 + dev: true - '@vitest/utils@1.6.0': + /@vitest/utils@1.6.0: + resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} dependencies: diff-sequences: 29.6.3 estree-walker: 3.0.3 loupe: 2.3.7 pretty-format: 29.7.0 + dev: true - abbrev@1.1.1: {} + /abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: true - abort-controller-x@0.4.3: {} + /abort-controller-x@0.4.3: + resolution: {integrity: sha512-VtUwTNU8fpMwvWGn4xE93ywbogTYsuT+AUxAXOeelbXuQVIwNmC5YLeho9sH4vZ4ITW8414TTAOG1nW6uIVHCA==} + dev: false - acorn-jsx@5.3.2(acorn@8.12.1): + /acorn-jsx@5.3.2(acorn@8.12.1): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: acorn: 8.12.1 - acorn-node@1.8.2: + /acorn-node@1.8.2: + resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==} dependencies: acorn: 7.4.1 acorn-walk: 7.2.0 xtend: 4.0.2 - acorn-walk@7.2.0: {} + /acorn-walk@7.2.0: + resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} + engines: {node: '>=0.4.0'} - acorn-walk@8.3.3: + /acorn-walk@8.3.3: + resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} + engines: {node: '>=0.4.0'} dependencies: acorn: 8.12.1 + dev: true - acorn@7.4.1: {} + /acorn@7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true - acorn@8.12.1: {} + /acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true - agent-base@6.0.2: + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} dependencies: debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color + dev: true - agent-base@7.1.1: + /agent-base@7.1.1: + resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + engines: {node: '>= 14'} dependencies: debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color + dev: true - aggregate-error@3.1.0: + /aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 + dev: true - aggregate-error@4.0.1: + /aggregate-error@4.0.1: + resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} + engines: {node: '>=12'} dependencies: clean-stack: 4.2.0 indent-string: 5.0.0 + dev: true - ajv@6.12.6: + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ansi-colors@4.1.3: {} + /ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + dev: true - ansi-escapes@4.3.2: + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} dependencies: type-fest: 0.21.3 + dev: true - ansi-regex@5.0.1: {} + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} - ansi-regex@6.0.1: {} + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} - ansi-styles@3.2.1: + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} dependencies: color-convert: 1.9.3 - ansi-styles@4.3.0: + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true - ansi-styles@6.2.1: {} + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} - any-promise@1.3.0: {} + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true - anymatch@3.1.3: + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 - aproba@2.0.0: {} + /aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + dev: true - arch@2.2.0: {} + /arch@2.2.0: + resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} + dev: true - are-we-there-yet@2.0.0: + /are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. dependencies: delegates: 1.0.0 readable-stream: 3.6.2 + dev: true - arg@5.0.2: {} + /arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - argparse@1.0.10: + /argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} dependencies: sprintf-js: 1.0.3 + dev: true - argparse@2.0.1: {} + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - aria-query@5.1.3: + /aria-query@5.1.3: + resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} dependencies: deep-equal: 2.2.3 - aria-query@5.3.0: + /aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: dequal: 2.0.3 + dev: true - array-buffer-byte-length@1.0.1: + /array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 is-array-buffer: 3.0.4 - array-includes@3.1.8: + /array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 @@ -5792,10 +2494,15 @@ snapshots: es-object-atoms: 1.0.0 get-intrinsic: 1.2.4 is-string: 1.0.7 + dev: false - array-union@2.1.0: {} + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} - array.prototype.findlast@1.2.5: + /array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 @@ -5803,8 +2510,11 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.0.0 es-shim-unscopables: 1.0.2 + dev: false - array.prototype.findlastindex@1.2.5: + /array.prototype.findlastindex@1.2.5: + resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 @@ -5812,30 +2522,42 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.0.0 es-shim-unscopables: 1.0.2 + dev: false - array.prototype.flat@1.3.2: + /array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 es-shim-unscopables: 1.0.2 + dev: false - array.prototype.flatmap@1.3.2: + /array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 es-shim-unscopables: 1.0.2 + dev: false - array.prototype.tosorted@1.1.4: + /array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 es-errors: 1.3.0 es-shim-unscopables: 1.0.2 + dev: false - arraybuffer.prototype.slice@1.0.3: + /arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.1 call-bind: 1.0.7 @@ -5845,118 +2567,201 @@ snapshots: get-intrinsic: 1.2.4 is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 + dev: false - arrify@1.0.1: {} + /arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true - asn1@0.2.6: + /asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} dependencies: safer-buffer: 2.1.2 + dev: true - assert-plus@1.0.0: {} + /assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + dev: true - assertion-error@1.1.0: {} + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true - ast-types-flow@0.0.8: {} + /ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + dev: false - astral-regex@2.0.0: {} + /astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + dev: true - async@3.2.5: {} + /async@3.2.5: + resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} + dev: true - asynckit@0.4.0: {} + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: true - at-least-node@1.0.0: {} + /at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + dev: true - autoprefixer@10.4.13(postcss@8.4.21): + /autoprefixer@10.4.13(postcss@8.4.21): + resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 dependencies: - browserslist: 4.23.2 - caniuse-lite: 1.0.30001644 + browserslist: 4.23.3 + caniuse-lite: 1.0.30001650 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.1 postcss: 8.4.21 postcss-value-parser: 4.2.0 + dev: true - available-typed-arrays@1.0.7: + /available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} dependencies: possible-typed-array-names: 1.0.0 - aws-sign2@0.7.0: {} + /aws-sign2@0.7.0: + resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} + dev: true - aws4@1.13.0: {} + /aws4@1.13.1: + resolution: {integrity: sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==} + dev: true - axe-core@4.10.0: {} + /axe-core@4.10.0: + resolution: {integrity: sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==} + engines: {node: '>=4'} + dev: false - axios@1.7.2(debug@4.3.6): + /axios@1.7.3(debug@4.3.6): + resolution: {integrity: sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==} dependencies: follow-redirects: 1.15.6(debug@4.3.6) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug + dev: true - axobject-query@3.1.1: + /axobject-query@3.1.1: + resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} dependencies: deep-equal: 2.2.3 + dev: false - balanced-match@1.0.2: {} + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base64-js@1.5.1: {} + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: true - bcrypt-pbkdf@1.0.2: + /bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} dependencies: tweetnacl: 0.14.5 + dev: true - better-path-resolve@1.0.0: + /better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} dependencies: is-windows: 1.0.2 + dev: true - binary-extensions@2.3.0: {} + /binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} - blob-util@2.0.2: {} + /blob-util@2.0.2: + resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==} + dev: true - bluebird@3.7.2: {} + /bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + dev: true - brace-expansion@1.1.11: + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - braces@3.0.3: + /braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} dependencies: fill-range: 7.1.1 - browserslist@4.23.2: + /browserslist@4.23.3: + resolution: {integrity: sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true dependencies: - caniuse-lite: 1.0.30001644 - electron-to-chromium: 1.5.3 + caniuse-lite: 1.0.30001650 + electron-to-chromium: 1.5.5 node-releases: 2.0.18 - update-browserslist-db: 1.1.0(browserslist@4.23.2) + update-browserslist-db: 1.1.0(browserslist@4.23.3) - buffer-crc32@0.2.13: {} + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: true - buffer@5.7.1: + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + dev: true - bundle-require@5.0.0(esbuild@0.23.0): + /bundle-require@5.0.0(esbuild@0.23.0): + resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' dependencies: esbuild: 0.23.0 load-tsconfig: 0.2.5 + dev: true - busboy@1.6.0: + /busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} dependencies: streamsearch: 1.1.0 + dev: false - cac@6.7.14: {} + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true - cachedir@2.4.0: {} + /cachedir@2.4.0: + resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} + engines: {node: '>=6'} + dev: true - call-bind@1.0.7: + /call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 @@ -5964,26 +2769,44 @@ snapshots: get-intrinsic: 1.2.4 set-function-length: 1.2.2 - callsites@3.1.0: {} + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} - camelcase-css@2.0.1: {} + /camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} - camelcase-keys@7.0.2: + /camelcase-keys@7.0.2: + resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==} + engines: {node: '>=12'} dependencies: camelcase: 6.3.0 map-obj: 4.3.0 quick-lru: 5.1.1 type-fest: 1.4.0 + dev: true - camelcase@6.3.0: {} + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true - caniuse-lite@1.0.30001644: {} + /caniuse-lite@1.0.30001650: + resolution: {integrity: sha512-fgEc7hP/LB7iicdXHUI9VsBsMZmUmlVJeQP2qqQW+3lkqVhbmjEU8zp+h5stWeilX+G7uXuIUIIlWlDw9jdt8g==} - case-anything@2.1.13: {} + /case-anything@2.1.13: + resolution: {integrity: sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==} + engines: {node: '>=12.13'} + dev: true - caseless@0.12.0: {} + /caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + dev: true - chai@4.5.0: + /chai@4.5.0: + resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} + engines: {node: '>=4'} dependencies: assertion-error: 1.1.0 check-error: 1.0.3 @@ -5992,32 +2815,49 @@ snapshots: loupe: 2.3.7 pathval: 1.1.1 type-detect: 4.1.0 + dev: true - chalk@2.4.2: + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} dependencies: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 - chalk@3.0.0: + /chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 + /chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: true - chardet@0.7.0: {} - - check-error@1.0.3: + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} dependencies: get-func-name: 2.0.2 + dev: true - check-more-types@2.24.0: {} + /check-more-types@2.24.0: + resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} + engines: {node: '>= 0.8.0'} + dev: true - chokidar@3.6.0: + /chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} dependencies: anymatch: 3.1.3 braces: 3.0.3 @@ -6029,77 +2869,137 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - chownr@2.0.0: {} + /chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + dev: true - ci-info@3.9.0: {} + /ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + dev: true - clean-stack@2.2.0: {} + /clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + dev: true - clean-stack@4.2.0: + /clean-stack@4.2.0: + resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} + engines: {node: '>=12'} dependencies: escape-string-regexp: 5.0.0 + dev: true - cli-cursor@3.1.0: + /cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} dependencies: restore-cursor: 3.1.0 + dev: true - cli-table3@0.6.5: + /cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} dependencies: string-width: 4.2.3 optionalDependencies: '@colors/colors': 1.5.0 + dev: true - cli-truncate@2.1.0: + /cli-truncate@2.1.0: + resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} + engines: {node: '>=8'} dependencies: slice-ansi: 3.0.0 string-width: 4.2.3 + dev: true - cli-truncate@3.1.0: + /cli-truncate@3.1.0: + resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: slice-ansi: 5.0.0 string-width: 5.1.2 + dev: true - client-only@0.0.1: {} + /client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + dev: false - cliui@8.0.1: + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - clsx@1.2.1: {} + /clsx@1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + dev: false - color-convert@1.9.3: + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: color-name: 1.1.3 - color-convert@2.0.1: + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - color-name@1.1.3: {} + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: {} + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-support@1.1.3: {} + /color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + dev: true - colorette@2.0.20: {} + /colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + dev: true - combined-stream@1.0.8: + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 + dev: true - commander@4.1.1: {} + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: true - commander@6.2.1: {} + /commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + dev: true - commander@9.5.0: {} + /commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + dev: true - common-tags@1.8.2: {} + /common-tags@1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} + dev: true - concat-map@0.0.1: {} + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concurrently@8.2.2: + /concurrently@8.2.2: + resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} + engines: {node: ^14.13.0 || >=16.0.0} + hasBin: true dependencies: chalk: 4.1.2 date-fns: 2.30.0 @@ -6110,44 +3010,75 @@ snapshots: supports-color: 8.1.1 tree-kill: 1.2.2 yargs: 17.7.2 + dev: true - confbox@0.1.7: {} + /confbox@0.1.7: + resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} + dev: true - consola@3.2.3: {} + /consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} + dev: true - console-control-strings@1.1.0: {} + /console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + dev: true - convert-source-map@2.0.0: {} + /convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - copy-to-clipboard@3.3.3: + /copy-to-clipboard@3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} dependencies: toggle-selection: 1.0.6 + dev: false - core-util-is@1.0.2: {} + /core-util-is@1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + dev: true - cross-spawn@5.1.0: + /cross-spawn@5.1.0: + resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} dependencies: lru-cache: 4.1.5 shebang-command: 1.2.0 which: 1.3.1 + dev: true - cross-spawn@7.0.3: + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - css.escape@1.5.1: {} + /css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + dev: true - cssesc@3.0.0: {} + /cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true - cssstyle@4.0.1: + /cssstyle@4.0.1: + resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} + engines: {node: '>=18'} dependencies: rrweb-cssom: 0.6.0 + dev: true - csstype@3.1.3: {} + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dev: true - cypress@13.13.1: + /cypress@13.13.2: + resolution: {integrity: sha512-PvJQU33933NvS1StfzEb8/mu2kMy4dABwCF+yd5Bi7Qly1HOVf+Bufrygee/tlmty/6j5lX+KIi8j9Q3JUMbhA==} + engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} + hasBin: true + requiresBuild: true dependencies: '@cypress/request': 3.0.1 '@cypress/xvfb': 1.2.4(supports-color@8.1.1) @@ -6191,76 +3122,132 @@ snapshots: tmp: 0.2.3 untildify: 4.0.0 yauzl: 2.10.0 + dev: true - damerau-levenshtein@1.0.8: {} + /damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + dev: false - dashdash@1.14.1: + /dashdash@1.14.1: + resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} + engines: {node: '>=0.10'} dependencies: assert-plus: 1.0.0 + dev: true - data-urls@5.0.0: + /data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} dependencies: whatwg-mimetype: 4.0.0 whatwg-url: 14.0.0 + dev: true - data-view-buffer@1.0.1: + /data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-data-view: 1.0.1 + dev: false - data-view-byte-length@1.0.1: + /data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-data-view: 1.0.1 + dev: false - data-view-byte-offset@1.0.0: + /data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-data-view: 1.0.1 + dev: false - date-fns@2.30.0: + /date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} dependencies: '@babel/runtime': 7.25.0 + dev: true - dayjs@1.11.12: {} + /dayjs@1.11.12: + resolution: {integrity: sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==} + dev: true - debug@3.2.7(supports-color@5.5.0): + /debug@3.2.7(supports-color@5.5.0): + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.1.3 - optionalDependencies: supports-color: 5.5.0 - debug@3.2.7(supports-color@8.1.1): + /debug@3.2.7(supports-color@8.1.1): + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.1.3 - optionalDependencies: supports-color: 8.1.1 + dev: true - debug@4.3.6(supports-color@8.1.1): + /debug@4.3.6(supports-color@8.1.1): + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.1.2 - optionalDependencies: supports-color: 8.1.1 - decamelize-keys@1.1.1: + /decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} dependencies: decamelize: 1.2.0 map-obj: 1.0.1 + dev: true - decamelize@1.2.0: {} + /decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + dev: true - decamelize@5.0.1: {} + /decamelize@5.0.1: + resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} + engines: {node: '>=10'} + dev: true - decimal.js@10.4.3: {} + /decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + dev: true - deep-eql@4.1.4: + /deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} dependencies: type-detect: 4.1.0 + dev: true - deep-equal@2.2.3: + /deep-equal@2.2.3: + resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} + engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.1 call-bind: 1.0.7 @@ -6281,28 +3268,40 @@ snapshots: which-collection: 1.0.2 which-typed-array: 1.1.15 - deep-is@0.1.4: {} + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - define-data-property@1.1.4: + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 gopd: 1.0.1 - define-properties@1.2.1: + /define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 has-property-descriptors: 1.0.2 object-keys: 1.1.1 - defined@1.0.1: {} + /defined@1.0.1: + resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} - del-cli@5.0.0: + /del-cli@5.0.0: + resolution: {integrity: sha512-rENFhUaYcjoMODwFhhlON+ogN7DoG+4+GFN+bsA1XeDt4w2OKQnQadFP1thHSAlK9FAtl88qgP66wOV+eFZZiQ==} + engines: {node: '>=14.16'} + hasBin: true dependencies: del: 7.1.0 meow: 10.1.5 + dev: true - del@7.1.0: + /del@7.1.0: + resolution: {integrity: sha512-v2KyNk7efxhlyHpjEvfyxaAihKKK0nWCuf6ZtqZcFFpQRG0bJ12Qsr0RpvsICMjAAZ8DOVCxrlqpxISlMHC4Kg==} + engines: {node: '>=14.16'} dependencies: globby: 13.2.2 graceful-fs: 4.2.11 @@ -6312,94 +3311,164 @@ snapshots: p-map: 5.5.0 rimraf: 3.0.2 slash: 4.0.0 + dev: true - delayed-stream@1.0.0: {} + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: true - delegates@1.0.0: {} + /delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + dev: true - dequal@2.0.3: {} + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: true - detect-indent@6.1.0: {} + /detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + dev: true - detect-libc@1.0.3: {} + /detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + dev: true - detect-libc@2.0.3: {} + /detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + dev: true - detective@5.2.1: + /detective@5.2.1: + resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} + engines: {node: '>=0.8.0'} + hasBin: true dependencies: acorn-node: 1.8.2 defined: 1.0.1 minimist: 1.2.8 - didyoumean@1.2.2: {} + /didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - diff-sequences@29.6.3: {} + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true - dir-glob@3.0.1: + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} dependencies: path-type: 4.0.0 - dlv@1.1.3: {} + /dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - doctrine@2.1.0: + /doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dependencies: + esutils: 2.0.3 + dev: false + + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} dependencies: esutils: 2.0.3 - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 + /dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dev: true - dom-accessibility-api@0.5.16: {} + /dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dev: true - dom-accessibility-api@0.6.3: {} + /dotenv@16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} + engines: {node: '>=12'} + dev: false - dotenv@16.0.3: {} - - dprint-node@1.0.8: + /dprint-node@1.0.8: + resolution: {integrity: sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==} dependencies: detect-libc: 1.0.3 + dev: true - duplexer@0.1.2: {} + /duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + dev: true - eastasianwidth@0.2.0: {} + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - ecc-jsbn@0.1.2: + /ecc-jsbn@0.1.2: + resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} dependencies: jsbn: 0.1.1 safer-buffer: 2.1.2 + dev: true - electron-to-chromium@1.5.3: {} + /electron-to-chromium@1.5.5: + resolution: {integrity: sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==} - emoji-regex@8.0.0: {} + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: {} + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - end-of-stream@1.4.4: + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 + dev: true - enhanced-resolve@5.17.1: + /enhanced-resolve@5.17.1: + resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} + engines: {node: '>=10.13.0'} dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 + dev: false - enquirer@2.4.1: + /enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} dependencies: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + dev: true - entities@4.5.0: {} + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: true - env-cmd@10.1.0: + /env-cmd@10.1.0: + resolution: {integrity: sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==} + engines: {node: '>=8.0.0'} + hasBin: true dependencies: commander: 4.1.1 cross-spawn: 7.0.3 + dev: true - error-ex@1.3.2: + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: is-arrayish: 0.2.1 + dev: true - es-abstract@1.23.3: + /es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} + engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.1 arraybuffer.prototype.slice: 1.0.3 @@ -6447,14 +3516,20 @@ snapshots: typed-array-length: 1.0.6 unbox-primitive: 1.0.2 which-typed-array: 1.1.15 + dev: false - es-define-property@1.0.0: + /es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} dependencies: get-intrinsic: 1.2.4 - es-errors@1.3.0: {} + /es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} - es-get-iterator@1.1.3: + /es-get-iterator@1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} dependencies: call-bind: 1.0.7 get-intrinsic: 1.2.4 @@ -6466,7 +3541,9 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.0.0 - es-iterator-helpers@1.0.19: + /es-iterator-helpers@1.0.19: + resolution: {integrity: sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 @@ -6482,28 +3559,44 @@ snapshots: internal-slot: 1.0.7 iterator.prototype: 1.1.2 safe-array-concat: 1.1.2 + dev: false - es-object-atoms@1.0.0: + /es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 + dev: false - es-set-tostringtag@2.0.3: + /es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} dependencies: get-intrinsic: 1.2.4 has-tostringtag: 1.0.2 hasown: 2.0.2 + dev: false - es-shim-unscopables@1.0.2: + /es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} dependencies: hasown: 2.0.2 + dev: false - es-to-primitive@1.2.1: + /es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} dependencies: is-callable: 1.2.7 is-date-object: 1.0.5 is-symbol: 1.0.4 + dev: false - esbuild@0.21.5: + /esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 '@esbuild/android-arm': 0.21.5 @@ -6528,8 +3621,13 @@ snapshots: '@esbuild/win32-arm64': 0.21.5 '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + dev: true - esbuild@0.23.0: + /esbuild@0.23.0: + resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} + engines: {node: '>=18'} + hasBin: true + requiresBuild: true optionalDependencies: '@esbuild/aix-ppc64': 0.23.0 '@esbuild/android-arm': 0.23.0 @@ -6555,57 +3653,90 @@ snapshots: '@esbuild/win32-arm64': 0.23.0 '@esbuild/win32-ia32': 0.23.0 '@esbuild/win32-x64': 0.23.0 + dev: true - escalade@3.1.2: {} + /escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} - escape-string-regexp@1.0.5: {} + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} - escape-string-regexp@4.0.0: {} + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} - escape-string-regexp@5.0.0: {} + /escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: true - eslint-config-next@14.2.5(eslint@8.57.0)(typescript@5.5.4): + /eslint-config-next@14.2.5(eslint@8.57.0)(typescript@5.5.4): + resolution: {integrity: sha512-zogs9zlOiZ7ka+wgUnmcM0KBEDjo4Jis7kxN1jvC0N4wynQ2MIx/KBkg4mVF63J5EK4W0QMCn7xO3vNisjaAoA==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true dependencies: '@next/eslint-plugin-next': 14.2.5 '@rushstack/eslint-patch': 1.10.4 '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) - optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - eslint-import-resolver-webpack - supports-color + dev: false - eslint-config-prettier@9.1.0(eslint@8.57.0): + /eslint-config-prettier@9.1.0(eslint@8.57.0): + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' dependencies: eslint: 8.57.0 + dev: false - eslint-config-turbo@2.0.9(eslint@8.57.0): + /eslint-config-turbo@2.0.12(eslint@8.57.0): + resolution: {integrity: sha512-3PUzoyeJi2SjsTSjfWgTUIHK7kOqsapDEaOT7sCjFnZXvuhYLKxW37lysjq7+55abGGm0yQTXxNFLjrQKUORag==} + peerDependencies: + eslint: '>6.6.0' dependencies: eslint: 8.57.0 - eslint-plugin-turbo: 2.0.9(eslint@8.57.0) + eslint-plugin-turbo: 2.0.12(eslint@8.57.0) + dev: false - eslint-import-resolver-node@0.3.9: + /eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: debug: 3.2.7(supports-color@5.5.0) is-core-module: 2.15.0 resolve: 1.22.8 transitivePeerDependencies: - supports-color + dev: false - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' dependencies: debug: 4.3.6(supports-color@8.1.1) enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.6 is-core-module: 2.15.0 @@ -6615,20 +3746,49 @@ snapshots: - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color + dev: false - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + /eslint-module-utils@2.8.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true dependencies: - debug: 3.2.7(supports-color@5.5.0) - optionalDependencies: '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) + debug: 3.2.7(supports-color@5.5.0) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color + dev: false - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true dependencies: + '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -6637,7 +3797,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -6647,14 +3807,17 @@ snapshots: object.values: 1.2.0 semver: 6.3.1 tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color + dev: false - eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.0): + /eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.0): + resolution: {integrity: sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 dependencies: aria-query: 5.1.3 array-includes: 3.1.8 @@ -6673,12 +3836,22 @@ snapshots: object.fromentries: 2.0.8 safe-regex-test: 1.0.3 string.prototype.includes: 2.0.0 + dev: false - eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): + /eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dependencies: eslint: 8.57.0 + dev: false - eslint-plugin-react@7.35.0(eslint@8.57.0): + /eslint-plugin-react@7.35.0(eslint@8.57.0): + resolution: {integrity: sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 dependencies: array-includes: 3.1.8 array.prototype.findlast: 1.2.5 @@ -6699,20 +3872,32 @@ snapshots: semver: 6.3.1 string.prototype.matchall: 4.0.11 string.prototype.repeat: 1.0.0 + dev: false - eslint-plugin-turbo@2.0.9(eslint@8.57.0): + /eslint-plugin-turbo@2.0.12(eslint@8.57.0): + resolution: {integrity: sha512-vXWKer7F0RPTcVy1B+hFTEK4mlEOpouB8MCAFD3WW4C6t98wvuDCsIPjxIldpxg7CnwmRxALpNWgNVkU2LVVEQ==} + peerDependencies: + eslint: '>6.6.0' dependencies: dotenv: 16.0.3 eslint: 8.57.0 + dev: false - eslint-scope@7.2.2: + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-visitor-keys@3.4.3: {} + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint@8.57.0: + /eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@eslint-community/regexpp': 4.11.0 @@ -6755,31 +3940,48 @@ snapshots: transitivePeerDependencies: - supports-color - espree@9.6.1: + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.12.1 acorn-jsx: 5.3.2(acorn@8.12.1) eslint-visitor-keys: 3.4.3 - esprima@4.0.1: {} + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true - esquery@1.6.0: + /esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 - esrecurse@4.3.0: + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} dependencies: estraverse: 5.3.0 - estraverse@5.3.0: {} + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} - estree-walker@3.0.3: + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: '@types/estree': 1.0.5 + dev: true - esutils@2.0.3: {} + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} - event-stream@3.3.4: + /event-stream@3.3.4: + resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} dependencies: duplexer: 0.1.2 from: 0.1.7 @@ -6788,10 +3990,15 @@ snapshots: split: 0.3.3 stream-combiner: 0.0.4 through: 2.3.8 + dev: true - eventemitter2@6.4.7: {} + /eventemitter2@6.4.7: + resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==} + dev: true - execa@4.1.0: + /execa@4.1.0: + resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} + engines: {node: '>=10'} dependencies: cross-spawn: 7.0.3 get-stream: 5.2.0 @@ -6802,8 +4009,11 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 + dev: true - execa@5.1.1: + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} dependencies: cross-spawn: 7.0.3 get-stream: 6.0.1 @@ -6814,8 +4024,11 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 + dev: true - execa@6.1.0: + /execa@6.1.0: + resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: cross-spawn: 7.0.3 get-stream: 6.0.1 @@ -6826,8 +4039,11 @@ snapshots: onetime: 6.0.0 signal-exit: 3.0.7 strip-final-newline: 3.0.0 + dev: true - execa@8.0.1: + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} dependencies: cross-spawn: 7.0.3 get-stream: 8.0.1 @@ -6838,22 +4054,36 @@ snapshots: onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 + dev: true - executable@4.1.1: + /executable@4.1.1: + resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} + engines: {node: '>=4'} dependencies: pify: 2.3.0 + dev: true - extend@3.0.2: {} + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: true - extendable-error@0.1.7: {} + /extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + dev: true - external-editor@3.1.0: + /external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} dependencies: chardet: 0.7.0 iconv-lite: 0.4.24 tmp: 0.0.33 + dev: true - extract-zip@2.0.1(supports-color@8.1.1): + /extract-zip@2.0.1(supports-color@8.1.1): + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true dependencies: debug: 4.3.6(supports-color@8.1.1) get-stream: 5.2.0 @@ -6862,12 +4092,19 @@ snapshots: '@types/yauzl': 2.10.3 transitivePeerDependencies: - supports-color + dev: true - extsprintf@1.3.0: {} + /extsprintf@1.3.0: + resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} + engines: {'0': node >=0.6.0} + dev: true - fast-deep-equal@3.1.3: {} + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-glob@3.3.2: + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -6875,124 +4112,194 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.7 - fast-json-stable-stringify@2.1.0: {} + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fast-levenshtein@2.0.6: {} + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.17.1: + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} dependencies: reusify: 1.0.4 - fd-slicer@1.1.0: + /fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} dependencies: pend: 1.2.0 + dev: true - figures@3.2.0: + /figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} dependencies: escape-string-regexp: 1.0.5 + dev: true - file-entry-cache@6.0.1: + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} dependencies: flat-cache: 3.2.0 - fill-range@7.1.1: + /fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 - find-up@4.1.0: + /find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} dependencies: locate-path: 5.0.0 path-exists: 4.0.0 + dev: true - find-up@5.0.0: + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} dependencies: locate-path: 6.0.0 path-exists: 4.0.0 - find-yarn-workspace-root2@1.2.16: + /find-yarn-workspace-root2@1.2.16: + resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} dependencies: micromatch: 4.0.7 pkg-dir: 4.2.0 + dev: true - flat-cache@3.2.0: + /flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} dependencies: flatted: 3.3.1 keyv: 4.5.4 rimraf: 3.0.2 - flatted@3.3.1: {} + /flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - follow-redirects@1.15.6(debug@4.3.6): - optionalDependencies: + /follow-redirects@1.15.6(debug@4.3.6): + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dependencies: debug: 4.3.6(supports-color@8.1.1) + dev: true - for-each@0.3.3: + /for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 - foreground-child@3.2.1: + /foreground-child@3.2.1: + resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} + engines: {node: '>=14'} dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - forever-agent@0.6.1: {} + /forever-agent@0.6.1: + resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} + dev: true - form-data@2.3.3: + /form-data@2.3.3: + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 + dev: true - form-data@4.0.0: + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 + dev: true - fraction.js@4.3.7: {} + /fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + dev: true - from@0.1.7: {} + /from@0.1.7: + resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + dev: true - fs-extra@7.0.1: + /fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} dependencies: graceful-fs: 4.2.11 jsonfile: 4.0.0 universalify: 0.1.2 + dev: true - fs-extra@8.1.0: + /fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} dependencies: graceful-fs: 4.2.11 jsonfile: 4.0.0 universalify: 0.1.2 + dev: true - fs-extra@9.1.0: + /fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} dependencies: at-least-node: 1.0.0 graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.1 + dev: true - fs-minipass@2.1.0: + /fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} dependencies: minipass: 3.3.6 + dev: true - fs.realpath@1.0.0: {} + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.3: + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true optional: true - function-bind@1.1.2: {} + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.6: + /function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 functions-have-names: 1.2.3 + dev: false - functions-have-names@1.2.3: {} + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - gauge@3.0.2: + /gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. dependencies: aproba: 2.0.0 color-support: 1.1.3 @@ -7003,14 +4310,23 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 wide-align: 1.1.5 + dev: true - gensync@1.0.0-beta.2: {} + /gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} - get-caller-file@2.0.5: {} + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} - get-func-name@2.0.2: {} + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true - get-intrinsic@1.2.4: + /get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 function-bind: 1.1.2 @@ -7018,49 +4334,77 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.2 - get-stream@5.2.0: + /get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} dependencies: pump: 3.0.0 + dev: true - get-stream@6.0.1: {} + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + dev: true - get-stream@8.0.1: {} + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true - get-symbol-description@1.0.2: + /get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 es-errors: 1.3.0 get-intrinsic: 1.2.4 + dev: false - get-tsconfig@4.7.6: + /get-tsconfig@4.7.6: + resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==} dependencies: resolve-pkg-maps: 1.0.0 + dev: false - getos@3.2.1: + /getos@3.2.1: + resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==} dependencies: async: 3.2.5 + dev: true - getpass@0.1.7: + /getpass@0.1.7: + resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} dependencies: assert-plus: 1.0.0 + dev: true - glob-parent@5.1.2: + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 - glob-parent@6.0.2: + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 - glob@10.3.10: + /glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true dependencies: foreground-child: 3.2.1 jackspeak: 2.3.6 minimatch: 9.0.5 minipass: 7.1.2 path-scurry: 1.11.1 + dev: false - glob@10.4.5: + /glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true dependencies: foreground-child: 3.2.1 jackspeak: 3.4.3 @@ -7068,8 +4412,11 @@ snapshots: minipass: 7.1.2 package-json-from-dist: 1.0.0 path-scurry: 1.11.1 + dev: true - glob@7.2.3: + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -7078,22 +4425,34 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - global-dirs@3.0.1: + /global-dirs@3.0.1: + resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} + engines: {node: '>=10'} dependencies: ini: 2.0.0 + dev: true - globals@11.12.0: {} + /globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} - globals@13.24.0: + /globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} dependencies: type-fest: 0.20.2 - globalthis@1.0.4: + /globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} dependencies: define-properties: 1.2.1 gopd: 1.0.1 + dev: false - globby@11.1.0: + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} dependencies: array-union: 2.1.0 dir-glob: 3.0.1 @@ -7102,336 +4461,566 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - globby@13.2.2: + /globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: dir-glob: 3.0.1 fast-glob: 3.3.2 ignore: 5.3.1 merge2: 1.4.1 slash: 4.0.0 + dev: true - globrex@0.1.2: {} + /globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + dev: true - gopd@1.0.1: + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: get-intrinsic: 1.2.4 - graceful-fs@4.2.11: {} + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphemer@1.4.0: {} + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - grpc-tools@1.11.3: + /grpc-tools@1.11.3: + resolution: {integrity: sha512-cRSK2uhDKHtZ9hLRM35HxaMAMxyh/L7C96Ojt58DhQBdwTOQlV1VIJHSK6X/pDeSQKhaQqWMFfebt8tIcvRfjQ==} + hasBin: true + requiresBuild: true dependencies: '@mapbox/node-pre-gyp': 1.0.11 transitivePeerDependencies: - encoding - supports-color + dev: true - hard-rejection@2.1.0: {} + /hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + dev: true - has-bigints@1.0.2: {} + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - has-flag@3.0.0: {} + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} - has-flag@4.0.0: {} + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} - has-property-descriptors@1.0.2: + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} dependencies: es-define-property: 1.0.0 - has-proto@1.0.3: {} + /has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} - has-symbols@1.0.3: {} + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} - has-tostringtag@1.0.2: + /has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 - has-unicode@2.0.1: {} + /has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + dev: true - hasown@2.0.2: + /hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 - hosted-git-info@4.1.0: + /hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} dependencies: lru-cache: 6.0.0 + dev: true - html-encoding-sniffer@4.0.0: + /html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} dependencies: whatwg-encoding: 3.1.1 + dev: true - http-proxy-agent@7.0.2: + /http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} dependencies: agent-base: 7.1.1 debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color + dev: true - http-signature@1.3.6: + /http-signature@1.3.6: + resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==} + engines: {node: '>=0.10'} dependencies: assert-plus: 1.0.0 jsprim: 2.0.2 sshpk: 1.18.0 + dev: true - https-proxy-agent@5.0.1: + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color + dev: true - https-proxy-agent@7.0.5: + /https-proxy-agent@7.0.5: + resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} + engines: {node: '>= 14'} dependencies: agent-base: 7.1.1 debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color + dev: true - human-id@1.0.2: {} + /human-id@1.0.2: + resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} + dev: true - human-signals@1.1.1: {} + /human-signals@1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + dev: true - human-signals@2.1.0: {} + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + dev: true - human-signals@3.0.1: {} + /human-signals@3.0.1: + resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} + engines: {node: '>=12.20.0'} + dev: true - human-signals@5.0.0: {} + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: true - iconv-lite@0.4.24: + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 + dev: true - iconv-lite@0.6.3: + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 + dev: true - ieee754@1.2.1: {} + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: true - ignore-by-default@1.0.1: {} + /ignore-by-default@1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + dev: true - ignore@5.3.1: {} + /ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} - immutable@4.3.7: {} + /immutable@4.3.7: + resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} - import-fresh@3.3.0: + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - imurmurhash@0.1.4: {} + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} - indent-string@4.0.0: {} + /indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + dev: true - indent-string@5.0.0: {} + /indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + dev: true - inflight@1.0.6: + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. dependencies: once: 1.4.0 wrappy: 1.0.2 - inherits@2.0.4: {} + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@2.0.0: {} + /ini@2.0.0: + resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} + engines: {node: '>=10'} + dev: true - internal-slot@1.0.7: + /internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 hasown: 2.0.2 side-channel: 1.0.6 - is-arguments@1.1.1: + /is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 has-tostringtag: 1.0.2 - is-array-buffer@3.0.4: + /is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 get-intrinsic: 1.2.4 - is-arrayish@0.2.1: {} + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true - is-async-function@2.0.0: + /is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.2 + dev: false - is-bigint@1.0.4: + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: has-bigints: 1.0.2 - is-binary-path@2.1.0: + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} dependencies: binary-extensions: 2.3.0 - is-boolean-object@1.1.2: + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 has-tostringtag: 1.0.2 - is-callable@1.2.7: {} + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} - is-ci@3.0.1: + /is-ci@3.0.1: + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + hasBin: true dependencies: ci-info: 3.9.0 + dev: true - is-core-module@2.15.0: + /is-core-module@2.15.0: + resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==} + engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 - is-data-view@1.0.1: + /is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} dependencies: is-typed-array: 1.1.13 + dev: false - is-date-object@1.0.5: + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.2 - is-extglob@2.1.1: {} + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} - is-finalizationregistry@1.0.2: + /is-finalizationregistry@1.0.2: + resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} dependencies: call-bind: 1.0.7 + dev: false - is-fullwidth-code-point@3.0.0: {} + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} - is-fullwidth-code-point@4.0.0: {} + /is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + dev: true - is-generator-function@1.0.10: + /is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.2 + dev: false - is-glob@4.0.3: + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 - is-installed-globally@0.4.0: + /is-installed-globally@0.4.0: + resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} + engines: {node: '>=10'} dependencies: global-dirs: 3.0.1 is-path-inside: 3.0.3 + dev: true - is-map@2.0.3: {} + /is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} - is-negative-zero@2.0.3: {} + /is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + dev: false - is-number-object@1.0.7: + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.2 - is-number@7.0.0: {} + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} - is-path-cwd@3.0.0: {} + /is-path-cwd@3.0.0: + resolution: {integrity: sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true - is-path-inside@3.0.3: {} + /is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} - is-path-inside@4.0.0: {} + /is-path-inside@4.0.0: + resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} + engines: {node: '>=12'} + dev: true - is-plain-obj@1.1.0: {} + /is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + dev: true - is-potential-custom-element-name@1.0.1: {} + /is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + dev: true - is-regex@1.1.4: + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 has-tostringtag: 1.0.2 - is-set@2.0.3: {} + /is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} - is-shared-array-buffer@1.0.3: + /is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 - is-stream@2.0.1: {} + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: true - is-stream@3.0.0: {} + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true - is-string@1.0.7: + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.2 - is-subdir@1.2.0: + /is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} dependencies: better-path-resolve: 1.0.0 + dev: true - is-symbol@1.0.4: + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 - is-typed-array@1.1.13: + /is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} dependencies: which-typed-array: 1.1.15 + dev: false - is-typedarray@1.0.0: {} + /is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + dev: true - is-unicode-supported@0.1.0: {} + /is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true - is-weakmap@2.0.2: {} + /is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} - is-weakref@1.0.2: + /is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.7 + dev: false - is-weakset@2.0.3: + /is-weakset@2.0.3: + resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 get-intrinsic: 1.2.4 - is-windows@1.0.2: {} + /is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + dev: true - isarray@2.0.5: {} + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - isexe@2.0.0: {} + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isstream@0.1.2: {} + /isstream@0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + dev: true - iterator.prototype@1.1.2: + /iterator.prototype@1.1.2: + resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} dependencies: define-properties: 1.2.1 get-intrinsic: 1.2.4 has-symbols: 1.0.3 reflect.getprototypeof: 1.0.6 set-function-name: 2.0.2 + dev: false - jackspeak@2.3.6: + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 + dev: false - jackspeak@3.4.3: + /jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 + dev: true - joi@17.13.3: + /joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} dependencies: '@hapi/hoek': 9.3.0 '@hapi/topo': 5.1.0 '@sideway/address': 4.1.5 '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 + dev: true - jose@5.6.3: {} + /jose@5.6.3: + resolution: {integrity: sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==} + dev: false - joycon@3.1.1: {} + /joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + dev: true - js-tokens@4.0.0: {} + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-tokens@9.0.0: {} + /js-tokens@9.0.0: + resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} + dev: true - js-yaml@3.14.1: + /js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true dependencies: argparse: 1.0.10 esprima: 4.0.1 + dev: true - js-yaml@4.1.0: + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true dependencies: argparse: 2.0.1 - jsbn@0.1.1: {} + /jsbn@0.1.1: + resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} + dev: true - jsdom@24.1.1: + /jsdom@24.1.1: + resolution: {integrity: sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true dependencies: cssstyle: 4.0.1 data-urls: 5.0.0 @@ -7458,79 +5047,135 @@ snapshots: - bufferutil - supports-color - utf-8-validate + dev: true - jsesc@2.5.2: {} + /jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true - json-buffer@3.0.1: {} + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-parse-even-better-errors@2.3.1: {} + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: true - json-schema-traverse@0.4.1: {} + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - json-schema@0.4.0: {} + /json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + dev: true - json-stable-stringify-without-jsonify@1.0.1: {} + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json-stringify-safe@5.0.1: {} + /json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: true - json5@1.0.2: + /json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true dependencies: minimist: 1.2.8 + dev: false - json5@2.2.3: {} + /json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true - jsonfile@4.0.0: + /jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} optionalDependencies: graceful-fs: 4.2.11 + dev: true - jsonfile@6.1.0: + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} dependencies: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 + dev: true - jsprim@2.0.2: + /jsprim@2.0.2: + resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==} + engines: {'0': node >=0.6.0} dependencies: assert-plus: 1.0.0 extsprintf: 1.3.0 json-schema: 0.4.0 verror: 1.10.0 + dev: true - jsx-ast-utils@3.3.5: + /jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} dependencies: array-includes: 3.1.8 array.prototype.flat: 1.3.2 object.assign: 4.1.5 object.values: 1.2.0 + dev: false - keyv@4.5.4: + /keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: json-buffer: 3.0.1 - kind-of@6.0.3: {} + /kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + dev: true - language-subtag-registry@0.3.23: {} + /language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + dev: false - language-tags@1.0.9: + /language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} dependencies: language-subtag-registry: 0.3.23 + dev: false - lazy-ass@1.6.0: {} + /lazy-ass@1.6.0: + resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==} + engines: {node: '> 0.8'} + dev: true - levn@0.4.1: + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - lilconfig@2.0.5: {} + /lilconfig@2.0.5: + resolution: {integrity: sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==} + engines: {node: '>=10'} + dev: true - lilconfig@2.1.0: {} + /lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} - lilconfig@3.1.2: {} + /lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} + dev: true - lines-and-columns@1.2.4: {} + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true - lint-staged@13.0.3(enquirer@2.4.1): + /lint-staged@13.0.3: + resolution: {integrity: sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug==} + engines: {node: ^14.13.1 || >=16.0.0} + hasBin: true dependencies: cli-truncate: 3.1.0 colorette: 2.0.20 @@ -7538,7 +5183,7 @@ snapshots: debug: 4.3.6(supports-color@8.1.1) execa: 6.1.0 lilconfig: 2.0.5 - listr2: 4.0.5(enquirer@2.4.1) + listr2: 4.0.5 micromatch: 4.0.7 normalize-path: 3.0.0 object-inspect: 1.13.2 @@ -7548,8 +5193,36 @@ snapshots: transitivePeerDependencies: - enquirer - supports-color + dev: true - listr2@3.14.0(enquirer@2.4.1): + /listr2@3.14.0(enquirer@2.4.1): + resolution: {integrity: sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==} + engines: {node: '>=10.0.0'} + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true + dependencies: + cli-truncate: 2.1.0 + colorette: 2.0.20 + enquirer: 2.4.1 + log-update: 4.0.0 + p-map: 4.0.0 + rfdc: 1.4.1 + rxjs: 7.8.1 + through: 2.3.8 + wrap-ansi: 7.0.0 + dev: true + + /listr2@4.0.5: + resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==} + engines: {node: '>=12'} + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true dependencies: cli-truncate: 2.1.0 colorette: 2.0.20 @@ -7559,115 +5232,166 @@ snapshots: rxjs: 7.8.1 through: 2.3.8 wrap-ansi: 7.0.0 - optionalDependencies: - enquirer: 2.4.1 + dev: true - listr2@4.0.5(enquirer@2.4.1): - dependencies: - cli-truncate: 2.1.0 - colorette: 2.0.20 - log-update: 4.0.0 - p-map: 4.0.0 - rfdc: 1.4.1 - rxjs: 7.8.1 - through: 2.3.8 - wrap-ansi: 7.0.0 - optionalDependencies: - enquirer: 2.4.1 + /load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true - load-tsconfig@0.2.5: {} - - load-yaml-file@0.2.0: + /load-yaml-file@0.2.0: + resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} + engines: {node: '>=6'} dependencies: graceful-fs: 4.2.11 js-yaml: 3.14.1 pify: 4.0.1 strip-bom: 3.0.0 + dev: true - local-pkg@0.5.0: + /local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} dependencies: mlly: 1.7.1 pkg-types: 1.1.3 + dev: true - locate-path@5.0.0: + /locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} dependencies: p-locate: 4.1.0 + dev: true - locate-path@6.0.0: + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} dependencies: p-locate: 5.0.0 - lodash.camelcase@4.3.0: {} + /lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + dev: false - lodash.merge@4.6.2: {} + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash.once@4.1.1: {} + /lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: true - lodash.sortby@4.7.0: {} + /lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + dev: true - lodash.startcase@4.4.0: {} + /lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + dev: true - lodash@4.17.21: {} + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: true - log-symbols@4.1.0: + /log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} dependencies: chalk: 4.1.2 is-unicode-supported: 0.1.0 + dev: true - log-update@4.0.0: + /log-update@4.0.0: + resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} + engines: {node: '>=10'} dependencies: ansi-escapes: 4.3.2 cli-cursor: 3.1.0 slice-ansi: 4.0.0 wrap-ansi: 6.2.0 + dev: true - long@5.2.3: {} + /long@5.2.3: + resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} - loose-envify@1.4.0: + /loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true dependencies: js-tokens: 4.0.0 - loupe@2.3.7: + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} dependencies: get-func-name: 2.0.2 + dev: true - lru-cache@10.4.3: {} + /lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@4.1.5: + /lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} dependencies: pseudomap: 1.0.2 yallist: 2.1.2 + dev: true - lru-cache@5.1.1: + /lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: yallist: 3.1.1 - lru-cache@6.0.0: + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} dependencies: yallist: 4.0.0 + dev: true - lz-string@1.5.0: {} + /lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + dev: true - magic-string@0.30.11: + /magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + dev: true - make-dir-cli@3.0.0: + /make-dir-cli@3.0.0: + resolution: {integrity: sha512-8yCjIAOQ8tezWRJWUG3tbvN2I19hiVr8K5DPDVl8fECS3qz0ZbeL194ZGRdf8K3LgvbjDCTadge6NrN/I4XrNw==} + engines: {node: '>=12.17'} + hasBin: true dependencies: make-dir: 3.1.0 meow: 10.1.5 + dev: true - make-dir@3.1.0: + /make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} dependencies: semver: 6.3.1 + dev: true - map-obj@1.0.1: {} + /map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + dev: true - map-obj@4.3.0: {} + /map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + dev: true - map-stream@0.1.0: {} + /map-stream@0.1.0: + resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} + dev: true - meow@10.1.5: + /meow@10.1.5: + resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: '@types/minimist': 1.2.5 camelcase-keys: 7.0.2 @@ -7681,102 +5405,186 @@ snapshots: trim-newlines: 4.1.1 type-fest: 1.4.0 yargs-parser: 20.2.9 + dev: true - merge-stream@2.0.0: {} + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true - merge2@1.4.1: {} + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} - micromatch@4.0.7: + /micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} dependencies: braces: 3.0.3 picomatch: 2.3.1 - mime-db@1.52.0: {} + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: true - mime-types@2.1.35: + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 + dev: true - mimic-fn@2.1.0: {} + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true - mimic-fn@4.0.0: {} + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true - min-indent@1.0.1: {} + /min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true - mini-svg-data-uri@1.4.4: {} + /mini-svg-data-uri@1.4.4: + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} + hasBin: true - minimatch@3.1.2: + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 - minimatch@9.0.5: + /minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - minimist-options@4.1.0: + /minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} dependencies: arrify: 1.0.1 is-plain-obj: 1.1.0 kind-of: 6.0.3 + dev: true - minimist@1.2.8: {} + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@3.3.6: + /minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} dependencies: yallist: 4.0.0 + dev: true - minipass@5.0.0: {} + /minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + dev: true - minipass@7.1.2: {} + /minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} - minizlib@2.1.2: + /minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} dependencies: minipass: 3.3.6 yallist: 4.0.0 + dev: true - mkdirp@1.0.4: {} + /mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + dev: true - mlly@1.7.1: + /mlly@1.7.1: + resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} dependencies: acorn: 8.12.1 pathe: 1.1.2 pkg-types: 1.1.3 ufo: 1.5.4 + dev: true - moment@2.30.1: {} + /moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + dev: false - mri@1.2.0: {} + /mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + dev: true - ms@2.1.2: {} + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - ms@2.1.3: {} + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - mz@2.7.0: + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} dependencies: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 + dev: true - nanoid@3.3.7: {} + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true - natural-compare@1.4.0: {} + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next-themes@0.2.1(next@14.2.3(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + /next-themes@0.2.1(next@14.2.3)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} + peerDependencies: + next: '*' + react: '*' + react-dom: '*' dependencies: - next: 14.2.3(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) + next: 14.2.3(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + dev: false - next@14.2.3(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8): + /next@14.2.3(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8): + resolution: {integrity: sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==} + engines: {node: '>=18.17.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + sass: + optional: true dependencies: '@next/env': 14.2.3 '@swc/helpers': 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001644 + caniuse-lite: 1.0.30001650 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + sass: 1.77.8 styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 14.2.3 @@ -7788,17 +5596,33 @@ snapshots: '@next/swc-win32-arm64-msvc': 14.2.3 '@next/swc-win32-ia32-msvc': 14.2.3 '@next/swc-win32-x64-msvc': 14.2.3 - sass: 1.77.8 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros + dev: false - next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.2.0))(react@18.2.0)(sass@1.77.8): + /next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.2.0): + resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} + engines: {node: '>=18.17.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + sass: + optional: true dependencies: '@next/env': 14.2.5 '@swc/helpers': 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001644 + caniuse-lite: 1.0.30001650 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.2.0 @@ -7814,28 +5638,44 @@ snapshots: '@next/swc-win32-arm64-msvc': 14.2.5 '@next/swc-win32-ia32-msvc': 14.2.5 '@next/swc-win32-x64-msvc': 14.2.5 - sass: 1.77.8 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros + dev: false - nice-grpc-common@2.0.2: + /nice-grpc-common@2.0.2: + resolution: {integrity: sha512-7RNWbls5kAL1QVUOXvBsv1uO0wPQK3lHv+cY1gwkTzirnG1Nop4cBJZubpgziNbaVc/bl9QJcyvsf/NQxa3rjQ==} dependencies: ts-error: 1.0.6 + dev: false - nice-grpc@2.0.1: + /nice-grpc@2.0.1: + resolution: {integrity: sha512-Q5CGXO08STsv+HAkXeFgRayANT62X1LnIDhNXdCf+LP0XaP7EiHM0Cr3QefnoFjDZAx/Kxq+qiQfY66BrtKcNQ==} dependencies: '@grpc/grpc-js': 1.11.1 abort-controller-x: 0.4.3 nice-grpc-common: 2.0.2 + dev: false - node-fetch@2.7.0: + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true dependencies: whatwg-url: 5.0.0 + dev: true - node-releases@2.0.18: {} + /node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - nodemon@2.0.22: + /nodemon@2.0.22: + resolution: {integrity: sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==} + engines: {node: '>=8.10.0'} + hasBin: true dependencies: chokidar: 3.6.0 debug: 3.2.7(supports-color@5.5.0) @@ -7847,97 +5687,154 @@ snapshots: supports-color: 5.5.0 touch: 3.1.1 undefsafe: 2.0.5 + dev: true - nopt@5.0.0: + /nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true dependencies: abbrev: 1.1.1 + dev: true - normalize-package-data@3.0.3: + /normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} dependencies: hosted-git-info: 4.1.0 is-core-module: 2.15.0 semver: 7.6.3 validate-npm-package-license: 3.0.4 + dev: true - normalize-path@3.0.0: {} + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} - normalize-range@0.1.2: {} + /normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + dev: true - npm-run-path@4.0.1: + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} dependencies: path-key: 3.1.1 + dev: true - npm-run-path@5.3.0: + /npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: path-key: 4.0.0 + dev: true - npmlog@5.0.1: + /npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. dependencies: are-we-there-yet: 2.0.0 console-control-strings: 1.1.0 gauge: 3.0.2 set-blocking: 2.0.0 + dev: true - nwsapi@2.2.12: {} + /nwsapi@2.2.12: + resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==} + dev: true - object-assign@4.1.1: {} + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} - object-hash@3.0.0: {} + /object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} - object-inspect@1.13.2: {} + /object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} - object-is@1.1.6: + /object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - object-keys@1.1.1: {} + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} - object.assign@4.1.5: + /object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 has-symbols: 1.0.3 object-keys: 1.1.1 - object.entries@1.1.8: + /object.entries@1.1.8: + resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-object-atoms: 1.0.0 + dev: false - object.fromentries@2.0.8: + /object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 es-object-atoms: 1.0.0 + dev: false - object.groupby@1.0.3: + /object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 + dev: false - object.values@1.2.0: + /object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-object-atoms: 1.0.0 + dev: false - once@1.4.0: + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 - onetime@5.1.2: + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} dependencies: mimic-fn: 2.1.0 + dev: true - onetime@6.0.0: + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} dependencies: mimic-fn: 4.0.0 + dev: true - optionator@0.9.4: + /optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} dependencies: deep-is: 0.1.4 fast-levenshtein: 2.0.6 @@ -7946,243 +5843,402 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - os-tmpdir@1.0.2: {} + /os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + dev: true - ospath@1.2.2: {} + /ospath@1.2.2: + resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==} + dev: true - outdent@0.5.0: {} + /outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + dev: true - p-filter@2.1.0: + /p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} dependencies: p-map: 2.1.0 + dev: true - p-limit@2.3.0: + /p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} dependencies: p-try: 2.2.0 + dev: true - p-limit@3.1.0: + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} dependencies: yocto-queue: 0.1.0 - p-limit@5.0.0: + /p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} dependencies: yocto-queue: 1.1.1 + dev: true - p-locate@4.1.0: + /p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} dependencies: p-limit: 2.3.0 + dev: true - p-locate@5.0.0: + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} dependencies: p-limit: 3.1.0 - p-map@2.1.0: {} + /p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + dev: true - p-map@4.0.0: + /p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} dependencies: aggregate-error: 3.1.0 + dev: true - p-map@5.5.0: + /p-map@5.5.0: + resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} + engines: {node: '>=12'} dependencies: aggregate-error: 4.0.1 + dev: true - p-try@2.2.0: {} + /p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + dev: true - package-json-from-dist@1.0.0: {} + /package-json-from-dist@1.0.0: + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + dev: true - parent-module@1.0.1: + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} dependencies: callsites: 3.1.0 - parse-json@5.2.0: + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} dependencies: '@babel/code-frame': 7.24.7 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + dev: true - parse5@7.1.2: + /parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} dependencies: entities: 4.5.0 + dev: true - path-exists@4.0.0: {} + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} - path-is-absolute@1.0.1: {} + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} - path-key@3.1.1: {} + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} - path-key@4.0.0: {} + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true - path-parse@1.0.7: {} + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.11.1: + /path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} dependencies: lru-cache: 10.4.3 minipass: 7.1.2 - path-type@4.0.0: {} + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} - pathe@1.1.2: {} + /pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + dev: true - pathval@1.1.1: {} + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true - pause-stream@0.0.11: + /pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} dependencies: through: 2.3.8 + dev: true - pend@1.2.0: {} + /pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + dev: true - performance-now@2.1.0: {} + /performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + dev: true - picocolors@1.0.1: {} + /picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} - picomatch@2.3.1: {} + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} - pidtree@0.6.0: {} + /pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + dev: true - pify@2.3.0: {} + /pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} - pify@4.0.1: {} + /pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + dev: true - pirates@4.0.6: {} + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + dev: true - pkg-dir@4.2.0: + /pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} dependencies: find-up: 4.1.0 + dev: true - pkg-types@1.1.3: + /pkg-types@1.1.3: + resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} dependencies: confbox: 0.1.7 mlly: 1.7.1 pathe: 1.1.2 + dev: true - possible-typed-array-names@1.0.0: {} + /possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} - postcss-import@14.1.0(postcss@8.4.21): + /postcss-import@14.1.0(postcss@8.4.21): + resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} + engines: {node: '>=10.0.0'} + peerDependencies: + postcss: ^8.0.0 dependencies: postcss: 8.4.21 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-import@14.1.0(postcss@8.4.40): - dependencies: - postcss: 8.4.40 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.8 - - postcss-js@4.0.1(postcss@8.4.21): + /postcss-js@4.0.1(postcss@8.4.21): + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 dependencies: camelcase-css: 2.0.1 postcss: 8.4.21 - postcss-js@4.0.1(postcss@8.4.40): - dependencies: - camelcase-css: 2.0.1 - postcss: 8.4.40 - - postcss-load-config@3.1.4(postcss@8.4.21): + /postcss-load-config@3.1.4(postcss@8.4.21): + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true dependencies: lilconfig: 2.1.0 - yaml: 1.10.2 - optionalDependencies: postcss: 8.4.21 - - postcss-load-config@3.1.4(postcss@8.4.40): - dependencies: - lilconfig: 2.1.0 yaml: 1.10.2 - optionalDependencies: - postcss: 8.4.40 - postcss-load-config@6.0.1(postcss@8.4.40)(yaml@2.5.0): + /postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true dependencies: lilconfig: 3.1.2 - optionalDependencies: - postcss: 8.4.40 - yaml: 2.5.0 + dev: true - postcss-nested@6.0.0(postcss@8.4.21): + /postcss-nested@6.0.0(postcss@8.4.21): + resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 dependencies: postcss: 8.4.21 postcss-selector-parser: 6.1.1 - postcss-nested@6.0.0(postcss@8.4.40): - dependencies: - postcss: 8.4.40 - postcss-selector-parser: 6.1.1 - - postcss-selector-parser@6.1.1: + /postcss-selector-parser@6.1.1: + resolution: {integrity: sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==} + engines: {node: '>=4'} dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-value-parser@4.2.0: {} + /postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.21: + /postcss@8.4.21: + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.7 picocolors: 1.0.1 source-map-js: 1.2.0 - postcss@8.4.31: + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.7 picocolors: 1.0.1 source-map-js: 1.2.0 + dev: false - postcss@8.4.40: + /postcss@8.4.41: + resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} + engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.7 picocolors: 1.0.1 source-map-js: 1.2.0 + dev: true - preferred-pm@3.1.4: + /preferred-pm@3.1.4: + resolution: {integrity: sha512-lEHd+yEm22jXdCphDrkvIJQU66EuLojPPtvZkpKIkiD+l0DMThF/niqZKJSoU8Vl7iuvtmzyMhir9LdVy5WMnA==} + engines: {node: '>=10'} dependencies: find-up: 5.0.0 find-yarn-workspace-root2: 1.2.16 path-exists: 4.0.0 which-pm: 2.2.0 + dev: true - prelude-ls@1.2.1: {} + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} - prettier-plugin-organize-imports@4.0.0(prettier@3.3.3)(typescript@5.5.4): + /prettier-plugin-organize-imports@4.0.0(prettier@3.3.3)(typescript@5.5.4): + resolution: {integrity: sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==} + peerDependencies: + '@vue/language-plugin-pug': ^2.0.24 + prettier: '>=2.0' + typescript: '>=2.9' + vue-tsc: ^2.0.24 + peerDependenciesMeta: + '@vue/language-plugin-pug': + optional: true + vue-tsc: + optional: true dependencies: prettier: 3.3.3 typescript: 5.5.4 + dev: true - prettier-plugin-tailwindcss@0.1.13(prettier@3.3.3): + /prettier-plugin-tailwindcss@0.1.13(prettier@3.3.3): + resolution: {integrity: sha512-/EKQURUrxLu66CMUg4+1LwGdxnz8of7IDvrSLqEtDqhLH61SAlNNUSr90UTvZaemujgl3OH/VHg+fyGltrNixw==} + engines: {node: '>=12.17.0'} + peerDependencies: + prettier: '>=2.2.0' dependencies: prettier: 3.3.3 + dev: true - prettier@2.8.8: {} + /prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true - prettier@3.3.3: {} + /prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + dev: true - pretty-bytes@5.6.0: {} + /pretty-bytes@5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} + dev: true - pretty-format@27.5.1: + /pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: ansi-regex: 5.0.1 ansi-styles: 5.2.0 react-is: 17.0.2 + dev: true - pretty-format@29.7.0: + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 react-is: 18.3.1 + dev: true - process@0.11.10: {} + /process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + dev: true - prop-types@15.8.1: + /prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 + dev: false - protobufjs@7.3.2: + /protobufjs@7.3.2: + resolution: {integrity: sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==} + engines: {node: '>=12.0.0'} + requiresBuild: true dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/base64': 1.1.2 @@ -8194,121 +6250,200 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.14.13 + '@types/node': 20.14.14 long: 5.2.3 - proxy-from-env@1.0.0: {} + /proxy-from-env@1.0.0: + resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} + dev: true - proxy-from-env@1.1.0: {} + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: true - ps-tree@1.2.0: + /ps-tree@1.2.0: + resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} + engines: {node: '>= 0.10'} + hasBin: true dependencies: event-stream: 3.3.4 + dev: true - pseudomap@1.0.2: {} + /pseudomap@1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + dev: true - psl@1.9.0: {} + /psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + dev: true - pstree.remy@1.1.8: {} + /pstree.remy@1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + dev: true - pump@3.0.0: + /pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: end-of-stream: 1.4.4 once: 1.4.0 + dev: true - punycode@2.3.1: {} + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} - qrcode.react@3.1.0(react@18.3.1): + /qrcode.react@3.1.0(react@18.3.1): + resolution: {integrity: sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: react: 18.3.1 + dev: false - qs@6.10.4: + /qs@6.10.4: + resolution: {integrity: sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==} + engines: {node: '>=0.6'} dependencies: side-channel: 1.0.6 + dev: true - querystringify@2.2.0: {} + /querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: true - queue-microtask@1.2.3: {} + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-lru@5.1.1: {} + /quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} - react-dom@18.3.1(react@18.2.0): + /react-dom@18.3.1(react@18.2.0): + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 dependencies: loose-envify: 1.4.0 react: 18.2.0 scheduler: 0.23.2 + dev: false - react-dom@18.3.1(react@18.3.1): + /react-dom@18.3.1(react@18.3.1): + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 dependencies: loose-envify: 1.4.0 react: 18.3.1 scheduler: 0.23.2 - react-hook-form@7.39.5(react@18.3.1): + /react-hook-form@7.39.5(react@18.3.1): + resolution: {integrity: sha512-OE0HKyz5IPc6svN2wd+e+evidZrw4O4WZWAWYzQVZuHi+hYnHFSLnxOq0ddjbdmaLIsLHut/ab7j72y2QT3+KA==} + engines: {node: '>=12.22.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 dependencies: react: 18.3.1 + dev: false - react-is@16.13.1: {} + /react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + dev: false - react-is@17.0.2: {} + /react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + dev: true - react-is@18.3.1: {} + /react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + dev: true - react-refresh@0.14.2: {} + /react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + dev: true - react@18.2.0: + /react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: false + + /react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} dependencies: loose-envify: 1.4.0 - react@18.3.1: - dependencies: - loose-envify: 1.4.0 - - read-cache@1.0.0: + /read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: pify: 2.3.0 - read-pkg-up@8.0.0: + /read-pkg-up@8.0.0: + resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} + engines: {node: '>=12'} dependencies: find-up: 5.0.0 read-pkg: 6.0.0 type-fest: 1.4.0 + dev: true - read-pkg@6.0.0: + /read-pkg@6.0.0: + resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==} + engines: {node: '>=12'} dependencies: '@types/normalize-package-data': 2.4.4 normalize-package-data: 3.0.3 parse-json: 5.2.0 type-fest: 1.4.0 + dev: true - read-yaml-file@1.1.0: + /read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} dependencies: graceful-fs: 4.2.11 js-yaml: 3.14.1 pify: 4.0.1 strip-bom: 3.0.0 + dev: true - readable-stream@3.6.2: + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + dev: true - readdirp@3.6.0: + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 - redent@3.0.0: + /redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} dependencies: indent-string: 4.0.0 strip-indent: 3.0.0 + dev: true - redent@4.0.0: + /redent@4.0.0: + resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} + engines: {node: '>=12'} dependencies: indent-string: 5.0.0 strip-indent: 4.0.0 + dev: true - reflect.getprototypeof@1.0.6: + /reflect.getprototypeof@1.0.6: + resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 @@ -8317,133 +6452,211 @@ snapshots: get-intrinsic: 1.2.4 globalthis: 1.0.4 which-builtin-type: 1.1.4 + dev: false - regenerator-runtime@0.14.1: {} + /regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + dev: true - regexp.prototype.flags@1.5.2: + /regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-errors: 1.3.0 set-function-name: 2.0.2 - request-progress@3.0.0: + /request-progress@3.0.0: + resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==} dependencies: throttleit: 1.0.1 + dev: true - require-directory@2.1.1: {} + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} - requires-port@1.0.0: {} + /requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + dev: true - resolve-from@4.0.0: {} + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} - resolve-from@5.0.0: {} + /resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true - resolve-pkg-maps@1.0.0: {} + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: false - resolve@1.22.8: + /resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true dependencies: is-core-module: 2.15.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resolve@2.0.0-next.5: + /resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true dependencies: is-core-module: 2.15.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + dev: false - restore-cursor@3.1.0: + /restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} dependencies: onetime: 5.1.2 signal-exit: 3.0.7 + dev: true - reusify@1.0.4: {} + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rfdc@1.4.1: {} + /rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + dev: true - rimraf@3.0.2: + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true dependencies: glob: 7.2.3 - rollup@4.19.1: + /rollup@4.20.0: + resolution: {integrity: sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.19.1 - '@rollup/rollup-android-arm64': 4.19.1 - '@rollup/rollup-darwin-arm64': 4.19.1 - '@rollup/rollup-darwin-x64': 4.19.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.19.1 - '@rollup/rollup-linux-arm-musleabihf': 4.19.1 - '@rollup/rollup-linux-arm64-gnu': 4.19.1 - '@rollup/rollup-linux-arm64-musl': 4.19.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.19.1 - '@rollup/rollup-linux-riscv64-gnu': 4.19.1 - '@rollup/rollup-linux-s390x-gnu': 4.19.1 - '@rollup/rollup-linux-x64-gnu': 4.19.1 - '@rollup/rollup-linux-x64-musl': 4.19.1 - '@rollup/rollup-win32-arm64-msvc': 4.19.1 - '@rollup/rollup-win32-ia32-msvc': 4.19.1 - '@rollup/rollup-win32-x64-msvc': 4.19.1 + '@rollup/rollup-android-arm-eabi': 4.20.0 + '@rollup/rollup-android-arm64': 4.20.0 + '@rollup/rollup-darwin-arm64': 4.20.0 + '@rollup/rollup-darwin-x64': 4.20.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.20.0 + '@rollup/rollup-linux-arm-musleabihf': 4.20.0 + '@rollup/rollup-linux-arm64-gnu': 4.20.0 + '@rollup/rollup-linux-arm64-musl': 4.20.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.20.0 + '@rollup/rollup-linux-riscv64-gnu': 4.20.0 + '@rollup/rollup-linux-s390x-gnu': 4.20.0 + '@rollup/rollup-linux-x64-gnu': 4.20.0 + '@rollup/rollup-linux-x64-musl': 4.20.0 + '@rollup/rollup-win32-arm64-msvc': 4.20.0 + '@rollup/rollup-win32-ia32-msvc': 4.20.0 + '@rollup/rollup-win32-x64-msvc': 4.20.0 fsevents: 2.3.3 + dev: true - rrweb-cssom@0.6.0: {} + /rrweb-cssom@0.6.0: + resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} + dev: true - rrweb-cssom@0.7.1: {} + /rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + dev: true - run-parallel@1.2.0: + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 - rxjs@7.8.1: + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: tslib: 2.6.3 + dev: true - safe-array-concat@1.1.2: + /safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + engines: {node: '>=0.4'} dependencies: call-bind: 1.0.7 get-intrinsic: 1.2.4 has-symbols: 1.0.3 isarray: 2.0.5 + dev: false - safe-buffer@5.2.1: {} + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true - safe-regex-test@1.0.3: + /safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-regex: 1.1.4 + dev: false - safer-buffer@2.1.2: {} + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: true - sass@1.77.8: + /sass@1.77.8: + resolution: {integrity: sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==} + engines: {node: '>=14.0.0'} + hasBin: true dependencies: chokidar: 3.6.0 immutable: 4.3.7 source-map-js: 1.2.0 - saxes@6.0.0: + /saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} dependencies: xmlchars: 2.2.0 + dev: true - scheduler@0.23.2: + /scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} dependencies: loose-envify: 1.4.0 - semver@5.7.2: {} + /semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + dev: true - semver@6.3.1: {} + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true - semver@7.0.0: {} + /semver@7.0.0: + resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==} + hasBin: true + dev: true - semver@7.6.3: {} + /semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true - server-only@0.0.1: {} + /server-only@0.0.1: + resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} + dev: false - set-blocking@2.0.0: {} + /set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + dev: true - set-function-length@1.2.2: + /set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 @@ -8452,99 +6665,162 @@ snapshots: gopd: 1.0.1 has-property-descriptors: 1.0.2 - set-function-name@2.0.2: + /set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 - shebang-command@1.2.0: + /shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} dependencies: shebang-regex: 1.0.0 + dev: true - shebang-command@2.0.0: + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - shebang-regex@1.0.0: {} + /shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + dev: true - shebang-regex@3.0.0: {} + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} - shell-quote@1.8.1: {} + /shell-quote@1.8.1: + resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + dev: true - side-channel@1.0.6: + /side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 es-errors: 1.3.0 get-intrinsic: 1.2.4 object-inspect: 1.13.2 - siginfo@2.0.0: {} + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true - signal-exit@3.0.7: {} + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true - signal-exit@4.1.0: {} + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} - simple-update-notifier@1.1.0: + /simple-update-notifier@1.1.0: + resolution: {integrity: sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==} + engines: {node: '>=8.10.0'} dependencies: semver: 7.0.0 + dev: true - slash@3.0.0: {} + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} - slash@4.0.0: {} + /slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + dev: true - slice-ansi@3.0.0: + /slice-ansi@3.0.0: + resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} + engines: {node: '>=8'} dependencies: ansi-styles: 4.3.0 astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 + dev: true - slice-ansi@4.0.0: + /slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 + dev: true - slice-ansi@5.0.0: + /slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} dependencies: ansi-styles: 6.2.1 is-fullwidth-code-point: 4.0.0 + dev: true - source-map-js@1.2.0: {} + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} - source-map@0.8.0-beta.0: + /source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} dependencies: whatwg-url: 7.1.0 + dev: true - spawn-command@0.0.2: {} + /spawn-command@0.0.2: + resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} + dev: true - spawndamnit@2.0.0: + /spawndamnit@2.0.0: + resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} dependencies: cross-spawn: 5.1.0 signal-exit: 3.0.7 + dev: true - spdx-correct@3.2.0: + /spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: spdx-expression-parse: 3.0.1 spdx-license-ids: 3.0.18 + dev: true - spdx-exceptions@2.5.0: {} + /spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + dev: true - spdx-expression-parse@3.0.1: + /spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} dependencies: spdx-exceptions: 2.5.0 spdx-license-ids: 3.0.18 + dev: true - spdx-license-ids@3.0.18: {} + /spdx-license-ids@3.0.18: + resolution: {integrity: sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==} + dev: true - split@0.3.3: + /split@0.3.3: + resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} dependencies: through: 2.3.8 + dev: true - sprintf-js@1.0.3: {} + /sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + dev: true - sshpk@1.18.0: + /sshpk@1.18.0: + resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} + engines: {node: '>=0.10.0'} + hasBin: true dependencies: asn1: 0.2.6 assert-plus: 1.0.0 @@ -8555,10 +6831,16 @@ snapshots: jsbn: 0.1.1 safer-buffer: 2.1.2 tweetnacl: 0.14.5 + dev: true - stackback@0.0.2: {} + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true - start-server-and-test@2.0.5: + /start-server-and-test@2.0.5: + resolution: {integrity: sha512-2CV4pz69NJVJKQmJeSr+O+SPtOreu0yxvhPmSXclzmAKkPREuMabyMh+Txpzemjx0RDzXOcG2XkhiUuxjztSQw==} + engines: {node: '>=16'} + hasBin: true dependencies: arg: 5.0.2 bluebird: 3.7.2 @@ -8570,39 +6852,60 @@ snapshots: wait-on: 7.2.0(debug@4.3.6) transitivePeerDependencies: - supports-color + dev: true - std-env@3.7.0: {} + /std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + dev: true - stop-iteration-iterator@1.0.0: + /stop-iteration-iterator@1.0.0: + resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} + engines: {node: '>= 0.4'} dependencies: internal-slot: 1.0.7 - stream-combiner@0.0.4: + /stream-combiner@0.0.4: + resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} dependencies: duplexer: 0.1.2 + dev: true - streamsearch@1.1.0: {} + /streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + dev: false - string-argv@0.3.2: {} + /string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + dev: true - string-width@4.2.3: + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - string.prototype.includes@2.0.0: + /string.prototype.includes@2.0.0: + resolution: {integrity: sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==} dependencies: define-properties: 1.2.1 es-abstract: 1.23.3 + dev: false - string.prototype.matchall@4.0.11: + /string.prototype.matchall@4.0.11: + resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 @@ -8616,78 +6919,138 @@ snapshots: regexp.prototype.flags: 1.5.2 set-function-name: 2.0.2 side-channel: 1.0.6 + dev: false - string.prototype.repeat@1.0.0: + /string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} dependencies: define-properties: 1.2.1 es-abstract: 1.23.3 + dev: false - string.prototype.trim@1.2.9: + /string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 es-object-atoms: 1.0.0 + dev: false - string.prototype.trimend@1.0.8: + /string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-object-atoms: 1.0.0 + dev: false - string.prototype.trimstart@1.0.8: + /string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-object-atoms: 1.0.0 + dev: false - string_decoder@1.3.0: + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 + dev: true - strip-ansi@6.0.1: + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - strip-bom@3.0.0: {} + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} - strip-final-newline@2.0.0: {} + /strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + dev: true - strip-final-newline@3.0.0: {} + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true - strip-indent@3.0.0: + /strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} dependencies: min-indent: 1.0.1 + dev: true - strip-indent@4.0.0: + /strip-indent@4.0.0: + resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} + engines: {node: '>=12'} dependencies: min-indent: 1.0.1 + dev: true - strip-json-comments@3.1.1: {} + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} - strip-literal@2.1.0: + /strip-literal@2.1.0: + resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} dependencies: js-tokens: 9.0.0 + dev: true - styled-jsx@5.1.1(@babel/core@7.25.2)(react@18.2.0): + /styled-jsx@5.1.1(@babel/core@7.25.2)(react@18.2.0): + resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true dependencies: + '@babel/core': 7.25.2 client-only: 0.0.1 react: 18.2.0 - optionalDependencies: - '@babel/core': 7.25.2 + dev: false - styled-jsx@5.1.1(@babel/core@7.25.2)(react@18.3.1): + /styled-jsx@5.1.1(@babel/core@7.25.2)(react@18.3.1): + resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true dependencies: + '@babel/core': 7.25.2 client-only: 0.0.1 react: 18.3.1 - optionalDependencies: - '@babel/core': 7.25.2 + dev: false - sucrase@3.35.0: + /sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true dependencies: '@jridgewell/gen-mapping': 0.3.5 commander: 4.1.1 @@ -8696,30 +7059,50 @@ snapshots: mz: 2.7.0 pirates: 4.0.6 ts-interface-checker: 0.1.13 + dev: true - supports-color@5.5.0: + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} dependencies: has-flag: 3.0.0 - supports-color@7.2.0: + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} dependencies: has-flag: 4.0.0 - supports-color@8.1.1: + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} dependencies: has-flag: 4.0.0 - supports-preserve-symlinks-flag@1.0.0: {} + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} - swr@2.2.5(react@18.3.1): + /swr@2.2.5(react@18.3.1): + resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 dependencies: client-only: 0.0.1 react: 18.3.1 use-sync-external-store: 1.2.2(react@18.3.1) + dev: false - symbol-tree@3.2.4: {} + /symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + dev: true - tailwindcss@3.2.4(postcss@8.4.21): + /tailwindcss@3.2.4(postcss@8.4.21): + resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==} + engines: {node: '>=12.13.0'} + hasBin: true + peerDependencies: + postcss: ^8.0.9 dependencies: arg: 5.0.2 chokidar: 3.6.0 @@ -8747,37 +7130,14 @@ snapshots: transitivePeerDependencies: - ts-node - tailwindcss@3.2.4(postcss@8.4.40): - dependencies: - arg: 5.0.2 - chokidar: 3.6.0 - color-name: 1.1.4 - detective: 5.2.1 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.2 - glob-parent: 6.0.2 - is-glob: 4.0.3 - lilconfig: 2.1.0 - micromatch: 4.0.7 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.0.1 - postcss: 8.4.40 - postcss-import: 14.1.0(postcss@8.4.40) - postcss-js: 4.0.1(postcss@8.4.40) - postcss-load-config: 3.1.4(postcss@8.4.40) - postcss-nested: 6.0.0(postcss@8.4.40) - postcss-selector-parser: 6.1.1 - postcss-value-parser: 4.2.0 - quick-lru: 5.1.1 - resolve: 1.22.8 - transitivePeerDependencies: - - ts-node + /tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + dev: false - tapable@2.2.1: {} - - tar@6.2.1: + /tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} dependencies: chownr: 2.0.0 fs-minipass: 2.1.0 @@ -8785,106 +7145,206 @@ snapshots: minizlib: 2.1.2 mkdirp: 1.0.4 yallist: 4.0.0 + dev: true - term-size@2.2.1: {} + /term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + dev: true - text-table@0.2.0: {} + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - thenify-all@1.6.0: + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 + dev: true - thenify@3.3.1: + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 + dev: true - throttleit@1.0.1: {} + /throttleit@1.0.1: + resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==} + dev: true - through@2.3.8: {} + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true - tinybench@2.8.0: {} + /tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + dev: true - tinycolor2@1.4.2: {} + /tinycolor2@1.4.2: + resolution: {integrity: sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==} + dev: false - tinypool@0.8.4: {} + /tinypool@0.8.4: + resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} + engines: {node: '>=14.0.0'} + dev: true - tinyspy@2.2.1: {} + /tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + dev: true - tmp@0.0.33: + /tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} dependencies: os-tmpdir: 1.0.2 + dev: true - tmp@0.2.3: {} + /tmp@0.2.3: + resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + engines: {node: '>=14.14'} + dev: true - to-fast-properties@2.0.0: {} + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} - to-regex-range@5.0.1: + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 - toggle-selection@1.0.6: {} + /toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + dev: false - touch@3.1.1: {} + /touch@3.1.1: + resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} + hasBin: true + dev: true - tough-cookie@4.1.4: + /tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} dependencies: psl: 1.9.0 punycode: 2.3.1 universalify: 0.2.0 url-parse: 1.5.10 + dev: true - tr46@0.0.3: {} + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: true - tr46@1.0.1: + /tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} dependencies: punycode: 2.3.1 + dev: true - tr46@5.0.0: + /tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} dependencies: punycode: 2.3.1 + dev: true - tree-kill@1.2.2: {} + /tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + dev: true - trim-newlines@4.1.1: {} + /trim-newlines@4.1.1: + resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==} + engines: {node: '>=12'} + dev: true - ts-api-utils@1.3.0(typescript@5.5.4): + /ts-api-utils@1.3.0(typescript@5.5.4): + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' dependencies: typescript: 5.5.4 + dev: false - ts-error@1.0.6: {} + /ts-error@1.0.6: + resolution: {integrity: sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==} + dev: false - ts-interface-checker@0.1.13: {} + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: true - ts-poet@6.9.0: + /ts-poet@6.9.0: + resolution: {integrity: sha512-roe6W6MeZmCjRmppyfOURklO5tQFQ6Sg7swURKkwYJvV7dbGCrK28um5+51iW3twdPRKtwarqFAVMU6G1mvnuQ==} dependencies: dprint-node: 1.0.8 + dev: true - ts-proto-descriptors@1.16.0: + /ts-proto-descriptors@1.16.0: + resolution: {integrity: sha512-3yKuzMLpltdpcyQji1PJZRfoo4OJjNieKTYkQY8pF7xGKsYz/RHe3aEe4KiRxcinoBmnEhmuI+yJTxLb922ULA==} dependencies: long: 5.2.3 protobufjs: 7.3.2 + dev: true - ts-proto@1.181.1: + /ts-proto@1.181.1: + resolution: {integrity: sha512-lNmd/KEgqWtwDG9mIM3EpcxBx+URRVHkDP/EEJBgQJaQwmZFTk6VjHg56HNQswd114yXGfF+8pKQvJ2iH9KfWw==} + hasBin: true dependencies: case-anything: 2.1.13 protobufjs: 7.3.2 ts-poet: 6.9.0 ts-proto-descriptors: 1.16.0 + dev: true - tsconfck@3.1.1(typescript@5.5.4): - optionalDependencies: + /tsconfck@3.1.1(typescript@5.5.4): + resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + dependencies: typescript: 5.5.4 + dev: true - tsconfig-paths@3.15.0: + /tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} dependencies: '@types/json5': 0.0.29 json5: 1.0.2 minimist: 1.2.8 strip-bom: 3.0.0 + dev: false - tslib@2.6.3: {} + /tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} - tsup@8.2.3(postcss@8.4.40)(typescript@5.5.4)(yaml@2.5.0): + /tsup@8.2.4(typescript@5.5.4): + resolution: {integrity: sha512-akpCPePnBnC/CXgRrcy72ZSntgIEUa1jN0oJbbvpALWKNOz1B7aM+UVDWGRGIO/T/PZugAESWDJUAb5FD48o8Q==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true dependencies: bundle-require: 5.0.0(esbuild@0.23.0) cac: 6.7.14 @@ -8896,44 +7356,77 @@ snapshots: globby: 11.1.0 joycon: 3.1.1 picocolors: 1.0.1 - postcss-load-config: 6.0.1(postcss@8.4.40)(yaml@2.5.0) + postcss-load-config: 6.0.1 resolve-from: 5.0.0 - rollup: 4.19.1 + rollup: 4.20.0 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tree-kill: 1.2.2 - optionalDependencies: - postcss: 8.4.40 typescript: 5.5.4 transitivePeerDependencies: - jiti - supports-color - tsx - yaml + dev: true - tunnel-agent@0.6.0: + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: safe-buffer: 5.2.1 + dev: true - turbo-darwin-64@2.0.9: + /turbo-darwin-64@2.0.9: + resolution: {integrity: sha512-owlGsOaExuVGBUfrnJwjkL1BWlvefjSKczEAcpLx4BI7Oh6ttakOi+JyomkPkFlYElRpjbvlR2gP8WIn6M/+xQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true optional: true - turbo-darwin-arm64@2.0.9: + /turbo-darwin-arm64@2.0.9: + resolution: {integrity: sha512-XAXkKkePth5ZPPE/9G9tTnPQx0C8UTkGWmNGYkpmGgRr8NedW+HrPsi9N0HcjzzIH9A4TpNYvtiV+WcwdaEjKA==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true optional: true - turbo-linux-64@2.0.9: + /turbo-linux-64@2.0.9: + resolution: {integrity: sha512-l9wSgEjrCFM1aG16zItBsZ206ZlhSSx1owB8Cgskfv0XyIXRGHRkluihiaxkp+UeU5WoEfz4EN5toc+ICA0q0w==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true - turbo-linux-arm64@2.0.9: + /turbo-linux-arm64@2.0.9: + resolution: {integrity: sha512-gRnjxXRne18B27SwxXMqL3fJu7jw/8kBrOBTBNRSmZZiG1Uu3nbnP7b4lgrA/bCku6C0Wligwqurvtpq6+nFHA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true - turbo-windows-64@2.0.9: + /turbo-windows-64@2.0.9: + resolution: {integrity: sha512-ZVo0apxUvaRq4Vm1qhsfqKKhtRgReYlBVf9MQvVU1O9AoyydEQvLDO1ryqpXDZWpcHoFxHAQc9msjAMtE5K2lA==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true optional: true - turbo-windows-arm64@2.0.9: + /turbo-windows-arm64@2.0.9: + resolution: {integrity: sha512-sGRz7c5Pey6y7y9OKi8ypbWNuIRPF9y8xcMqL56OZifSUSo+X2EOsOleR9MKxQXVaqHPGOUKWsE6y8hxBi9pag==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true optional: true - turbo@2.0.9: + /turbo@2.0.9: + resolution: {integrity: sha512-QaLaUL1CqblSKKPgLrFW3lZWkWG4pGBQNW+q1ScJB5v1D/nFWtsrD/yZljW/bdawg90ihi4/ftQJ3h6fz1FamA==} + hasBin: true optionalDependencies: turbo-darwin-64: 2.0.9 turbo-darwin-arm64: 2.0.9 @@ -8941,36 +7434,60 @@ snapshots: turbo-linux-arm64: 2.0.9 turbo-windows-64: 2.0.9 turbo-windows-arm64: 2.0.9 + dev: true - tweetnacl@0.14.5: {} + /tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + dev: true - type-check@0.4.0: + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 - type-detect@4.1.0: {} + /type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + dev: true - type-fest@0.20.2: {} + /type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} - type-fest@0.21.3: {} + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true - type-fest@1.4.0: {} + /type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + dev: true - typed-array-buffer@1.0.2: + /typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-typed-array: 1.1.13 + dev: false - typed-array-byte-length@1.0.1: + /typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 for-each: 0.3.3 gopd: 1.0.1 has-proto: 1.0.3 is-typed-array: 1.1.13 + dev: false - typed-array-byte-offset@1.0.2: + /typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.7 @@ -8978,8 +7495,11 @@ snapshots: gopd: 1.0.1 has-proto: 1.0.3 is-typed-array: 1.1.13 + dev: false - typed-array-length@1.0.6: + /typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 for-each: 0.3.3 @@ -8987,75 +7507,124 @@ snapshots: has-proto: 1.0.3 is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 + dev: false - typescript@5.5.4: {} + /typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + engines: {node: '>=14.17'} + hasBin: true - ufo@1.5.4: {} + /ufo@1.5.4: + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + dev: true - unbox-primitive@1.0.2: + /unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: call-bind: 1.0.7 has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + dev: false - undefsafe@2.0.5: {} + /undefsafe@2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + dev: true - undici-types@5.26.5: {} + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici@5.28.4: + /undici@5.28.4: + resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} + engines: {node: '>=14.0'} dependencies: '@fastify/busboy': 2.1.1 + dev: false - universalify@0.1.2: {} + /universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + dev: true - universalify@0.2.0: {} + /universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + dev: true - universalify@2.0.1: {} + /universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + dev: true - untildify@4.0.0: {} + /untildify@4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + dev: true - update-browserslist-db@1.1.0(browserslist@4.23.2): + /update-browserslist-db@1.1.0(browserslist@4.23.3): + resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' dependencies: - browserslist: 4.23.2 + browserslist: 4.23.3 escalade: 3.1.2 picocolors: 1.0.1 - uri-js@4.4.1: + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.3.1 - url-parse@1.5.10: + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} dependencies: querystringify: 2.2.0 requires-port: 1.0.0 + dev: true - use-sync-external-store@1.2.2(react@18.3.1): + /use-sync-external-store@1.2.2(react@18.3.1): + resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: react: 18.3.1 + dev: false - util-deprecate@1.0.2: {} + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@8.3.2: {} + /uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: true - validate-npm-package-license@3.0.4: + /validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 + dev: true - verror@1.10.0: + /verror@1.10.0: + resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} + engines: {'0': node >=0.6.0} dependencies: assert-plus: 1.0.0 core-util-is: 1.0.2 extsprintf: 1.3.0 + dev: true - vite-node@1.6.0(@types/node@20.14.13)(sass@1.77.8): + /vite-node@1.6.0: + resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true dependencies: cac: 6.7.14 debug: 4.3.6(supports-color@8.1.1) pathe: 1.1.2 picocolors: 1.0.1 - vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vite: 5.3.5 transitivePeerDependencies: - '@types/node' - less @@ -9065,29 +7634,84 @@ snapshots: - sugarss - supports-color - terser + dev: true - vite-tsconfig-paths@4.3.2(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)): + /vite-tsconfig-paths@4.3.2(typescript@5.5.4)(vite@5.3.5): + resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true dependencies: debug: 4.3.6(supports-color@8.1.1) globrex: 0.1.2 tsconfck: 3.1.1(typescript@5.5.4) - optionalDependencies: - vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vite: 5.3.5 transitivePeerDependencies: - supports-color - typescript + dev: true - vite@5.3.5(@types/node@20.14.13)(sass@1.77.8): + /vite@5.3.5: + resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true dependencies: esbuild: 0.21.5 - postcss: 8.4.40 - rollup: 4.19.1 + postcss: 8.4.41 + rollup: 4.20.0 optionalDependencies: - '@types/node': 20.14.13 fsevents: 2.3.3 - sass: 1.77.8 + dev: true - vitest@1.6.0(@types/node@20.14.13)(jsdom@24.1.1)(sass@1.77.8): + /vitest@1.6.0: + resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 1.6.0 + '@vitest/ui': 1.6.0 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -9104,14 +7728,11 @@ snapshots: picocolors: 1.0.1 std-env: 3.7.0 strip-literal: 2.1.0 - tinybench: 2.8.0 + tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) - vite-node: 1.6.0(@types/node@20.14.13)(sass@1.77.8) + vite: 5.3.5 + vite-node: 1.6.0 why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 20.14.13 - jsdom: 24.1.1 transitivePeerDependencies: - less - lightningcss @@ -9120,50 +7741,79 @@ snapshots: - sugarss - supports-color - terser + dev: true - w3c-xmlserializer@5.0.0: + /w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} dependencies: xml-name-validator: 5.0.0 + dev: true - wait-on@7.2.0(debug@4.3.6): + /wait-on@7.2.0(debug@4.3.6): + resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} + engines: {node: '>=12.0.0'} + hasBin: true dependencies: - axios: 1.7.2(debug@4.3.6) + axios: 1.7.3(debug@4.3.6) joi: 17.13.3 lodash: 4.17.21 minimist: 1.2.8 rxjs: 7.8.1 transitivePeerDependencies: - debug + dev: true - webidl-conversions@3.0.1: {} + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: true - webidl-conversions@4.0.2: {} + /webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + dev: true - webidl-conversions@7.0.0: {} + /webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + dev: true - whatwg-encoding@3.1.1: + /whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} dependencies: iconv-lite: 0.6.3 + dev: true - whatwg-mimetype@4.0.0: {} + /whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + dev: true - whatwg-url@14.0.0: + /whatwg-url@14.0.0: + resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + engines: {node: '>=18'} dependencies: tr46: 5.0.0 webidl-conversions: 7.0.0 + dev: true - whatwg-url@5.0.0: + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 + dev: true - whatwg-url@7.1.0: + /whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} dependencies: lodash.sortby: 4.7.0 tr46: 1.0.1 webidl-conversions: 4.0.2 + dev: true - which-boxed-primitive@1.0.2: + /which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: is-bigint: 1.0.4 is-boolean-object: 1.1.2 @@ -9171,7 +7821,9 @@ snapshots: is-string: 1.0.7 is-symbol: 1.0.4 - which-builtin-type@1.1.4: + /which-builtin-type@1.1.4: + resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==} + engines: {node: '>= 0.4'} dependencies: function.prototype.name: 1.1.6 has-tostringtag: 1.0.2 @@ -9185,20 +7837,28 @@ snapshots: which-boxed-primitive: 1.0.2 which-collection: 1.0.2 which-typed-array: 1.1.15 + dev: false - which-collection@1.0.2: + /which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} dependencies: is-map: 2.0.3 is-set: 2.0.3 is-weakmap: 2.0.2 is-weakset: 2.0.3 - which-pm@2.2.0: + /which-pm@2.2.0: + resolution: {integrity: sha512-MOiaDbA5ZZgUjkeMWM5EkJp4loW5ZRoa5bc3/aeMox/PJelMhE6t7S/mLuiY43DBupyxH+S0U1bTui9kWUlmsw==} + engines: {node: '>=8.15'} dependencies: load-yaml-file: 0.2.0 path-exists: 4.0.0 + dev: true - which-typed-array@1.1.15: + /which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.7 @@ -9206,70 +7866,130 @@ snapshots: gopd: 1.0.1 has-tostringtag: 1.0.2 - which@1.3.1: + /which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true dependencies: isexe: 2.0.0 - which@2.0.2: - dependencies: - isexe: 2.0.0 - - why-is-node-running@2.3.0: + /why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true dependencies: siginfo: 2.0.0 stackback: 0.0.2 + dev: true - wide-align@1.1.5: + /wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: string-width: 4.2.3 + dev: true - word-wrap@1.2.5: {} + /word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} - wrap-ansi@6.2.0: + /wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - wrappy@1.0.2: {} + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.0: {} + /ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true - xml-name-validator@5.0.0: {} + /xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + dev: true - xmlchars@2.2.0: {} + /xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + dev: true - xtend@4.0.2: {} + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} - y18n@5.0.8: {} + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} - yallist@2.1.2: {} + /yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + dev: true - yallist@3.1.1: {} + /yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: {} + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true - yaml@1.10.2: {} + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} - yaml@2.5.0: {} + /yaml@2.5.0: + resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==} + engines: {node: '>= 14'} + hasBin: true + dev: true - yargs-parser@20.2.9: {} + /yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + dev: true - yargs-parser@21.1.1: {} + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} - yargs@17.7.2: + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} dependencies: cliui: 8.0.1 escalade: 3.1.2 @@ -9279,11 +7999,18 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yauzl@2.10.0: + /yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} dependencies: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 + dev: true - yocto-queue@0.1.0: {} + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} - yocto-queue@1.1.1: {} + /yocto-queue@1.1.1: + resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} + engines: {node: '>=12.20'} + dev: true From 9d5751ae7a7358a7bd8048125758318698ec50bd Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 7 Aug 2024 15:55:50 +0200 Subject: [PATCH 004/640] postcss vulnerability --- apps/login/package.json | 2 +- packages/zitadel-next/package.json | 2 +- packages/zitadel-react/package.json | 2 +- pnpm-lock.yaml | 69 +++++++++++++---------------- 4 files changed, 33 insertions(+), 42 deletions(-) diff --git a/apps/login/package.json b/apps/login/package.json index 9afbf349a0..1967a3f68b 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -77,7 +77,7 @@ "lint-staged": "13.0.3", "make-dir-cli": "3.0.0", "nodemon": "^2.0.22", - "postcss": "8.4.21", + "postcss": "8.4.31", "prettier-plugin-tailwindcss": "0.1.13", "sass": "^1.77.1", "start-server-and-test": "^2.0.0", diff --git a/packages/zitadel-next/package.json b/packages/zitadel-next/package.json index 5b4ef1519f..4326a8b150 100644 --- a/packages/zitadel-next/package.json +++ b/packages/zitadel-next/package.json @@ -38,7 +38,7 @@ "@types/react": "^17.0.80", "@zitadel/tsconfig": "workspace:*", "eslint-config-zitadel": "workspace:*", - "postcss": "8.4.21", + "postcss": "8.4.31", "tailwindcss": "3.2.4", "zitadel-tailwind-config": "workspace:*" } diff --git a/packages/zitadel-react/package.json b/packages/zitadel-react/package.json index eb8592a532..337b551b27 100644 --- a/packages/zitadel-react/package.json +++ b/packages/zitadel-react/package.json @@ -40,7 +40,7 @@ "autoprefixer": "10.4.13", "eslint-config-zitadel": "workspace:*", "jsdom": "^24.0.0", - "postcss": "8.4.21", + "postcss": "8.4.31", "sass": "^1.77.1", "tailwindcss": "3.2.4", "zitadel-tailwind-config": "workspace:*" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa9b14cb02..eb2bbc6835 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -149,7 +149,7 @@ importers: version: link:../../packages/zitadel-tsconfig autoprefixer: specifier: 10.4.13 - version: 10.4.13(postcss@8.4.21) + version: 10.4.13(postcss@8.4.31) concurrently: specifier: ^8.1.0 version: 8.2.2 @@ -178,8 +178,8 @@ importers: specifier: ^2.0.22 version: 2.0.22 postcss: - specifier: 8.4.21 - version: 8.4.21 + specifier: 8.4.31 + version: 8.4.31 prettier-plugin-tailwindcss: specifier: 0.1.13 version: 0.1.13(prettier@3.3.3) @@ -191,7 +191,7 @@ importers: version: 2.0.5 tailwindcss: specifier: 3.2.4 - version: 3.2.4(postcss@8.4.21) + version: 3.2.4(postcss@8.4.31) ts-proto: specifier: ^1.139.0 version: 1.181.1 @@ -270,11 +270,11 @@ importers: specifier: workspace:* version: link:../eslint-config-zitadel postcss: - specifier: 8.4.21 - version: 8.4.21 + specifier: 8.4.31 + version: 8.4.31 tailwindcss: specifier: 3.2.4 - version: 3.2.4(postcss@8.4.21) + version: 3.2.4(postcss@8.4.31) zitadel-tailwind-config: specifier: workspace:* version: link:../zitadel-tailwind-config @@ -339,7 +339,7 @@ importers: version: link:../zitadel-tsconfig autoprefixer: specifier: 10.4.13 - version: 10.4.13(postcss@8.4.21) + version: 10.4.13(postcss@8.4.31) eslint-config-zitadel: specifier: workspace:* version: link:../eslint-config-zitadel @@ -347,14 +347,14 @@ importers: specifier: ^24.0.0 version: 24.1.1 postcss: - specifier: 8.4.21 - version: 8.4.21 + specifier: 8.4.31 + version: 8.4.31 sass: specifier: ^1.77.1 version: 1.77.8 tailwindcss: specifier: 3.2.4 - version: 3.2.4(postcss@8.4.21) + version: 3.2.4(postcss@8.4.31) zitadel-tailwind-config: specifier: workspace:* version: link:../zitadel-tailwind-config @@ -366,7 +366,7 @@ importers: version: 0.5.3(tailwindcss@3.2.4) tailwindcss: specifier: ^3.2.4 - version: 3.2.4(postcss@8.4.21) + version: 3.2.4(postcss@8.4.31) packages/zitadel-tsconfig: {} @@ -1914,7 +1914,7 @@ packages: tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.2.4(postcss@8.4.21) + tailwindcss: 3.2.4(postcss@8.4.31) dev: true /@tailwindcss/forms@0.5.7(tailwindcss@3.2.4): @@ -1923,7 +1923,7 @@ packages: tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.2.4(postcss@8.4.21) + tailwindcss: 3.2.4(postcss@8.4.31) dev: false /@tanstack/react-virtual@3.8.4(react-dom@18.3.1)(react@18.3.1): @@ -2611,7 +2611,7 @@ packages: engines: {node: '>= 4.0.0'} dev: true - /autoprefixer@10.4.13(postcss@8.4.21): + /autoprefixer@10.4.13(postcss@8.4.31): resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} engines: {node: ^10 || ^12 || >=14} hasBin: true @@ -2623,7 +2623,7 @@ packages: fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.1 - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -6045,27 +6045,27 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} - /postcss-import@14.1.0(postcss@8.4.21): + /postcss-import@14.1.0(postcss@8.4.31): resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} engines: {node: '>=10.0.0'} peerDependencies: postcss: ^8.0.0 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - /postcss-js@4.0.1(postcss@8.4.21): + /postcss-js@4.0.1(postcss@8.4.31): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} engines: {node: ^12 || ^14 || >= 16} peerDependencies: postcss: ^8.4.21 dependencies: camelcase-css: 2.0.1 - postcss: 8.4.21 + postcss: 8.4.31 - /postcss-load-config@3.1.4(postcss@8.4.21): + /postcss-load-config@3.1.4(postcss@8.4.31): resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} engines: {node: '>= 10'} peerDependencies: @@ -6078,7 +6078,7 @@ packages: optional: true dependencies: lilconfig: 2.1.0 - postcss: 8.4.21 + postcss: 8.4.31 yaml: 1.10.2 /postcss-load-config@6.0.1: @@ -6102,13 +6102,13 @@ packages: lilconfig: 3.1.2 dev: true - /postcss-nested@6.0.0(postcss@8.4.21): + /postcss-nested@6.0.0(postcss@8.4.31): resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-selector-parser: 6.1.1 /postcss-selector-parser@6.1.1: @@ -6121,14 +6121,6 @@ packages: /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - /postcss@8.4.21: - resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 - /postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -6136,7 +6128,6 @@ packages: nanoid: 3.3.7 picocolors: 1.0.1 source-map-js: 1.2.0 - dev: false /postcss@8.4.41: resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} @@ -7097,7 +7088,7 @@ packages: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: true - /tailwindcss@3.2.4(postcss@8.4.21): + /tailwindcss@3.2.4(postcss@8.4.31): resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==} engines: {node: '>=12.13.0'} hasBin: true @@ -7118,11 +7109,11 @@ packages: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.1 - postcss: 8.4.21 - postcss-import: 14.1.0(postcss@8.4.21) - postcss-js: 4.0.1(postcss@8.4.21) - postcss-load-config: 3.1.4(postcss@8.4.21) - postcss-nested: 6.0.0(postcss@8.4.21) + postcss: 8.4.31 + postcss-import: 14.1.0(postcss@8.4.31) + postcss-js: 4.0.1(postcss@8.4.31) + postcss-load-config: 3.1.4(postcss@8.4.31) + postcss-nested: 6.0.0(postcss@8.4.31) postcss-selector-parser: 6.1.1 postcss-value-parser: 4.2.0 quick-lru: 5.1.1 From 451385c19821fc5bc1aff3dea9714958f337ace5 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 8 Aug 2024 08:58:54 +0200 Subject: [PATCH 005/640] cookie utils --- apps/login/src/app/(login)/accounts/page.tsx | 2 +- apps/login/src/app/(login)/mfa/page.tsx | 2 +- apps/login/src/app/(login)/mfa/set/page.tsx | 2 +- .../src/app/(login)/otp/[method]/set/page.tsx | 3 - .../src/app/(login)/passkey/add/page.tsx | 2 +- .../src/app/(login)/passkey/login/page.tsx | 2 +- apps/login/src/app/(login)/password/page.tsx | 2 +- apps/login/src/app/(login)/signedin/page.tsx | 2 +- apps/login/src/app/(login)/u2f/page.tsx | 2 +- apps/login/src/app/(login)/u2f/set/page.tsx | 2 +- apps/login/src/app/api/otp/set/route.ts | 5 +- apps/login/src/app/api/passkeys/route.ts | 2 +- .../src/app/api/passkeys/verify/route.ts | 2 +- apps/login/src/app/api/session/route.ts | 5 +- apps/login/src/app/api/u2f/route.ts | 2 +- apps/login/src/app/api/u2f/verify/route.ts | 2 +- apps/login/src/app/login/route.ts | 4 +- apps/login/src/app/sessions/route.ts | 4 +- apps/login/src/lib/server-actions.ts | 2 +- apps/login/src/utils/cookies.ts | 284 ------------------ apps/login/src/utils/session.ts | 31 +- packages/zitadel-next/src/index.tsx | 1 + 22 files changed, 42 insertions(+), 323 deletions(-) delete mode 100644 apps/login/src/utils/cookies.ts diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index b7b17d95b1..164dbae5bc 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -1,5 +1,5 @@ import { getBrandingSettings, listSessions } from "@/lib/zitadel"; -import { getAllSessionCookieIds } from "@/utils/cookies"; +import { getAllSessionCookieIds } from "@zitadel/next"; import { UserPlusIcon } from "@heroicons/react/24/outline"; import Link from "next/link"; import SessionsList from "@/ui/SessionsList"; diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index 75d491b856..e230819390 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -11,7 +11,7 @@ import UserAvatar from "@/ui/UserAvatar"; import { getMostRecentCookieWithLoginname, getSessionCookieById, -} from "@/utils/cookies"; +} from "@zitadel/next"; export default async function Page({ searchParams, diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index c9f3ce8c36..9e52b10a83 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -13,7 +13,7 @@ import UserAvatar from "@/ui/UserAvatar"; import { getMostRecentCookieWithLoginname, getSessionCookieById, -} from "@/utils/cookies"; +} from "@zitadel/next"; export default async function Page({ searchParams, diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index 23f5dcb199..740f758b97 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -2,17 +2,14 @@ import { addOTPEmail, addOTPSMS, getBrandingSettings, - getSession, registerTOTP, } from "@/lib/zitadel"; import Alert from "@/ui/Alert"; import BackButton from "@/ui/BackButton"; import { Button, ButtonVariants } from "@/ui/Button"; import DynamicTheme from "@/ui/DynamicTheme"; -import { Spinner } from "@/ui/Spinner"; import TOTPRegister from "@/ui/TOTPRegister"; import UserAvatar from "@/ui/UserAvatar"; -import { getMostRecentCookieWithLoginname } from "@/utils/cookies"; import Link from "next/link"; import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { loadMostRecentSession } from "@zitadel/next"; diff --git a/apps/login/src/app/(login)/passkey/add/page.tsx b/apps/login/src/app/(login)/passkey/add/page.tsx index 52a3fe3a2a..7bb80eae8a 100644 --- a/apps/login/src/app/(login)/passkey/add/page.tsx +++ b/apps/login/src/app/(login)/passkey/add/page.tsx @@ -3,7 +3,7 @@ import Alert, { AlertType } from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; import RegisterPasskey from "@/ui/RegisterPasskey"; import UserAvatar from "@/ui/UserAvatar"; -import { getMostRecentCookieWithLoginname } from "@/utils/cookies"; +import { getMostRecentCookieWithLoginname } from "@zitadel/next"; import { loadMostRecentSession } from "@zitadel/next"; export default async function Page({ diff --git a/apps/login/src/app/(login)/passkey/login/page.tsx b/apps/login/src/app/(login)/passkey/login/page.tsx index fe31534850..d1f6d36391 100644 --- a/apps/login/src/app/(login)/passkey/login/page.tsx +++ b/apps/login/src/app/(login)/passkey/login/page.tsx @@ -6,7 +6,7 @@ import UserAvatar from "@/ui/UserAvatar"; import { getMostRecentCookieWithLoginname, getSessionCookieById, -} from "@/utils/cookies"; +} from "@zitadel/next"; const title = "Authenticate with a passkey"; const description = diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 5601784fe7..2bad137461 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -7,7 +7,7 @@ import Alert from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; import PasswordForm from "@/ui/PasswordForm"; import UserAvatar from "@/ui/UserAvatar"; -import { getMostRecentCookieWithLoginname } from "@/utils/cookies"; +import { getMostRecentCookieWithLoginname } from "@zitadel/next"; import { loadMostRecentSession } from "@zitadel/next"; export default async function Page({ diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index bdc6d0d698..b924f5703c 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -1,7 +1,7 @@ import { createCallback, getBrandingSettings, getSession } from "@/lib/zitadel"; import DynamicTheme from "@/ui/DynamicTheme"; import UserAvatar from "@/ui/UserAvatar"; -import { getMostRecentCookieWithLoginname } from "@/utils/cookies"; +import { getMostRecentCookieWithLoginname } from "@zitadel/next"; import { redirect } from "next/navigation"; async function loadSession(loginName: string, authRequestId?: string) { diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index 2c8c8b19d6..bd9975e784 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -10,7 +10,7 @@ import UserAvatar from "@/ui/UserAvatar"; import { getMostRecentCookieWithLoginname, getSessionCookieById, -} from "@/utils/cookies"; +} from "@zitadel/next"; export default async function Page({ searchParams, diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index 33de8a775f..5834c7828c 100644 --- a/apps/login/src/app/(login)/u2f/set/page.tsx +++ b/apps/login/src/app/(login)/u2f/set/page.tsx @@ -4,7 +4,7 @@ import DynamicTheme from "@/ui/DynamicTheme"; import RegisterPasskey from "@/ui/RegisterPasskey"; import RegisterU2F from "@/ui/RegisterU2F"; import UserAvatar from "@/ui/UserAvatar"; -import { getMostRecentCookieWithLoginname } from "@/utils/cookies"; +import { getMostRecentCookieWithLoginname } from "@zitadel/next"; import { loadMostRecentSession } from "@zitadel/next"; export default async function Page({ diff --git a/apps/login/src/app/api/otp/set/route.ts b/apps/login/src/app/api/otp/set/route.ts index 174dfff119..d5afcb0162 100644 --- a/apps/login/src/app/api/otp/set/route.ts +++ b/apps/login/src/app/api/otp/set/route.ts @@ -1,9 +1,8 @@ import { - SessionCookie, getMostRecentSessionCookie, getSessionCookieById, getSessionCookieByLoginName, -} from "@/utils/cookies"; +} from "@zitadel/next"; import { setSessionAndUpdateCookie } from "@/utils/session"; import { NextRequest, NextResponse, userAgent } from "next/server"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; @@ -16,7 +15,7 @@ export async function POST(request: NextRequest) { const { loginName, sessionId, organization, authRequestId, code, method } = body; - const recentPromise: Promise = sessionId + const recentPromise = sessionId ? getSessionCookieById(sessionId).catch((error) => { return Promise.reject(error); }) diff --git a/apps/login/src/app/api/passkeys/route.ts b/apps/login/src/app/api/passkeys/route.ts index 4489bee308..a9f135da2a 100644 --- a/apps/login/src/app/api/passkeys/route.ts +++ b/apps/login/src/app/api/passkeys/route.ts @@ -3,7 +3,7 @@ import { getSession, registerPasskey, } from "@/lib/zitadel"; -import { getSessionCookieById } from "@/utils/cookies"; +import { getSessionCookieById } from "@zitadel/next"; import { NextRequest, NextResponse } from "next/server"; export async function POST(request: NextRequest) { diff --git a/apps/login/src/app/api/passkeys/verify/route.ts b/apps/login/src/app/api/passkeys/verify/route.ts index a09b848c08..674d17cb0c 100644 --- a/apps/login/src/app/api/passkeys/verify/route.ts +++ b/apps/login/src/app/api/passkeys/verify/route.ts @@ -1,5 +1,5 @@ import { getSession, verifyPasskeyRegistration } from "@/lib/zitadel"; -import { getSessionCookieById } from "@/utils/cookies"; +import { getSessionCookieById } from "@zitadel/next"; import { NextRequest, NextResponse, userAgent } from "next/server"; export async function POST(request: NextRequest) { diff --git a/apps/login/src/app/api/session/route.ts b/apps/login/src/app/api/session/route.ts index c0bc0d06e2..83baceef86 100644 --- a/apps/login/src/app/api/session/route.ts +++ b/apps/login/src/app/api/session/route.ts @@ -5,12 +5,11 @@ import { listAuthenticationMethodTypes, } from "@/lib/zitadel"; import { - SessionCookie, getMostRecentSessionCookie, getSessionCookieById, getSessionCookieByLoginName, removeSessionFromCookie, -} from "@/utils/cookies"; +} from "@zitadel/next"; import { createSessionAndUpdateCookie, createSessionForIdpAndUpdateCookie, @@ -76,7 +75,7 @@ export async function PUT(request: NextRequest) { challenges, } = body; - const recentPromise: Promise = sessionId + const recentPromise = sessionId ? getSessionCookieById(sessionId).catch((error) => { return Promise.reject(error); }) diff --git a/apps/login/src/app/api/u2f/route.ts b/apps/login/src/app/api/u2f/route.ts index 6a1c1a82b4..7789582c89 100644 --- a/apps/login/src/app/api/u2f/route.ts +++ b/apps/login/src/app/api/u2f/route.ts @@ -4,7 +4,7 @@ import { registerPasskey, registerU2F, } from "@/lib/zitadel"; -import { getSessionCookieById } from "@/utils/cookies"; +import { getSessionCookieById } from "@zitadel/next"; import { NextRequest, NextResponse } from "next/server"; export async function POST(request: NextRequest) { diff --git a/apps/login/src/app/api/u2f/verify/route.ts b/apps/login/src/app/api/u2f/verify/route.ts index eae5d13fe8..6e8558d590 100644 --- a/apps/login/src/app/api/u2f/verify/route.ts +++ b/apps/login/src/app/api/u2f/verify/route.ts @@ -1,5 +1,5 @@ import { getSession, verifyU2FRegistration } from "@/lib/zitadel"; -import { getSessionCookieById } from "@/utils/cookies"; +import { getSessionCookieById } from "@zitadel/next"; import { NextRequest, NextResponse, userAgent } from "next/server"; import { VerifyU2FRegistrationRequest } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { PlainMessage } from "@zitadel/client"; diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 0393813534..7cb9d10bc7 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -6,7 +6,7 @@ import { listSessions, startIdentityProviderFlow, } from "@/lib/zitadel"; -import { SessionCookie, getAllSessions } from "@/utils/cookies"; +import { getAllSessions } from "@zitadel/next"; import { NextRequest, NextResponse } from "next/server"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { @@ -52,7 +52,7 @@ export async function GET(request: NextRequest) { const authRequestId = searchParams.get("authRequest"); const sessionId = searchParams.get("sessionId"); - const sessionCookies: SessionCookie[] = await getAllSessions(); + const sessionCookies = await getAllSessions(); const ids = sessionCookies.map((s) => s.id); let sessions: Session[] = []; if (ids && ids.length) { diff --git a/apps/login/src/app/sessions/route.ts b/apps/login/src/app/sessions/route.ts index 330ad15633..062a5696df 100644 --- a/apps/login/src/app/sessions/route.ts +++ b/apps/login/src/app/sessions/route.ts @@ -1,5 +1,5 @@ import { listSessions } from "@/lib/zitadel"; -import { SessionCookie, getAllSessions } from "@/utils/cookies"; +import { getAllSessions } from "@zitadel/next"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { NextRequest, NextResponse } from "next/server"; @@ -12,7 +12,7 @@ async function loadSessions(ids: string[]): Promise { } export async function GET(request: NextRequest) { - const sessionCookies: SessionCookie[] = await getAllSessions(); + const sessionCookies = await getAllSessions(); const ids = sessionCookies.map((s) => s.id); let sessions: Session[] = []; if (ids && ids.length) { diff --git a/apps/login/src/lib/server-actions.ts b/apps/login/src/lib/server-actions.ts index 612f5f059c..14e1889509 100644 --- a/apps/login/src/lib/server-actions.ts +++ b/apps/login/src/lib/server-actions.ts @@ -1,6 +1,6 @@ "use server"; -import { getMostRecentCookieWithLoginname } from "@/utils/cookies"; +import { getMostRecentCookieWithLoginname } from "@zitadel/next"; import { getSession, verifyTOTPRegistration } from "./zitadel"; export async function verifyTOTP( diff --git a/apps/login/src/utils/cookies.ts b/apps/login/src/utils/cookies.ts deleted file mode 100644 index 526e5de4f1..0000000000 --- a/apps/login/src/utils/cookies.ts +++ /dev/null @@ -1,284 +0,0 @@ -"use server"; - -import { cookies } from "next/headers"; - -export type SessionCookie = { - id: string; - token: string; - loginName: string; - organization?: string; - creationDate: string; - expirationDate: string; - changeDate: string; - authRequestId?: string; // if its linked to an OIDC flow -}; - -function setSessionHttpOnlyCookie(sessions: SessionCookie[]) { - const cookiesList = cookies(); - // @ts-ignore - return cookiesList.set({ - name: "sessions", - value: JSON.stringify(sessions), - httpOnly: true, - path: "/", - }); -} - -export async function addSessionToCookie( - session: SessionCookie, - cleanup: boolean = false, -): Promise { - const cookiesList = cookies(); - const stringifiedCookie = cookiesList.get("sessions"); - - let currentSessions: SessionCookie[] = stringifiedCookie?.value - ? JSON.parse(stringifiedCookie?.value) - : []; - - const index = currentSessions.findIndex( - (s) => s.loginName === session.loginName, - ); - - if (index > -1) { - currentSessions[index] = session; - } else { - currentSessions = [...currentSessions, session]; - } - - if (cleanup) { - const now = new Date(); - const filteredSessions = currentSessions.filter((session) => - session.expirationDate ? new Date(session.expirationDate) > now : true, - ); - return setSessionHttpOnlyCookie(filteredSessions); - } else { - return setSessionHttpOnlyCookie(currentSessions); - } -} - -export async function updateSessionCookie( - id: string, - session: SessionCookie, - cleanup: boolean = false, -): Promise { - const cookiesList = cookies(); - const stringifiedCookie = cookiesList.get("sessions"); - - const sessions: SessionCookie[] = stringifiedCookie?.value - ? JSON.parse(stringifiedCookie?.value) - : [session]; - - const foundIndex = sessions.findIndex((session) => session.id === id); - - if (foundIndex > -1) { - sessions[foundIndex] = session; - if (cleanup) { - const now = new Date(); - const filteredSessions = sessions.filter((session) => - session.expirationDate ? new Date(session.expirationDate) > now : true, - ); - return setSessionHttpOnlyCookie(filteredSessions); - } else { - return setSessionHttpOnlyCookie(sessions); - } - } else { - throw "updateSessionCookie: session id now found"; - } -} - -export async function removeSessionFromCookie( - session: SessionCookie, - cleanup: boolean = false, -): Promise { - const cookiesList = cookies(); - const stringifiedCookie = cookiesList.get("sessions"); - - const sessions: SessionCookie[] = stringifiedCookie?.value - ? JSON.parse(stringifiedCookie?.value) - : [session]; - - const reducedSessions = sessions.filter((s) => s.id !== session.id); - if (cleanup) { - const now = new Date(); - const filteredSessions = reducedSessions.filter((session) => - session.expirationDate ? new Date(session.expirationDate) > now : true, - ); - return setSessionHttpOnlyCookie(filteredSessions); - } else { - return setSessionHttpOnlyCookie(reducedSessions); - } -} - -export async function getMostRecentSessionCookie(): Promise { - const cookiesList = cookies(); - const stringifiedCookie = cookiesList.get("sessions"); - - if (stringifiedCookie?.value) { - const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); - - const latest = sessions.reduce((prev, current) => { - return new Date(prev.changeDate).getTime() > - new Date(current.changeDate).getTime() - ? prev - : current; - }); - - return latest; - } else { - return Promise.reject("no session cookie found"); - } -} - -export async function getSessionCookieById( - id: string, - organization?: string, -): Promise { - const cookiesList = cookies(); - const stringifiedCookie = cookiesList.get("sessions"); - - if (stringifiedCookie?.value) { - const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); - - const found = sessions.find((s) => - organization - ? s.organization === organization && s.id === id - : s.id === id, - ); - if (found) { - return found; - } else { - return Promise.reject(); - } - } else { - return Promise.reject(); - } -} - -export async function getSessionCookieByLoginName( - loginName: string, - organization?: string, -): Promise { - const cookiesList = cookies(); - const stringifiedCookie = cookiesList.get("sessions"); - - if (stringifiedCookie?.value) { - const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); - const found = sessions.find((s) => - organization - ? s.organization === organization && s.loginName === loginName - : s.loginName === loginName, - ); - if (found) { - return found; - } else { - return Promise.reject("no cookie found with loginName: " + loginName); - } - } else { - return Promise.reject("no session cookie found"); - } -} - -/** - * - * @param cleanup when true, removes all expired sessions, default true - * @returns Session Cookies - */ -export async function getAllSessionCookieIds( - cleanup: boolean = false, -): Promise { - const cookiesList = cookies(); - const stringifiedCookie = cookiesList.get("sessions"); - - if (stringifiedCookie?.value) { - const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); - - if (cleanup) { - const now = new Date(); - return sessions - .filter((session) => - session.expirationDate - ? new Date(session.expirationDate) > now - : true, - ) - .map((session) => session.id); - } else { - return sessions.map((session) => session.id); - } - } else { - return []; - } -} - -/** - * - * @param cleanup when true, removes all expired sessions, default true - * @returns Session Cookies - */ -export async function getAllSessions( - cleanup: boolean = false, -): Promise { - const cookiesList = cookies(); - const stringifiedCookie = cookiesList.get("sessions"); - - if (stringifiedCookie?.value) { - const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); - - if (cleanup) { - const now = new Date(); - return sessions.filter((session) => - session.expirationDate ? new Date(session.expirationDate) > now : true, - ); - } else { - return sessions; - } - } else { - return []; - } -} - -/** - * Returns most recent session filtered by optinal loginName - * @param loginName - * @returns most recent session - */ -export async function getMostRecentCookieWithLoginname( - loginName?: string, - organization?: string, -): Promise { - const cookiesList = cookies(); - const stringifiedCookie = cookiesList.get("sessions"); - - if (stringifiedCookie?.value) { - const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); - let filtered = sessions.filter((cookie) => { - return !!loginName ? cookie.loginName === loginName : true; - }); - - if (organization) { - filtered = filtered.filter((cookie) => { - return cookie.organization === organization; - }); - } - - const latest = - filtered && filtered.length - ? filtered.reduce((prev, current) => { - return new Date(prev.changeDate).getTime() > - new Date(current.changeDate).getTime() - ? prev - : current; - }) - : undefined; - - if (latest) { - return latest; - } else { - console.error("sessions", sessions, loginName, organization); - return Promise.reject("Could not get the context or retrieve a session"); - } - } else { - return Promise.reject("Could not read session cookie"); - } -} - -export async function clearSessions() {} diff --git a/apps/login/src/utils/session.ts b/apps/login/src/utils/session.ts index a9e83d4604..7a2a6853a4 100644 --- a/apps/login/src/utils/session.ts +++ b/apps/login/src/utils/session.ts @@ -6,11 +6,7 @@ import { getSession, setSession, } from "@/lib/zitadel"; -import { - SessionCookie, - addSessionToCookie, - updateSessionCookie, -} from "./cookies"; +import { addSessionToCookie, updateSessionCookie } from "@zitadel/next"; import { Challenges, RequestChallenges, @@ -19,6 +15,17 @@ import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PlainMessage } from "@zitadel/client"; +type CustomCookieData = { + id: string; + token: string; + loginName: string; + organization?: string; + creationDate: string; + expirationDate: string; + changeDate: string; + authRequestId?: string; // if its linked to an OIDC flow +}; + export async function createSessionAndUpdateCookie( loginName: string, password: string | undefined, @@ -43,7 +50,7 @@ export async function createSessionAndUpdateCookie( createdSession.sessionToken, ).then((response) => { if (response?.session && response.session?.factors?.user?.loginName) { - const sessionCookie: SessionCookie = { + const sessionCookie: any = { id: createdSession.sessionId, token: createdSession.sessionToken, creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`, @@ -61,7 +68,7 @@ export async function createSessionAndUpdateCookie( sessionCookie.organization = organization; } - return addSessionToCookie(sessionCookie).then(() => { + return addSessionToCookie(sessionCookie).then(() => { return response.session as Session; }); } else { @@ -96,7 +103,7 @@ export async function createSessionForUserIdAndUpdateCookie( createdSession.sessionToken, ).then((response) => { if (response?.session && response.session?.factors?.user?.loginName) { - const sessionCookie: SessionCookie = { + const sessionCookie: any = { id: createdSession.sessionId, token: createdSession.sessionToken, creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`, @@ -146,7 +153,7 @@ export async function createSessionForIdpAndUpdateCookie( createdSession.sessionToken, ).then((response) => { if (response?.session && response.session?.factors?.user?.loginName) { - const sessionCookie: SessionCookie = { + const sessionCookie: any = { id: createdSession.sessionId, token: createdSession.sessionToken, creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`, @@ -181,7 +188,7 @@ export type SessionWithChallenges = Session & { }; export async function setSessionAndUpdateCookie( - recentCookie: SessionCookie, + recentCookie: CustomCookieData, checks: PlainMessage, challenges: RequestChallenges | undefined, authRequestId: string | undefined, @@ -193,7 +200,7 @@ export async function setSessionAndUpdateCookie( checks, ).then((updatedSession) => { if (updatedSession) { - const sessionCookie: SessionCookie = { + const sessionCookie: CustomCookieData = { id: recentCookie.id, token: updatedSession.sessionToken, creationDate: recentCookie.creationDate, @@ -211,7 +218,7 @@ export async function setSessionAndUpdateCookie( (response) => { if (response?.session && response.session.factors?.user?.loginName) { const { session } = response; - const newCookie: SessionCookie = { + const newCookie: CustomCookieData = { id: sessionCookie.id, token: updatedSession.sessionToken, creationDate: sessionCookie.creationDate, diff --git a/packages/zitadel-next/src/index.tsx b/packages/zitadel-next/src/index.tsx index d218d51870..2ff89e0ddd 100644 --- a/packages/zitadel-next/src/index.tsx +++ b/packages/zitadel-next/src/index.tsx @@ -1,4 +1,5 @@ // import "./styles.css"; export { ZitadelNextProvider, type ZitadelNextProps } from "./components/ZitadelNextProvider"; +export * from "./utils/cookies"; export { loadMostRecentSession } from "./utils/session"; From afa024f1e4b613f5feadc30cb5f6dc0927be2559 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 8 Aug 2024 09:06:01 +0200 Subject: [PATCH 006/640] move transport out of package --- packages/zitadel-next/src/utils/session.ts | 26 ++++++++-------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/packages/zitadel-next/src/utils/session.ts b/packages/zitadel-next/src/utils/session.ts index 4e8c6c9f94..fc50d005bc 100644 --- a/packages/zitadel-next/src/utils/session.ts +++ b/packages/zitadel-next/src/utils/session.ts @@ -1,22 +1,14 @@ -import { createSessionServiceClient } from "@zitadel/client/v2"; -import { createServerTransport } from "@zitadel/node"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { GetSessionResponse } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { getMostRecentCookieWithLoginname } from "./cookies"; -const SESSION_LIFETIME_S = 3000; - -const transport = createServerTransport(process.env.ZITADEL_SERVICE_USER_TOKEN!, { - baseUrl: process.env.ZITADEL_API_URL!, - httpVersion: "2", -}); - -const sessionService = createSessionServiceClient(transport); - -export async function loadMostRecentSession(loginName?: string, organization?: string): Promise { +export async function loadMostRecentSession( + sessionService: any, // TODO: SessionServiceClient, + loginName?: string, + organization?: string, +): Promise { const recent = await getMostRecentCookieWithLoginname(loginName, organization); - return getMostRecentSession(recent.id, recent.token); -} - -async function getMostRecentSession(sessionId: string, sessionToken: string) { - return sessionService.getSession({ sessionId, sessionToken }, {}).then((resp) => resp.session); + return sessionService + .getSession({ sessionId: recent.id, sessionToken: recent.token }, {}) + .then((resp: GetSessionResponse) => resp.session); } From cacd4e76a27c7c01b7eef02a7934f0bb748e92f1 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 8 Aug 2024 09:24:03 +0200 Subject: [PATCH 007/640] single-object --- apps/login/src/app/(login)/mfa/page.tsx | 4 +-- apps/login/src/app/api/otp/set/route.ts | 2 +- apps/login/src/app/api/session/route.ts | 2 +- packages/zitadel-next/src/utils/cookies.ts | 29 +++++++++++++++++----- packages/zitadel-next/src/utils/session.ts | 2 +- packages/zitadel-next/turbo.json | 3 --- packages/zitadel-proto/turbo.json | 2 +- 7 files changed, 29 insertions(+), 15 deletions(-) diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index e230819390..bb875daf3b 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -29,10 +29,10 @@ export default async function Page({ loginName?: string, organization?: string, ) { - const recent = await getMostRecentCookieWithLoginname( + const recent = await getMostRecentCookieWithLoginname({ loginName, organization, - ); + }); return getSession(recent.id, recent.token).then((response) => { if (response?.session && response.session.factors?.user?.id) { return listAuthenticationMethodTypes( diff --git a/apps/login/src/app/api/otp/set/route.ts b/apps/login/src/app/api/otp/set/route.ts index d5afcb0162..f7fa1d020c 100644 --- a/apps/login/src/app/api/otp/set/route.ts +++ b/apps/login/src/app/api/otp/set/route.ts @@ -20,7 +20,7 @@ export async function POST(request: NextRequest) { return Promise.reject(error); }) : loginName - ? getSessionCookieByLoginName(loginName, organization).catch( + ? getSessionCookieByLoginName({ loginName, organization }).catch( (error) => { return Promise.reject(error); }, diff --git a/apps/login/src/app/api/session/route.ts b/apps/login/src/app/api/session/route.ts index 83baceef86..1a6a4329d5 100644 --- a/apps/login/src/app/api/session/route.ts +++ b/apps/login/src/app/api/session/route.ts @@ -80,7 +80,7 @@ export async function PUT(request: NextRequest) { return Promise.reject(error); }) : loginName - ? getSessionCookieByLoginName(loginName, organization).catch( + ? getSessionCookieByLoginName({ loginName, organization }).catch( (error) => { return Promise.reject(error); }, diff --git a/packages/zitadel-next/src/utils/cookies.ts b/packages/zitadel-next/src/utils/cookies.ts index e16a48602e..1cc4eadea6 100644 --- a/packages/zitadel-next/src/utils/cookies.ts +++ b/packages/zitadel-next/src/utils/cookies.ts @@ -110,7 +110,13 @@ export async function getMostRecentSessionCookie(): Promise { } } -export async function getSessionCookieById(id: string, organization?: string): Promise> { +export async function getSessionCookieById({ + id, + organization, +}: { + id: string; + organization?: string; +}): Promise> { const cookiesList = cookies(); const stringifiedCookie = cookiesList.get("sessions"); @@ -128,7 +134,13 @@ export async function getSessionCookieById(id: string, organization?: string) } } -export async function getSessionCookieByLoginName(loginName: string, organization?: string): Promise> { +export async function getSessionCookieByLoginName({ + loginName, + organization, +}: { + loginName?: string; + organization?: string; +}): Promise> { const cookiesList = cookies(); const stringifiedCookie = cookiesList.get("sessions"); @@ -197,10 +209,17 @@ export async function getAllSessions(cleanup: boolean = false): Promise(loginName?: string, organization?: string): Promise { +export async function getMostRecentCookieWithLoginname({ + loginName, + organization, +}: { + loginName?: string; + organization?: string; +}): Promise { const cookiesList = cookies(); const stringifiedCookie = cookiesList.get("sessions"); @@ -233,5 +252,3 @@ export async function getMostRecentCookieWithLoginname(loginName?: string, or return Promise.reject("Could not read session cookie"); } } - -export async function clearSessions() {} diff --git a/packages/zitadel-next/src/utils/session.ts b/packages/zitadel-next/src/utils/session.ts index fc50d005bc..fac865fffc 100644 --- a/packages/zitadel-next/src/utils/session.ts +++ b/packages/zitadel-next/src/utils/session.ts @@ -7,7 +7,7 @@ export async function loadMostRecentSession( loginName?: string, organization?: string, ): Promise { - const recent = await getMostRecentCookieWithLoginname(loginName, organization); + const recent = await getMostRecentCookieWithLoginname({ loginName, organization }); return sessionService .getSession({ sessionId: recent.id, sessionToken: recent.token }, {}) .then((resp: GetSessionResponse) => resp.session); diff --git a/packages/zitadel-next/turbo.json b/packages/zitadel-next/turbo.json index 5a1a7d4995..3e82b759f9 100644 --- a/packages/zitadel-next/turbo.json +++ b/packages/zitadel-next/turbo.json @@ -1,9 +1,6 @@ { "extends": ["//"], "tasks": { - "dev": { - "dependsOn": ["@zitadel/client#build"] - }, "build": { "outputs": ["dist/**"], "dependsOn": ["@zitadel/client#build", "@zitadel/react#build"] diff --git a/packages/zitadel-proto/turbo.json b/packages/zitadel-proto/turbo.json index 2d24f0349b..bffd614f62 100644 --- a/packages/zitadel-proto/turbo.json +++ b/packages/zitadel-proto/turbo.json @@ -3,7 +3,7 @@ "tasks": { "generate": { "outputs": ["zitadel/**"], - "cache": false + "cache": true } } } From 113e19a9579e2a07cfdcaf3764f59074e2c94036 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 8 Aug 2024 09:28:59 +0200 Subject: [PATCH 008/640] single-object --- apps/login/src/app/(login)/mfa/page.tsx | 2 +- apps/login/src/app/(login)/mfa/set/page.tsx | 6 +++--- apps/login/src/app/(login)/passkey/add/page.tsx | 1 - apps/login/src/app/(login)/passkey/login/page.tsx | 6 +++--- apps/login/src/app/(login)/password/page.tsx | 1 - apps/login/src/app/(login)/signedin/page.tsx | 2 +- apps/login/src/app/(login)/u2f/page.tsx | 6 +++--- apps/login/src/app/api/otp/set/route.ts | 2 +- apps/login/src/app/api/passkeys/route.ts | 2 +- apps/login/src/app/api/passkeys/verify/route.ts | 2 +- apps/login/src/app/api/session/route.ts | 6 +++--- apps/login/src/app/api/u2f/route.ts | 2 +- apps/login/src/app/api/u2f/verify/route.ts | 2 +- apps/login/src/lib/server-actions.ts | 2 +- packages/zitadel-next/src/utils/cookies.ts | 8 +++++--- 15 files changed, 25 insertions(+), 25 deletions(-) diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index bb875daf3b..cce415ff59 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -48,7 +48,7 @@ export default async function Page({ } async function loadSessionById(sessionId: string, organization?: string) { - const recent = await getSessionCookieById(sessionId, organization); + const recent = await getSessionCookieById({ sessionId, organization }); return getSession(recent.id, recent.token).then((response) => { if (response?.session && response.session.factors?.user?.id) { return listAuthenticationMethodTypes( diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index 9e52b10a83..f39b84f305 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -31,10 +31,10 @@ export default async function Page({ loginName?: string, organization?: string, ) { - const recent = await getMostRecentCookieWithLoginname( + const recent = await getMostRecentCookieWithLoginname({ loginName, organization, - ); + }); return getSession(recent.id, recent.token).then((response) => { if (response?.session && response.session.factors?.user?.id) { const userId = response.session.factors.user.id; @@ -58,7 +58,7 @@ export default async function Page({ } async function loadSessionById(sessionId: string, organization?: string) { - const recent = await getSessionCookieById(sessionId, organization); + const recent = await getSessionCookieById({ sessionId, organization }); return getSession(recent.id, recent.token).then((response) => { if (response?.session && response.session.factors?.user?.id) { const userId = response.session.factors.user.id; diff --git a/apps/login/src/app/(login)/passkey/add/page.tsx b/apps/login/src/app/(login)/passkey/add/page.tsx index 7bb80eae8a..59b63020aa 100644 --- a/apps/login/src/app/(login)/passkey/add/page.tsx +++ b/apps/login/src/app/(login)/passkey/add/page.tsx @@ -3,7 +3,6 @@ import Alert, { AlertType } from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; import RegisterPasskey from "@/ui/RegisterPasskey"; import UserAvatar from "@/ui/UserAvatar"; -import { getMostRecentCookieWithLoginname } from "@zitadel/next"; import { loadMostRecentSession } from "@zitadel/next"; export default async function Page({ diff --git a/apps/login/src/app/(login)/passkey/login/page.tsx b/apps/login/src/app/(login)/passkey/login/page.tsx index d1f6d36391..dea25d19a1 100644 --- a/apps/login/src/app/(login)/passkey/login/page.tsx +++ b/apps/login/src/app/(login)/passkey/login/page.tsx @@ -28,10 +28,10 @@ export default async function Page({ loginName?: string, organization?: string, ) { - const recent = await getMostRecentCookieWithLoginname( + const recent = await getMostRecentCookieWithLoginname({ loginName, organization, - ); + }); return getSession(recent.id, recent.token).then((response) => { if (response?.session) { return response.session; @@ -40,7 +40,7 @@ export default async function Page({ } async function loadSessionById(sessionId: string, organization?: string) { - const recent = await getSessionCookieById(sessionId, organization); + const recent = await getSessionCookieById({ sessionId, organization }); return getSession(recent.id, recent.token).then((response) => { if (response?.session) { return response.session; diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 2bad137461..9927fda97a 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -7,7 +7,6 @@ import Alert from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; import PasswordForm from "@/ui/PasswordForm"; import UserAvatar from "@/ui/UserAvatar"; -import { getMostRecentCookieWithLoginname } from "@zitadel/next"; import { loadMostRecentSession } from "@zitadel/next"; export default async function Page({ diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index b924f5703c..c54208957e 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -5,7 +5,7 @@ import { getMostRecentCookieWithLoginname } from "@zitadel/next"; import { redirect } from "next/navigation"; async function loadSession(loginName: string, authRequestId?: string) { - const recent = await getMostRecentCookieWithLoginname(`${loginName}`); + const recent = await getMostRecentCookieWithLoginname({ loginName }); if (authRequestId) { return createCallback({ diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index bd9975e784..d42b5ac72b 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -31,10 +31,10 @@ export default async function Page({ loginName?: string, organization?: string, ) { - const recent = await getMostRecentCookieWithLoginname( + const recent = await getMostRecentCookieWithLoginname({ loginName, organization, - ); + }); return getSession(recent.id, recent.token).then((response) => { if (response?.session) { return response.session; @@ -43,7 +43,7 @@ export default async function Page({ } async function loadSessionById(sessionId: string, organization?: string) { - const recent = await getSessionCookieById(sessionId, organization); + const recent = await getSessionCookieById({ sessionId, organization }); return getSession(recent.id, recent.token).then((response) => { if (response?.session) { return response.session; diff --git a/apps/login/src/app/api/otp/set/route.ts b/apps/login/src/app/api/otp/set/route.ts index f7fa1d020c..a432e88705 100644 --- a/apps/login/src/app/api/otp/set/route.ts +++ b/apps/login/src/app/api/otp/set/route.ts @@ -16,7 +16,7 @@ export async function POST(request: NextRequest) { body; const recentPromise = sessionId - ? getSessionCookieById(sessionId).catch((error) => { + ? getSessionCookieById({ sessionId }).catch((error) => { return Promise.reject(error); }) : loginName diff --git a/apps/login/src/app/api/passkeys/route.ts b/apps/login/src/app/api/passkeys/route.ts index a9f135da2a..1583df298b 100644 --- a/apps/login/src/app/api/passkeys/route.ts +++ b/apps/login/src/app/api/passkeys/route.ts @@ -11,7 +11,7 @@ export async function POST(request: NextRequest) { if (body) { const { sessionId } = body; - const sessionCookie = await getSessionCookieById(sessionId); + const sessionCookie = await getSessionCookieById({ sessionId }); const session = await getSession(sessionCookie.id, sessionCookie.token); diff --git a/apps/login/src/app/api/passkeys/verify/route.ts b/apps/login/src/app/api/passkeys/verify/route.ts index 674d17cb0c..064ba6e77a 100644 --- a/apps/login/src/app/api/passkeys/verify/route.ts +++ b/apps/login/src/app/api/passkeys/verify/route.ts @@ -13,7 +13,7 @@ export async function POST(request: NextRequest) { device.vendor || device.model ? ", " : "" }${os.name}${os.name ? ", " : ""}${browser.name}`; } - const sessionCookie = await getSessionCookieById(sessionId); + const sessionCookie = await getSessionCookieById({ sessionId }); const session = await getSession(sessionCookie.id, sessionCookie.token); diff --git a/apps/login/src/app/api/session/route.ts b/apps/login/src/app/api/session/route.ts index 1a6a4329d5..4ef40511a5 100644 --- a/apps/login/src/app/api/session/route.ts +++ b/apps/login/src/app/api/session/route.ts @@ -165,9 +165,9 @@ export async function PUT(request: NextRequest) { */ export async function DELETE(request: NextRequest) { const { searchParams } = new URL(request.url); - const id = searchParams.get("id"); - if (id) { - const session = await getSessionCookieById(id); + const sessionId = searchParams.get("id"); + if (sessionId) { + const session = await getSessionCookieById({ sessionId }); return deleteSession(session.id, session.token) .then(() => { diff --git a/apps/login/src/app/api/u2f/route.ts b/apps/login/src/app/api/u2f/route.ts index 7789582c89..f8a646a981 100644 --- a/apps/login/src/app/api/u2f/route.ts +++ b/apps/login/src/app/api/u2f/route.ts @@ -12,7 +12,7 @@ export async function POST(request: NextRequest) { if (body) { const { sessionId } = body; - const sessionCookie = await getSessionCookieById(sessionId); + const sessionCookie = await getSessionCookieById({ sessionId }); const session = await getSession(sessionCookie.id, sessionCookie.token); diff --git a/apps/login/src/app/api/u2f/verify/route.ts b/apps/login/src/app/api/u2f/verify/route.ts index 6e8558d590..a842c8065c 100644 --- a/apps/login/src/app/api/u2f/verify/route.ts +++ b/apps/login/src/app/api/u2f/verify/route.ts @@ -15,7 +15,7 @@ export async function POST(request: NextRequest) { device.vendor || device.model ? ", " : "" }${os.name}${os.name ? ", " : ""}${browser.name}`; } - const sessionCookie = await getSessionCookieById(sessionId); + const sessionCookie = await getSessionCookieById({ sessionId }); const session = await getSession(sessionCookie.id, sessionCookie.token); diff --git a/apps/login/src/lib/server-actions.ts b/apps/login/src/lib/server-actions.ts index 14e1889509..3b879d5dd6 100644 --- a/apps/login/src/lib/server-actions.ts +++ b/apps/login/src/lib/server-actions.ts @@ -8,7 +8,7 @@ export async function verifyTOTP( loginName?: string, organization?: string, ) { - return getMostRecentCookieWithLoginname(loginName, organization) + return getMostRecentCookieWithLoginname({ loginName, organization }) .then((recent) => { return getSession(recent.id, recent.token).then((response) => { return { session: response?.session, token: recent.token }; diff --git a/packages/zitadel-next/src/utils/cookies.ts b/packages/zitadel-next/src/utils/cookies.ts index 1cc4eadea6..3eb1b27422 100644 --- a/packages/zitadel-next/src/utils/cookies.ts +++ b/packages/zitadel-next/src/utils/cookies.ts @@ -111,10 +111,10 @@ export async function getMostRecentSessionCookie(): Promise { } export async function getSessionCookieById({ - id, + sessionId, organization, }: { - id: string; + sessionId: string; organization?: string; }): Promise> { const cookiesList = cookies(); @@ -123,7 +123,9 @@ export async function getSessionCookieById({ if (stringifiedCookie?.value) { const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); - const found = sessions.find((s) => (organization ? s.organization === organization && s.id === id : s.id === id)); + const found = sessions.find((s) => + organization ? s.organization === organization && s.id === sessionId : s.id === sessionId, + ); if (found) { return found; } else { From 6cd07c70aac19bee08f187aba76818a03375e49d Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 8 Aug 2024 10:29:17 +0200 Subject: [PATCH 009/640] register button improvement --- apps/login/src/app/(login)/loginname/page.tsx | 20 ++++++------- apps/login/src/ui/SignInWithIDP.tsx | 4 --- apps/login/src/ui/UsernameForm.tsx | 28 +++++++++++-------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index ee2139aff4..7d04c3924b 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -51,16 +51,16 @@ export default async function Page({ organization={organization} submit={submit} allowRegister={!!loginSettings?.allowRegister} - /> - - {legal && identityProviders && process.env.ZITADEL_API_URL && ( - - )} + > + {legal && identityProviders && process.env.ZITADEL_API_URL && ( + + )} +
); diff --git a/apps/login/src/ui/SignInWithIDP.tsx b/apps/login/src/ui/SignInWithIDP.tsx index eee49e2572..55677301a7 100644 --- a/apps/login/src/ui/SignInWithIDP.tsx +++ b/apps/login/src/ui/SignInWithIDP.tsx @@ -143,10 +143,6 @@ export function SignInWithIDP({ {error}
)} -
- - -
); } diff --git a/apps/login/src/ui/UsernameForm.tsx b/apps/login/src/ui/UsernameForm.tsx index a470d77aec..96b719bdfa 100644 --- a/apps/login/src/ui/UsernameForm.tsx +++ b/apps/login/src/ui/UsernameForm.tsx @@ -11,6 +11,7 @@ import { LoginSettings, PasskeysType, } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import BackButton from "./BackButton"; type Inputs = { loginName: string; @@ -23,6 +24,7 @@ type Props = { organization?: string; submit: boolean; allowRegister: boolean; + children?: React.ReactNode; }; export default function UsernameForm({ @@ -32,6 +34,7 @@ export default function UsernameForm({ organization, submit, allowRegister, + children, }: Props) { const { register, handleSubmit, formState } = useForm({ mode: "onBlur", @@ -220,6 +223,16 @@ export default function UsernameForm({ {...register("loginName", { required: "This field is required" })} label="Loginname" /> + {allowRegister && ( + + )} {error && ( @@ -228,17 +241,10 @@ export default function UsernameForm({ )} -
- {allowRegister && ( - - )} +
{children}
+ +
+
- -
-
- Deploy your own on Vercel -
- - Deploy with Vercel - -
diff --git a/apps/login/src/ui/Avatar.tsx b/apps/login/src/ui/Avatar.tsx index bfa5d229ae..a6203d4b46 100644 --- a/apps/login/src/ui/Avatar.tsx +++ b/apps/login/src/ui/Avatar.tsx @@ -2,6 +2,7 @@ import { ColorShade, getColorHash } from "@/utils/colors"; import { useTheme } from "next-themes"; +import Image from "next/image"; interface AvatarProps { name: string | null | undefined; @@ -77,8 +78,11 @@ export function Avatar({ style={resolvedTheme === "light" ? avatarStyleLight : avatarStyleDark} > {imageUrl ? ( - avatar ) : ( From 8544291c80205d50121fd99544bfbf34e5072dda Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 12 Aug 2024 11:23:04 +0200 Subject: [PATCH 026/640] escape redirect on non prod --- apps/login/src/app/page.tsx | 4 +++- apps/login/src/ui/Avatar.tsx | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/login/src/app/page.tsx b/apps/login/src/app/page.tsx index 9c0ef93d28..8fe59006f4 100644 --- a/apps/login/src/app/page.tsx +++ b/apps/login/src/app/page.tsx @@ -2,5 +2,7 @@ import { redirect } from "next/navigation"; export default function Page() { // automatically redirect to loginname - redirect("/loginname"); + if (process.env.NODE_ENV === "production") { + redirect("/loginname"); + } } diff --git a/apps/login/src/ui/Avatar.tsx b/apps/login/src/ui/Avatar.tsx index a6203d4b46..e42e6017af 100644 --- a/apps/login/src/ui/Avatar.tsx +++ b/apps/login/src/ui/Avatar.tsx @@ -72,8 +72,8 @@ export function Avatar({ : size === "base" ? "w-[38px] h-[38px] font-bold" : size === "small" - ? "w-[32px] h-[32px] font-bold text-[13px]" - : "" + ? "!w-[32px] !h-[32px] font-bold text-[13px]" + : "w-12 h-12" }`} style={resolvedTheme === "light" ? avatarStyleLight : avatarStyleDark} > @@ -82,7 +82,7 @@ export function Avatar({ height={48} width={48} alt="avatar" - className="border border-divider-light dark:border-divider-dark rounded-full" + className="w-full h-full border border-divider-light dark:border-divider-dark rounded-full" src={imageUrl} /> ) : ( From 16ea8910d462e37a36d48b393aa16d128b4c1afe Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 12 Aug 2024 11:31:18 +0200 Subject: [PATCH 027/640] debug env --- apps/login/.env.integration | 3 ++- apps/login/src/app/page.tsx | 2 +- turbo.json | 4 +--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/login/.env.integration b/apps/login/.env.integration index 44e8f7b6bf..d258e2cb55 100644 --- a/apps/login/.env.integration +++ b/apps/login/.env.integration @@ -1 +1,2 @@ -ZITADEL_API_URL=http://localhost:22222 \ No newline at end of file +ZITADEL_API_URL=http://localhost:22222 +DEBUG=true \ No newline at end of file diff --git a/apps/login/src/app/page.tsx b/apps/login/src/app/page.tsx index 8fe59006f4..fa304faca6 100644 --- a/apps/login/src/app/page.tsx +++ b/apps/login/src/app/page.tsx @@ -2,7 +2,7 @@ import { redirect } from "next/navigation"; export default function Page() { // automatically redirect to loginname - if (process.env.NODE_ENV === "production") { + if (!process.env.DEBUG) { redirect("/loginname"); } } diff --git a/turbo.json b/turbo.json index 609e4bec6c..3c2ec89865 100644 --- a/turbo.json +++ b/turbo.json @@ -1,9 +1,7 @@ { "$schema": "https://turbo.build/schema.json", "ui": "tui", - "globalDependencies": [ - "**/.env.*local" - ], + "globalDependencies": ["**/.env.*local"], "globalEnv": [ "DEBUG", "ZITADEL_API_URL", From edad488401ca244d77bbbe4db934a1c61e9be8bd Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 12 Aug 2024 11:44:35 +0200 Subject: [PATCH 028/640] change condition --- apps/login/src/app/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/app/page.tsx b/apps/login/src/app/page.tsx index fa304faca6..f1fce50f90 100644 --- a/apps/login/src/app/page.tsx +++ b/apps/login/src/app/page.tsx @@ -2,7 +2,7 @@ import { redirect } from "next/navigation"; export default function Page() { // automatically redirect to loginname - if (!process.env.DEBUG) { + if (process.env.DEBUG !== "true") { redirect("/loginname"); } } From e488bd9ea6b813785c0492bece9eb3ea10270369 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 12 Aug 2024 12:19:24 +0200 Subject: [PATCH 029/640] fix: use branding colors across packages --- .../src/components/SignInWithAzureAD.tsx | 23 ++++--------------- .../src/components/SignInWithGithub.tsx | 13 +++-------- .../src/components/SignInWithGitlab.tsx | 20 ++++------------ .../src/components/SignInWithGoogle.tsx | 20 ++++------------ 4 files changed, 15 insertions(+), 61 deletions(-) diff --git a/packages/zitadel-react/src/components/SignInWithAzureAD.tsx b/packages/zitadel-react/src/components/SignInWithAzureAD.tsx index d9f7859bcf..f30bd58e54 100644 --- a/packages/zitadel-react/src/components/SignInWithAzureAD.tsx +++ b/packages/zitadel-react/src/components/SignInWithAzureAD.tsx @@ -3,38 +3,23 @@ import { ReactNode, forwardRef } from "react"; import { SignInWithIdentityProviderProps } from "./SignInWith"; -export const SignInWithAzureAD = forwardRef< - HTMLButtonElement, - SignInWithIdentityProviderProps ->( +export const SignInWithAzureAD = forwardRef( ({ children, className = "", name = "", ...props }, ref): ReactNode => ( ), ); diff --git a/packages/zitadel-react/src/components/SignInWithGithub.tsx b/packages/zitadel-react/src/components/SignInWithGithub.tsx index d04f9b5fc0..28a1982923 100644 --- a/packages/zitadel-react/src/components/SignInWithGithub.tsx +++ b/packages/zitadel-react/src/components/SignInWithGithub.tsx @@ -3,15 +3,12 @@ import { ReactNode, forwardRef } from "react"; import { SignInWithIdentityProviderProps } from "./SignInWith"; -export const SignInWithGithub = forwardRef< - HTMLButtonElement, - SignInWithIdentityProviderProps ->( +export const SignInWithGithub = forwardRef( ({ children, className = "", name = "", ...props }, ref): ReactNode => ( ), ); diff --git a/packages/zitadel-react/src/components/SignInWithGitlab.tsx b/packages/zitadel-react/src/components/SignInWithGitlab.tsx index 62b4d04c34..6ec0941f87 100644 --- a/packages/zitadel-react/src/components/SignInWithGitlab.tsx +++ b/packages/zitadel-react/src/components/SignInWithGitlab.tsx @@ -1,24 +1,16 @@ import { ReactNode, forwardRef } from "react"; import { SignInWithIdentityProviderProps } from "./SignInWith"; -export const SignInWithGitlab = forwardRef< - HTMLButtonElement, - SignInWithIdentityProviderProps ->( +export const SignInWithGitlab = forwardRef( ({ children, className = "", name = "", ...props }, ref): ReactNode => ( ), ); diff --git a/packages/zitadel-react/src/components/SignInWithGoogle.tsx b/packages/zitadel-react/src/components/SignInWithGoogle.tsx index e3dc0c8596..d3d9d0f8b7 100644 --- a/packages/zitadel-react/src/components/SignInWithGoogle.tsx +++ b/packages/zitadel-react/src/components/SignInWithGoogle.tsx @@ -1,24 +1,16 @@ import { ReactNode, forwardRef } from "react"; import { SignInWithIdentityProviderProps } from "./SignInWith"; -export const SignInWithGoogle = forwardRef< - HTMLButtonElement, - SignInWithIdentityProviderProps ->( +export const SignInWithGoogle = forwardRef( ({ children, className = "", name = "", ...props }, ref): ReactNode => ( ), ); From 222f141e992cbb2a9261962405398983cc68eb0a Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 13 Aug 2024 10:14:42 +0200 Subject: [PATCH 030/640] fix: use login app namespace for component theming --- .../zitadel-react/src/components/SignInWithAzureAD.tsx | 8 ++------ .../zitadel-react/src/components/SignInWithGithub.tsx | 8 ++------ .../zitadel-react/src/components/SignInWithGitlab.tsx | 8 ++------ .../zitadel-react/src/components/SignInWithGoogle.tsx | 8 ++------ packages/zitadel-react/src/components/classes.ts | 2 ++ 5 files changed, 10 insertions(+), 24 deletions(-) create mode 100644 packages/zitadel-react/src/components/classes.ts diff --git a/packages/zitadel-react/src/components/SignInWithAzureAD.tsx b/packages/zitadel-react/src/components/SignInWithAzureAD.tsx index f30bd58e54..38acc64591 100644 --- a/packages/zitadel-react/src/components/SignInWithAzureAD.tsx +++ b/packages/zitadel-react/src/components/SignInWithAzureAD.tsx @@ -2,15 +2,11 @@ import { ReactNode, forwardRef } from "react"; import { SignInWithIdentityProviderProps } from "./SignInWith"; +import { IdpButtonClasses } from "./classes"; export const SignInWithAzureAD = forwardRef( ({ children, className = "", name = "", ...props }, ref): ReactNode => ( - + ), +); + +SignInWithAzureAD.displayName = "SignInWithAzureAD"; diff --git a/packages/zitadel-react/src/components/SignInWithGithub.tsx b/apps/login/src/ui/idps/SignInWithGithub.tsx similarity index 81% rename from packages/zitadel-react/src/components/SignInWithGithub.tsx rename to apps/login/src/ui/idps/SignInWithGithub.tsx index 435459def5..c0d1130b60 100644 --- a/packages/zitadel-react/src/components/SignInWithGithub.tsx +++ b/apps/login/src/ui/idps/SignInWithGithub.tsx @@ -1,20 +1,27 @@ "use client"; import { ReactNode, forwardRef } from "react"; -import { SignInWithIdentityProviderProps } from "./SignInWith"; -import { IdpButtonClasses } from "./classes"; +import { IdpButtonClasses, SignInWithIdentityProviderProps } from "./classes"; -export const SignInWithGithub = forwardRef( +export const SignInWithGithub = forwardRef< + HTMLButtonElement, + SignInWithIdentityProviderProps +>( ({ children, className = "", name = "", ...props }, ref): ReactNode => ( - ), ); diff --git a/packages/zitadel-react/src/components/SignInWithGitlab.test.tsx b/apps/login/src/ui/idps/SignInWithGitlab.test.tsx similarity index 100% rename from packages/zitadel-react/src/components/SignInWithGitlab.test.tsx rename to apps/login/src/ui/idps/SignInWithGitlab.test.tsx diff --git a/packages/zitadel-react/src/components/SignInWithGitlab.tsx b/apps/login/src/ui/idps/SignInWithGitlab.tsx similarity index 64% rename from packages/zitadel-react/src/components/SignInWithGitlab.tsx rename to apps/login/src/ui/idps/SignInWithGitlab.tsx index ffb1d09c49..4fcd5bf049 100644 --- a/packages/zitadel-react/src/components/SignInWithGitlab.tsx +++ b/apps/login/src/ui/idps/SignInWithGitlab.tsx @@ -1,12 +1,26 @@ -import { ReactNode, forwardRef } from "react"; -import { SignInWithIdentityProviderProps } from "./SignInWith"; -import { IdpButtonClasses } from "./classes"; +"use client"; -export const SignInWithGitlab = forwardRef( +import { ReactNode, forwardRef } from "react"; +import { IdpButtonClasses, SignInWithIdentityProviderProps } from "./classes"; + +export const SignInWithGitlab = forwardRef< + HTMLButtonElement, + SignInWithIdentityProviderProps +>( ({ children, className = "", name = "", ...props }, ref): ReactNode => ( - ), ); diff --git a/packages/zitadel-react/src/components/SignInWithGoogle.test.tsx b/apps/login/src/ui/idps/SignInWithGoogle.test.tsx similarity index 100% rename from packages/zitadel-react/src/components/SignInWithGoogle.test.tsx rename to apps/login/src/ui/idps/SignInWithGoogle.test.tsx diff --git a/packages/zitadel-react/src/components/SignInWithGoogle.tsx b/apps/login/src/ui/idps/SignInWithGoogle.tsx similarity index 77% rename from packages/zitadel-react/src/components/SignInWithGoogle.tsx rename to apps/login/src/ui/idps/SignInWithGoogle.tsx index af2dc40d26..94caa3759a 100644 --- a/packages/zitadel-react/src/components/SignInWithGoogle.tsx +++ b/apps/login/src/ui/idps/SignInWithGoogle.tsx @@ -1,12 +1,26 @@ -import { ReactNode, forwardRef } from "react"; -import { SignInWithIdentityProviderProps } from "./SignInWith"; -import { IdpButtonClasses } from "./classes"; +"use client"; -export const SignInWithGoogle = forwardRef( +import { ReactNode, forwardRef } from "react"; +import { IdpButtonClasses, SignInWithIdentityProviderProps } from "./classes"; + +export const SignInWithGoogle = forwardRef< + HTMLButtonElement, + SignInWithIdentityProviderProps +>( ({ children, className = "", name = "", ...props }, ref): ReactNode => ( - ), ); diff --git a/apps/login/src/ui/idps/classes.tsx b/apps/login/src/ui/idps/classes.tsx new file mode 100644 index 0000000000..f0bf32c1ed --- /dev/null +++ b/apps/login/src/ui/idps/classes.tsx @@ -0,0 +1,17 @@ +import { ButtonHTMLAttributes, DetailedHTMLProps } from "react"; + +export const IdpButtonClasses = + "transition-all w-full cursor-pointer flex flex-row items-center bg-background-light-400 text-text-light-500 dark:bg-background-dark-500 dark:text-text-dark-500 border border-divider-light hover:border-black dark:border-divider-dark hover:dark:border-white focus:border-primary-light-500 focus:dark:border-primary-dark-500 outline-none rounded-md px-4 text-sm"; + +export interface SignInWithIDPProps { + children?: React.ReactNode; + orgId?: string; +} + +export type SignInWithIdentityProviderProps = DetailedHTMLProps< + ButtonHTMLAttributes, + HTMLButtonElement +> & { + name?: string; + e2e?: string; +}; diff --git a/apps/login/src/utils/session.ts b/apps/login/src/utils/session.ts index a192b75839..2d4e9f52b3 100644 --- a/apps/login/src/utils/session.ts +++ b/apps/login/src/utils/session.ts @@ -6,7 +6,6 @@ import { getSession, setSession, } from "@/lib/zitadel"; -import { addSessionToCookie, updateSessionCookie } from "@zitadel/next"; import { Challenges, RequestChallenges, @@ -18,6 +17,7 @@ import { } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { timestampDate, toDate } from "@zitadel/client"; import { create } from "@zitadel/client"; +import { addSessionToCookie, updateSessionCookie } from "@/lib/cookies"; type CustomCookieData = { id: string; diff --git a/apps/login/turbo.json b/apps/login/turbo.json index 20ad9b2492..8d03cac2be 100644 --- a/apps/login/turbo.json +++ b/apps/login/turbo.json @@ -6,36 +6,16 @@ "dependsOn": ["^build"] }, "test": { - "dependsOn": [ - "@zitadel/node#build", - "@zitadel/client#build", - "@zitadel/react#build", - "@zitadel/next#build" - ] + "dependsOn": ["@zitadel/node#build", "@zitadel/client#build"] }, "test:integration": { - "dependsOn": [ - "@zitadel/node#build", - "@zitadel/client#build", - "@zitadel/react#build", - "@zitadel/next#build" - ] + "dependsOn": ["@zitadel/node#build", "@zitadel/client#build"] }, "test:unit": { - "dependsOn": [ - "@zitadel/node#build", - "@zitadel/client#build", - "@zitadel/react#build", - "@zitadel/next#build" - ] + "dependsOn": ["@zitadel/node#build", "@zitadel/client#build"] }, "test:watch": { - "dependsOn": [ - "@zitadel/node#build", - "@zitadel/client#build", - "@zitadel/react#build", - "@zitadel/next#build" - ] + "dependsOn": ["@zitadel/node#build", "@zitadel/client#build"] } } } diff --git a/packages/zitadel-next/.eslintrc.cjs b/packages/zitadel-next/.eslintrc.cjs deleted file mode 100644 index 8e247ab3c2..0000000000 --- a/packages/zitadel-next/.eslintrc.cjs +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - root: true, - extends: ["zitadel"], -}; diff --git a/packages/zitadel-next/package.json b/packages/zitadel-next/package.json deleted file mode 100644 index 4bd3b41373..0000000000 --- a/packages/zitadel-next/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@zitadel/next", - "version": "0.0.0", - "sideEffects": false, - "license": "MIT", - "type": "module", - "exports": { - ".": { - "import": "./dist/index.js", - "types": "./dist/index.d.ts" - } - }, - "files": [ - "dist/**" - ], - "publishConfig": { - "access": "public" - }, - "scripts": { - "build": "tsup", - "test": "pnpm test:unit", - "test:watch": "pnpm test:unit:watch", - "test:unit": "vitest", - "test:unit:watch": "vitest --watch", - "dev": "tsup --watch", - "lint": "eslint \"src/**/*.ts*\"", - "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist" - }, - "peerDependencies": { - "@zitadel/node": "workspace:*", - "@zitadel/react": "workspace:*", - "@zitadel/client": "workspace:*", - "@zitadel/proto": "workspace:*", - "next": "^14.2.5", - "react": "^18.3.1" - }, - "dependencies": { - "next": "^14.2.5" - }, - "devDependencies": { - "@types/react": "^18.3.3", - "@zitadel/tsconfig": "workspace:*", - "eslint-config-zitadel": "workspace:*", - "postcss": "8.4.41", - "tailwindcss": "3.4.9", - "zitadel-tailwind-config": "workspace:*" - } -} diff --git a/packages/zitadel-next/postcss.config.cjs b/packages/zitadel-next/postcss.config.cjs deleted file mode 100644 index 07aa434b2b..0000000000 --- a/packages/zitadel-next/postcss.config.cjs +++ /dev/null @@ -1,9 +0,0 @@ -// If you want to use other PostCSS plugins, see the following: -// https://tailwindcss.com/docs/using-with-preprocessors - -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/packages/zitadel-next/src/components/ZitadelNextProvider.tsx b/packages/zitadel-next/src/components/ZitadelNextProvider.tsx deleted file mode 100644 index 1ff78ee76e..0000000000 --- a/packages/zitadel-next/src/components/ZitadelNextProvider.tsx +++ /dev/null @@ -1,8 +0,0 @@ -export type ZitadelNextProps = { - dark: boolean; - children: React.ReactNode; -}; - -export function ZitadelNextProvider({ dark, children }: ZitadelNextProps) { - return
{children}
; -} diff --git a/packages/zitadel-next/src/index.tsx b/packages/zitadel-next/src/index.tsx deleted file mode 100644 index 2ff89e0ddd..0000000000 --- a/packages/zitadel-next/src/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -// import "./styles.css"; - -export { ZitadelNextProvider, type ZitadelNextProps } from "./components/ZitadelNextProvider"; -export * from "./utils/cookies"; -export { loadMostRecentSession } from "./utils/session"; diff --git a/packages/zitadel-next/src/styles.css b/packages/zitadel-next/src/styles.css deleted file mode 100644 index b5c61c9567..0000000000 --- a/packages/zitadel-next/src/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/packages/zitadel-next/tailwind.config.mjs b/packages/zitadel-next/tailwind.config.mjs deleted file mode 100644 index 666dc993a2..0000000000 --- a/packages/zitadel-next/tailwind.config.mjs +++ /dev/null @@ -1,9 +0,0 @@ -const sharedConfig = require("zitadel-tailwind-config/tailwind.config.mjs"); - -/** @type {import('tailwindcss').Config} */ -module.exports = { - presets: [sharedConfig], - prefix: "ztdl-next-", - darkMode: "class", - content: [`src/**/*.{js,ts,jsx,tsx}`], -}; diff --git a/packages/zitadel-next/tsconfig.json b/packages/zitadel-next/tsconfig.json deleted file mode 100644 index 460eef71f5..0000000000 --- a/packages/zitadel-next/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "@zitadel/tsconfig/react-library.json", - "include": ["."], - "exclude": ["dist", "build", "node_modules"] -} diff --git a/packages/zitadel-next/tsup.config.ts b/packages/zitadel-next/tsup.config.ts deleted file mode 100644 index 8a2f312e51..0000000000 --- a/packages/zitadel-next/tsup.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig, Options } from "tsup"; - -export default defineConfig((options: Options) => ({ - treeshake: true, - splitting: true, - publicDir: true, - entry: ["src/**/*.tsx"], - format: ["esm"], - dts: true, - minify: true, - clean: true, - external: ["react"], - ...options, -})); diff --git a/packages/zitadel-next/turbo.json b/packages/zitadel-next/turbo.json deleted file mode 100644 index 3e82b759f9..0000000000 --- a/packages/zitadel-next/turbo.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": ["//"], - "tasks": { - "build": { - "outputs": ["dist/**"], - "dependsOn": ["@zitadel/client#build", "@zitadel/react#build"] - } - } -} diff --git a/packages/zitadel-react/.eslintrc.cjs b/packages/zitadel-react/.eslintrc.cjs deleted file mode 100644 index 8e247ab3c2..0000000000 --- a/packages/zitadel-react/.eslintrc.cjs +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - root: true, - extends: ["zitadel"], -}; diff --git a/packages/zitadel-react/README.md b/packages/zitadel-react/README.md deleted file mode 100644 index 0d2b62c8d6..0000000000 --- a/packages/zitadel-react/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# How to use - -### Install - -```sh -npm install @zitadel/react -``` - -or - -```sh -yarn add @zitadel/react -``` - -### Import styles file - -To get the styles, import them in `_app.tsx` or global styling file - -``` -import "@zitadel/react/styles.css"; -``` - -### Setup Dark mode - -to set dark theme, wrap your components in a `ui-dark` class. - -### Use components - -```tsx -import { SignInWithGoogle } from "@zitadel/react"; - -export default function IdentityProviders() { - return ( -
- -
- ); -} -``` diff --git a/packages/zitadel-react/package.json b/packages/zitadel-react/package.json deleted file mode 100644 index 1392457393..0000000000 --- a/packages/zitadel-react/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@zitadel/react", - "version": "0.0.0", - "types": "./dist/index.d.ts", - "sideEffects": [ - "**/*.css" - ], - "license": "MIT", - "exports": { - ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.cjs" - }, - "./styles.css": "./dist/index.css", - "./assets/*": "./dist/assets/*" - }, - "publishConfig": { - "access": "public" - }, - "scripts": { - "build": "tsup", - "test": "pnpm test:unit", - "test:watch": "pnpm test:unit:watch", - "test:unit": "vitest", - "test:unit:watch": "vitest --watch", - "dev": "tsup --watch", - "lint": "eslint \"src/**/*.ts*\"", - "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist", - "copy-files": "cp -R ./src/public/ ./dist/" - }, - "peerDependencies": { - "react": "^18.3.1" - }, - "devDependencies": { - "@testing-library/jest-dom": "^6.4.5", - "@testing-library/react": "^16.0.0", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@zitadel/tsconfig": "workspace:*", - "autoprefixer": "10.4.20", - "eslint-config-zitadel": "workspace:*", - "jsdom": "^24.0.0", - "postcss": "8.4.41", - "sass": "^1.77.1", - "tailwindcss": "3.4.9", - "zitadel-tailwind-config": "workspace:*" - } -} diff --git a/packages/zitadel-react/postcss.config.cjs b/packages/zitadel-react/postcss.config.cjs deleted file mode 100644 index 07aa434b2b..0000000000 --- a/packages/zitadel-react/postcss.config.cjs +++ /dev/null @@ -1,9 +0,0 @@ -// If you want to use other PostCSS plugins, see the following: -// https://tailwindcss.com/docs/using-with-preprocessors - -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/packages/zitadel-react/src/components/SignInWith.tsx b/packages/zitadel-react/src/components/SignInWith.tsx deleted file mode 100644 index 933d8577a5..0000000000 --- a/packages/zitadel-react/src/components/SignInWith.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { ButtonHTMLAttributes, DetailedHTMLProps } from "react"; - -export type SignInWithIdentityProviderProps = DetailedHTMLProps< - ButtonHTMLAttributes, - HTMLButtonElement -> & { - name?: string; - e2e?: string; -}; diff --git a/packages/zitadel-react/src/components/SignInWithAzureAD.tsx b/packages/zitadel-react/src/components/SignInWithAzureAD.tsx deleted file mode 100644 index 38acc64591..0000000000 --- a/packages/zitadel-react/src/components/SignInWithAzureAD.tsx +++ /dev/null @@ -1,23 +0,0 @@ -"use client"; - -import { ReactNode, forwardRef } from "react"; -import { SignInWithIdentityProviderProps } from "./SignInWith"; -import { IdpButtonClasses } from "./classes"; - -export const SignInWithAzureAD = forwardRef( - ({ children, className = "", name = "", ...props }, ref): ReactNode => ( - - ), -); - -SignInWithAzureAD.displayName = "SignInWithAzureAD"; diff --git a/packages/zitadel-react/src/components/SignInWithIDP.tsx b/packages/zitadel-react/src/components/SignInWithIDP.tsx deleted file mode 100644 index eddcda928c..0000000000 --- a/packages/zitadel-react/src/components/SignInWithIDP.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from "react"; - -export interface SignInWithIDPProps { - children?: React.ReactNode; - orgId?: string; -} - -export function SignInWithIDP(props: SignInWithIDPProps) { - return ( -
-
- {props.children} -
- ); -} - -SignInWithIDP.displayName = "SignInWithIDP"; diff --git a/packages/zitadel-react/src/components/ZitadelReactProvider.tsx b/packages/zitadel-react/src/components/ZitadelReactProvider.tsx deleted file mode 100644 index 34226e5b29..0000000000 --- a/packages/zitadel-react/src/components/ZitadelReactProvider.tsx +++ /dev/null @@ -1,8 +0,0 @@ -export type ZitadelReactProps = { - dark: boolean; - children: React.ReactNode; -}; - -export function ZitadelReactProvider({ dark, children }: ZitadelReactProps) { - return
{children}
; -} diff --git a/packages/zitadel-react/src/components/classes.ts b/packages/zitadel-react/src/components/classes.ts deleted file mode 100644 index 11216a561c..0000000000 --- a/packages/zitadel-react/src/components/classes.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const IdpButtonClasses = - "transition-all ztdl-w-full ztdl-cursor-pointer ztdl-flex ztdl-flex-row ztdl-items-center bg-background-light-400 text-text-light-500 dark:bg-background-dark-500 dark:text-text-dark-500 border border-divider-light hover:border-black dark:border-divider-dark hover:dark:border-white focus:border-primary-light-500 focus:dark:border-primary-dark-500 outline-none rounded-md px-4 text-sm"; diff --git a/packages/zitadel-react/src/index.tsx b/packages/zitadel-react/src/index.tsx deleted file mode 100644 index e65655d6c8..0000000000 --- a/packages/zitadel-react/src/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import "./styles.css"; - -export { SignInWithGoogle } from "./components/SignInWithGoogle"; - -export { SignInWithGitlab } from "./components/SignInWithGitlab"; - -export { SignInWithAzureAD } from "./components/SignInWithAzureAD"; - -export { SignInWithGithub } from "./components/SignInWithGithub"; - -export { ZitadelReactProvider, type ZitadelReactProps } from "./components/ZitadelReactProvider"; - -export { SignInWithIDP, type SignInWithIDPProps } from "./components/SignInWithIDP"; diff --git a/packages/zitadel-react/src/styles.css b/packages/zitadel-react/src/styles.css deleted file mode 100644 index b5c61c9567..0000000000 --- a/packages/zitadel-react/src/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/packages/zitadel-react/tailwind.config.mjs b/packages/zitadel-react/tailwind.config.mjs deleted file mode 100644 index 8f267f9274..0000000000 --- a/packages/zitadel-react/tailwind.config.mjs +++ /dev/null @@ -1,9 +0,0 @@ -const sharedConfig = require("zitadel-tailwind-config/tailwind.config.mjs"); - -/** @type {import('tailwindcss').Config} */ -module.exports = { - presets: [sharedConfig], - prefix: "ztdl-", - darkMode: "class", - content: [`src/**/*.{js,ts,jsx,tsx}`], -}; diff --git a/packages/zitadel-react/tsconfig.json b/packages/zitadel-react/tsconfig.json deleted file mode 100644 index 2200a65a4b..0000000000 --- a/packages/zitadel-react/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "@zitadel/tsconfig/react-library.json", - "include": ["."], - "exclude": ["dist", "build", "node_modules"], - "compilerOptions": { - "types": ["@testing-library/jest-dom"] - } -} diff --git a/packages/zitadel-react/tsup.config.ts b/packages/zitadel-react/tsup.config.ts deleted file mode 100644 index 730147e6c1..0000000000 --- a/packages/zitadel-react/tsup.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig, Options } from "tsup"; - -export default defineConfig((options: Options) => ({ - entry: ["src/index.tsx"], - format: ["esm", "cjs"], - dts: true, - external: ["react"], - ...options, -})); diff --git a/packages/zitadel-react/turbo.json b/packages/zitadel-react/turbo.json deleted file mode 100644 index 52e8c763f0..0000000000 --- a/packages/zitadel-react/turbo.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": [ - "//" - ], - "tasks": { - "build": { - "outputs": [ - "dist/**" - ] - } - } -} diff --git a/packages/zitadel-react/vitest.config.mts b/packages/zitadel-react/vitest.config.mts deleted file mode 100644 index a0ee71ace8..0000000000 --- a/packages/zitadel-react/vitest.config.mts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from "vitest/config"; -import react from "@vitejs/plugin-react"; - -export default defineConfig({ - plugins: [react()], - test: { - include: ['src/**/*.test.ts', 'src/**/*.test.tsx'], - environment: "jsdom", - setupFiles: ["@testing-library/jest-dom/vitest"], - }, -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 244d652697..fce6beabbc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,18 +62,12 @@ importers: '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client - '@zitadel/next': - specifier: workspace:* - version: link:../../packages/zitadel-next '@zitadel/node': specifier: workspace:* version: link:../../packages/zitadel-node '@zitadel/proto': specifier: workspace:* version: link:../../packages/zitadel-proto - '@zitadel/react': - specifier: workspace:* - version: link:../../packages/zitadel-react clsx: specifier: 1.2.1 version: 1.2.1 @@ -118,7 +112,7 @@ importers: specifier: ^6.4.5 version: 6.5.0 '@testing-library/react': - specifier: ^16.0.0 + specifier: ^16.0.1 version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) '@types/ms': specifier: 0.7.34 @@ -142,7 +136,7 @@ importers: specifier: 1.0.0 version: 1.0.0 '@zitadel/prettier-config': - specifier: workspace:* + specifier: workspace:^ version: link:../../packages/zitadel-prettier-config '@zitadel/tsconfig': specifier: workspace:* @@ -239,46 +233,6 @@ importers: specifier: workspace:* version: link:../eslint-config-zitadel - packages/zitadel-next: - dependencies: - '@zitadel/client': - specifier: workspace:* - version: link:../zitadel-client - '@zitadel/node': - specifier: workspace:* - version: link:../zitadel-node - '@zitadel/proto': - specifier: workspace:* - version: link:../zitadel-proto - '@zitadel/react': - specifier: workspace:* - version: link:../zitadel-react - next: - specifier: ^14.2.5 - version: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8) - react: - specifier: ^18.3.1 - version: 18.3.1 - devDependencies: - '@types/react': - specifier: ^18.3.3 - version: 18.3.3 - '@zitadel/tsconfig': - specifier: workspace:* - version: link:../zitadel-tsconfig - eslint-config-zitadel: - specifier: workspace:* - version: link:../eslint-config-zitadel - postcss: - specifier: 8.4.41 - version: 8.4.41 - tailwindcss: - specifier: 3.4.9 - version: 3.4.9 - zitadel-tailwind-config: - specifier: workspace:* - version: link:../zitadel-tailwind-config - packages/zitadel-node: dependencies: '@connectrpc/connect-node': @@ -316,49 +270,6 @@ importers: specifier: ^1.36.0 version: 1.39.0 - packages/zitadel-react: - dependencies: - react: - specifier: ^18.3.1 - version: 18.3.1 - devDependencies: - '@testing-library/jest-dom': - specifier: ^6.4.5 - version: 6.5.0 - '@testing-library/react': - specifier: ^16.0.0 - version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) - '@types/react': - specifier: ^18.3.3 - version: 18.3.3 - '@types/react-dom': - specifier: ^18.3.0 - version: 18.3.0 - '@zitadel/tsconfig': - specifier: workspace:* - version: link:../zitadel-tsconfig - autoprefixer: - specifier: 10.4.20 - version: 10.4.20(postcss@8.4.41) - eslint-config-zitadel: - specifier: workspace:* - version: link:../eslint-config-zitadel - jsdom: - specifier: ^24.0.0 - version: 24.1.3 - postcss: - specifier: 8.4.41 - version: 8.4.41 - sass: - specifier: ^1.77.1 - version: 1.77.8 - tailwindcss: - specifier: 3.4.9 - version: 3.4.9 - zitadel-tailwind-config: - specifier: workspace:* - version: link:../zitadel-tailwind-config - packages/zitadel-tailwind-config: devDependencies: '@tailwindcss/forms': @@ -2189,15 +2100,6 @@ packages: - supports-color dev: true - /agent-base@7.1.1: - resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} - engines: {node: '>= 14'} - dependencies: - debug: 4.3.6(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - dev: true - /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -2914,13 +2816,6 @@ packages: engines: {node: '>=4'} hasBin: true - /cssstyle@4.0.1: - resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} - engines: {node: '>=18'} - dependencies: - rrweb-cssom: 0.6.0 - dev: true - /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} dev: true @@ -2986,14 +2881,6 @@ packages: assert-plus: 1.0.0 dev: true - /data-urls@5.0.0: - resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} - engines: {node: '>=18'} - dependencies: - whatwg-mimetype: 4.0.0 - whatwg-url: 14.0.0 - dev: true - /data-view-buffer@1.0.1: resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} @@ -3086,10 +2973,6 @@ packages: engines: {node: '>=10'} dev: true - /decimal.js@10.4.3: - resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} - dev: true - /deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -3285,11 +3168,6 @@ packages: strip-ansi: 6.0.1 dev: true - /entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - dev: true - /env-cmd@10.1.0: resolution: {integrity: sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==} engines: {node: '>=8.0.0'} @@ -4400,23 +4278,6 @@ packages: lru-cache: 6.0.0 dev: true - /html-encoding-sniffer@4.0.0: - resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} - engines: {node: '>=18'} - dependencies: - whatwg-encoding: 3.1.1 - dev: true - - /http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - dependencies: - agent-base: 7.1.1 - debug: 4.3.6(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - dev: true - /http-signature@1.3.6: resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==} engines: {node: '>=0.10'} @@ -4436,16 +4297,6 @@ packages: - supports-color dev: true - /https-proxy-agent@7.0.5: - resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} - engines: {node: '>= 14'} - dependencies: - agent-base: 7.1.1 - debug: 4.3.6(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - dev: true - /human-id@1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} dev: true @@ -4472,13 +4323,6 @@ packages: safer-buffer: 2.1.2 dev: true - /iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 - dev: true - /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true @@ -4711,10 +4555,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - dev: true - /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -4887,42 +4727,6 @@ packages: resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} dev: true - /jsdom@24.1.3: - resolution: {integrity: sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==} - engines: {node: '>=18'} - peerDependencies: - canvas: ^2.11.2 - peerDependenciesMeta: - canvas: - optional: true - dependencies: - cssstyle: 4.0.1 - data-urls: 5.0.0 - decimal.js: 10.4.3 - form-data: 4.0.0 - html-encoding-sniffer: 4.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.12 - parse5: 7.1.2 - rrweb-cssom: 0.7.1 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 4.1.4 - w3c-xmlserializer: 5.0.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 3.1.1 - whatwg-mimetype: 4.0.0 - whatwg-url: 14.0.0 - ws: 8.18.0 - xml-name-validator: 5.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: true - /jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} @@ -5562,10 +5366,6 @@ packages: set-blocking: 2.0.0 dev: true - /nwsapi@2.2.12: - resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==} - dev: true - /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -5764,12 +5564,6 @@ packages: lines-and-columns: 1.2.4 dev: true - /parse5@7.1.2: - resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} - dependencies: - entities: 4.5.0 - dev: true - /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -6395,14 +6189,6 @@ packages: fsevents: 2.3.3 dev: true - /rrweb-cssom@0.6.0: - resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} - dev: true - - /rrweb-cssom@0.7.1: - resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} - dev: true - /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -6450,13 +6236,6 @@ packages: immutable: 4.3.7 source-map-js: 1.2.0 - /saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} - dependencies: - xmlchars: 2.2.0 - dev: true - /scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} dependencies: @@ -6912,10 +6691,6 @@ packages: use-sync-external-store: 1.2.2(react@18.3.1) dev: false - /symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - dev: true - /tailwindcss@3.4.9: resolution: {integrity: sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==} engines: {node: '>=14.0.0'} @@ -7064,13 +6839,6 @@ packages: punycode: 2.3.1 dev: true - /tr46@5.0.0: - resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} - engines: {node: '>=18'} - dependencies: - punycode: 2.3.1 - dev: true - /tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -7556,13 +7324,6 @@ packages: - terser dev: true - /w3c-xmlserializer@5.0.0: - resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} - engines: {node: '>=18'} - dependencies: - xml-name-validator: 5.0.0 - dev: true - /wait-on@7.2.0(debug@4.3.6): resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} engines: {node: '>=12.0.0'} @@ -7585,31 +7346,6 @@ packages: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} dev: true - /webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - dev: true - - /whatwg-encoding@3.1.1: - resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} - engines: {node: '>=18'} - dependencies: - iconv-lite: 0.6.3 - dev: true - - /whatwg-mimetype@4.0.0: - resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} - engines: {node: '>=18'} - dev: true - - /whatwg-url@14.0.0: - resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} - engines: {node: '>=18'} - dependencies: - tr46: 5.0.0 - webidl-conversions: 7.0.0 - dev: true - /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: @@ -7752,28 +7488,6 @@ packages: /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - /ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: true - - /xml-name-validator@5.0.0: - resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} - engines: {node: '>=18'} - dev: true - - /xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - dev: true - /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} From 5c0bd8777a881c7f82d06ae8a667764b287d2977 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 10:18:15 +0200 Subject: [PATCH 107/640] log session response --- apps/login/src/ui/LoginPasskey.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/login/src/ui/LoginPasskey.tsx b/apps/login/src/ui/LoginPasskey.tsx index 4c6dc12296..47d61cf86a 100644 --- a/apps/login/src/ui/LoginPasskey.tsx +++ b/apps/login/src/ui/LoginPasskey.tsx @@ -47,6 +47,7 @@ export default function LoginPasskey({ setLoading(true); updateSessionForChallenge() .then((response) => { + console.log(response); const pK = response?.challenges?.webAuthN?.publicKeyCredentialRequestOptions ?.publicKey; From ecd49ee93867951690749a40edc8a4a42ab15f69 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 10:27:31 +0200 Subject: [PATCH 108/640] cleanup session update for otp --- apps/login/src/lib/server/session.ts | 50 ++++++++++++++++------------ apps/login/src/ui/LoginPasskey.tsx | 1 - 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index b3f490ef1a..083e475e06 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -112,31 +112,37 @@ export async function updateSession(options: UpdateSessionCommand) { const recent = await sessionPromise; - if (recent && challenges && (!challenges.otpEmail || !challenges.otpSms)) { - const sessionResponse = await getSession(recent.id, recent.token); + // if ( + // (recent && + // challenges && + // challenges.otpEmail && + // !challenges.otpEmail?.deliveryType) || + // (challenges?.otpSms && !challenges.otpSms.returnCode) + // ) { + // const sessionResponse = await getSession(recent.id, recent.token); - if (sessionResponse && sessionResponse?.session?.factors?.user?.id) { - const userResponse = await getUserByID( - sessionResponse.session.factors.user.id, - ); - const humanUser = - userResponse.user?.type.case === "human" - ? userResponse.user.type.value - : undefined; + // if (sessionResponse && sessionResponse?.session?.factors?.user?.id) { + // const userResponse = await getUserByID( + // sessionResponse.session.factors.user.id, + // ); + // const humanUser = + // userResponse.user?.type.case === "human" + // ? userResponse.user.type.value + // : undefined; - if (!challenges.otpEmail && humanUser?.email?.email) { - challenges = create(RequestChallengesSchema, { - otpEmail: { deliveryType: { case: "sendCode", value: {} } }, - }); - } + // if (!challenges.otpEmail && humanUser?.email?.email) { + // challenges = create(RequestChallengesSchema, { + // otpEmail: { deliveryType: { case: "sendCode", value: {} } }, + // }); + // } - if (!challenges.otpEmail && humanUser?.email?.email) { - challenges = create(RequestChallengesSchema, { - otpSms: { returnCode: true }, - }); - } - } - } + // if (!challenges.otpEmail && humanUser?.email?.email) { + // challenges = create(RequestChallengesSchema, { + // otpSms: { returnCode: true }, + // }); + // } + // } + // } const session = await setSessionAndUpdateCookie( recent, diff --git a/apps/login/src/ui/LoginPasskey.tsx b/apps/login/src/ui/LoginPasskey.tsx index 47d61cf86a..4c6dc12296 100644 --- a/apps/login/src/ui/LoginPasskey.tsx +++ b/apps/login/src/ui/LoginPasskey.tsx @@ -47,7 +47,6 @@ export default function LoginPasskey({ setLoading(true); updateSessionForChallenge() .then((response) => { - console.log(response); const pK = response?.challenges?.webAuthN?.publicKeyCredentialRequestOptions ?.publicKey; From 7549f6819fa4e8cb366e83eb625bc80789f450a7 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 13:38:03 +0200 Subject: [PATCH 109/640] command adr --- apps/login/src/lib/server/email.ts | 8 ++---- apps/login/src/lib/server/idp.ts | 8 ++---- apps/login/src/lib/server/loginname.ts | 40 +++++++++++++------------- apps/login/src/lib/server/otp.ts | 34 +++++++++++----------- apps/login/src/lib/server/passkeys.ts | 11 +++---- apps/login/src/lib/server/password.ts | 5 ++-- apps/login/src/lib/server/register.ts | 17 +++++------ apps/login/src/lib/server/u2f.ts | 13 +++++---- apps/login/src/lib/zitadel.ts | 1 - 9 files changed, 63 insertions(+), 74 deletions(-) diff --git a/apps/login/src/lib/server/email.ts b/apps/login/src/lib/server/email.ts index e3b64596bc..a048b117a2 100644 --- a/apps/login/src/lib/server/email.ts +++ b/apps/login/src/lib/server/email.ts @@ -8,8 +8,7 @@ type VerifyUserByEmailCommand = { }; export async function verifyUserByEmail(command: VerifyUserByEmailCommand) { - const { userId, code } = command; - return verifyEmail(userId, code); + return verifyEmail(command.userId, command.code); } type resendVerifyEmailCommand = { @@ -17,8 +16,5 @@ type resendVerifyEmailCommand = { }; export async function resendVerifyEmail(command: resendVerifyEmailCommand) { - const { userId } = command; - - // replace with resend Mail method once its implemented - return resendEmailCode(userId); + return resendEmailCode(command.userId); } diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index 5bf85796a1..be00840626 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -9,13 +9,11 @@ export type StartIDPFlowCommand = { }; export async function startIDPFlow(command: StartIDPFlowCommand) { - const { idpId, successUrl, failureUrl } = command; - return startIdentityProviderFlow({ - idpId, + idpId: command.idpId, urls: { - successUrl, - failureUrl, + successUrl: command.successUrl, + failureUrl: command.failureUrl, }, }); } diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 933bafcb24..97ad88773b 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -18,11 +18,10 @@ export type SendLoginnameCommand = { organization?: string; }; -export async function sendLoginname(options: SendLoginnameCommand) { - const { loginName, authRequestId, organization } = options; +export async function sendLoginname(command: SendLoginnameCommand) { const users = await listUsers({ - userName: loginName, - organizationId: organization, + userName: command.loginName, + organizationId: command.organization, }); if (users.details?.totalResult == BigInt(1) && users.result[0].userId) { @@ -31,7 +30,7 @@ export async function sendLoginname(options: SendLoginnameCommand) { userId, undefined, undefined, - authRequestId, + command.authRequestId, ); if (!session.factors?.user?.id) { @@ -49,14 +48,14 @@ export async function sendLoginname(options: SendLoginnameCommand) { }; } - const loginSettings = await getLoginSettings(organization); + const loginSettings = await getLoginSettings(command.organization); // TODO: check if allowDomainDiscovery has to be allowed too, to redirect to the register page // user not found, check if register is enabled on organization if (loginSettings?.allowRegister && !loginSettings?.allowUsernamePassword) { // TODO redirect to loginname page with idp hint const identityProviders = await getActiveIdentityProviders( - organization, + command.organization, ).then((resp) => { return resp.identityProviders; }); @@ -70,12 +69,12 @@ export async function sendLoginname(options: SendLoginnameCommand) { const params = new URLSearchParams(); - if (authRequestId) { - params.set("authRequestId", authRequestId); + if (command.authRequestId) { + params.set("authRequestId", command.authRequestId); } - if (organization) { - params.set("organization", organization); + if (command.organization) { + params.set("organization", command.organization); } return startIdentityProviderFlow({ @@ -98,18 +97,19 @@ export async function sendLoginname(options: SendLoginnameCommand) { loginSettings?.allowRegister && loginSettings?.allowUsernamePassword ) { - const params: any = { organization }; - if (authRequestId) { - params.authRequestId = authRequestId; + const params = new URLSearchParams(); + + if (command.organization) { + params.set("organization", command.organization); } - if (loginName) { - params.email = loginName; + if (command.authRequestId) { + params.set("authRequestId", command.authRequestId); + } + if (command.loginName) { + params.set("loginName", command.loginName); } - const registerUrl = new URL( - "/register?" + new URLSearchParams(params), - // request.url, - ); + const registerUrl = new URL("/register?" + params); return redirect(registerUrl.toString()); } diff --git a/apps/login/src/lib/server/otp.ts b/apps/login/src/lib/server/otp.ts index 6c3c28292c..47c3ec5350 100644 --- a/apps/login/src/lib/server/otp.ts +++ b/apps/login/src/lib/server/otp.ts @@ -23,19 +23,17 @@ export type SetOTPCommand = { }; export async function setOTP(command: SetOTPCommand) { - const { loginName, sessionId, organization, authRequestId, code, method } = - command; - - const recentPromise = sessionId - ? getSessionCookieById({ sessionId }).catch((error) => { + const recentPromise = command.sessionId + ? getSessionCookieById({ sessionId: command.sessionId }).catch((error) => { return Promise.reject(error); }) - : loginName - ? getSessionCookieByLoginName({ loginName, organization }).catch( - (error) => { - return Promise.reject(error); - }, - ) + : command.loginName + ? getSessionCookieByLoginName({ + loginName: command.loginName, + organization: command.organization, + }).catch((error) => { + return Promise.reject(error); + }) : getMostRecentSessionCookie().catch((error) => { return Promise.reject(error); }); @@ -43,17 +41,17 @@ export async function setOTP(command: SetOTPCommand) { return recentPromise.then((recent) => { const checks = create(ChecksSchema, {}); - if (method === "time-based") { + if (command.method === "time-based") { checks.totp = create(CheckTOTPSchema, { - code, + code: command.code, }); - } else if (method === "sms") { + } else if (command.method === "sms") { checks.otpSms = create(CheckOTPSchema, { - code, + code: command.code, }); - } else if (method === "email") { + } else if (command.method === "email") { checks.otpEmail = create(CheckOTPSchema, { - code, + code: command.code, }); } @@ -61,7 +59,7 @@ export async function setOTP(command: SetOTPCommand) { recent, checks, undefined, - authRequestId, + command.authRequestId, ).then((session) => { return { sessionId: session.id, diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 88d82f6d07..46773d9cab 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -54,9 +54,8 @@ export async function registerPasskeyLink( } export async function verifyPasskey(command: VerifyPasskeyCommand) { - let { passkeyId, passkeyName, publicKeyCredential, sessionId } = command; - // if no name is provided, try to generate one from the user agent + let passkeyName = command.passkeyName; if (!!!passkeyName) { const headersList = headers(); const userAgentStructure = { headers: headersList }; @@ -67,7 +66,9 @@ export async function verifyPasskey(command: VerifyPasskeyCommand) { }${os.name}${os.name ? ", " : ""}${browser.name}`; } - const sessionCookie = await getSessionCookieById({ sessionId }); + const sessionCookie = await getSessionCookieById({ + sessionId: command.sessionId, + }); const session = await getSession(sessionCookie.id, sessionCookie.token); const userId = session?.session?.factors?.user?.id; @@ -77,9 +78,9 @@ export async function verifyPasskey(command: VerifyPasskeyCommand) { return verifyPasskeyRegistration( create(VerifyPasskeyRegistrationRequestSchema, { - passkeyId, + passkeyId: command.passkeyId, + publicKeyCredential: command.publicKeyCredential, passkeyName, - publicKeyCredential, userId, }), ); diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 26a413ab18..756f41f671 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -8,10 +8,9 @@ type ResetPasswordCommand = { }; export async function resetPassword(command: ResetPasswordCommand) { - const { loginName, organization } = command; const users = await listUsers({ - userName: loginName, - organizationId: organization, + userName: command.loginName, + organizationId: command.organization, }); if ( diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index c01e9491bf..c6ed2b7a9d 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -12,15 +12,12 @@ type RegisterUserCommand = { authRequestId?: string; }; export async function registerUser(command: RegisterUserCommand) { - const { email, password, firstName, lastName, organization, authRequestId } = - command; - const human = await addHumanUser({ - email: email, - firstName, - lastName, - password: password ? password : undefined, - organization, + email: command.email, + firstName: command.firstName, + lastName: command.lastName, + password: command.password ? command.password : undefined, + organization: command.organization, }); if (!human) { throw Error("Could not create user"); @@ -28,9 +25,9 @@ export async function registerUser(command: RegisterUserCommand) { return createSessionForUserIdAndUpdateCookie( human.userId, - password, + command.password, undefined, - authRequestId, + command.authRequestId, ).then((session) => { return { userId: human.userId, diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index 01ddab6cbe..b19ca2a1c7 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -19,9 +19,9 @@ type VerifyU2FCommand = { }; export async function addU2F(command: RegisterU2FCommand) { - const { sessionId } = command; - - const sessionCookie = await getSessionCookieById({ sessionId }); + const sessionCookie = await getSessionCookieById({ + sessionId: command.sessionId, + }); const session = await getSession(sessionCookie.id, sessionCookie.token); @@ -40,8 +40,7 @@ export async function addU2F(command: RegisterU2FCommand) { } export async function verifyU2F(command: VerifyU2FCommand) { - let { passkeyName, sessionId } = command; - + let passkeyName = command.passkeyName; if (!!!passkeyName) { const headersList = headers(); const userAgentStructure = { headers: headersList }; @@ -51,7 +50,9 @@ export async function verifyU2F(command: VerifyU2FCommand) { device.vendor || device.model ? ", " : "" }${os.name}${os.name ? ", " : ""}${browser.name}`; } - const sessionCookie = await getSessionCookieById({ sessionId }); + const sessionCookie = await getSessionCookieById({ + sessionId: command.sessionId, + }); const session = await getSession(sessionCookie.id, sessionCookie.token); diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index bb6603915d..d4d4209a2f 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -7,7 +7,6 @@ import { makeReqCtx, createOrganizationServiceClient, } from "@zitadel/client/v2"; -import { createManagementServiceClient } from "@zitadel/client/v1"; import { createServerTransport } from "@zitadel/node"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; From 28dde6d69619e43ba4e8823925c08162bd14f760 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 13:48:33 +0200 Subject: [PATCH 110/640] command adr, prettier import config --- .prettierrc.json | 5 -- apps/login/src/app/(login)/accounts/page.tsx | 6 +- apps/login/src/app/(login)/mfa/page.tsx | 3 +- apps/login/src/app/(login)/mfa/set/page.tsx | 3 +- .../src/app/(login)/otp/[method]/set/page.tsx | 4 +- apps/login/src/app/(login)/signedin/page.tsx | 4 +- apps/login/src/app/layout.tsx | 9 +-- apps/login/src/app/login/route.ts | 11 ++- apps/login/src/lib/cookies.ts | 68 +++++++++++++++---- apps/login/src/lib/idp.ts | 6 +- apps/login/src/lib/server/loginname.ts | 4 +- apps/login/src/lib/server/otp.ts | 2 +- apps/login/src/lib/server/passkeys.ts | 8 ++- apps/login/src/lib/server/session.ts | 15 +--- apps/login/src/lib/server/u2f.ts | 4 +- apps/login/src/lib/session.ts | 9 +-- apps/login/src/lib/zitadel.ts | 12 ++-- apps/login/src/middleware.ts | 2 +- apps/login/src/ui/AddressBar.tsx | 2 +- apps/login/src/ui/AuthMethods.tsx | 4 +- apps/login/src/ui/Button.tsx | 7 +- apps/login/src/ui/Checkbox.tsx | 2 +- apps/login/src/ui/ChooseSecondFactor.tsx | 6 +- .../src/ui/ChooseSecondFactorToSetup.tsx | 2 +- apps/login/src/ui/DynamicTheme.tsx | 4 +- apps/login/src/ui/GlobalNav.tsx | 6 +- apps/login/src/ui/IdpSignin.tsx | 8 +-- apps/login/src/ui/Input.tsx | 2 +- apps/login/src/ui/LoginOTP.tsx | 18 ++--- apps/login/src/ui/LoginPasskey.tsx | 18 ++--- apps/login/src/ui/PasswordComplexity.test.tsx | 4 +- apps/login/src/ui/PasswordForm.tsx | 26 +++---- apps/login/src/ui/PrivacyPolicyCheckboxes.tsx | 6 +- .../src/ui/RegisterFormWithoutPassword.tsx | 18 ++--- apps/login/src/ui/RegisterPasskey.tsx | 17 +++-- apps/login/src/ui/RegisterU2F.tsx | 16 ++--- apps/login/src/ui/SessionItem.tsx | 11 ++- apps/login/src/ui/SessionsList.tsx | 6 +- apps/login/src/ui/SetPasswordForm.tsx | 18 ++--- apps/login/src/ui/SignInWithIDP.tsx | 16 +++-- apps/login/src/ui/TOTPRegister.tsx | 16 ++--- apps/login/src/ui/Theme.tsx | 4 +- apps/login/src/ui/ThemeWrapper.tsx | 2 +- apps/login/src/ui/UsernameForm.tsx | 18 ++--- apps/login/src/ui/VerifyEmailForm.tsx | 8 +-- apps/login/src/ui/ZitadelLogo.tsx | 2 - apps/login/src/utils/colors.ts | 2 +- apps/login/src/utils/session.ts | 7 +- apps/login/vitest.config.mts | 2 +- package.json | 1 + packages/zitadel-prettier-config/index.js | 1 + pnpm-lock.yaml | 3 + prettier.config.cjs | 1 + 53 files changed, 234 insertions(+), 225 deletions(-) delete mode 100644 .prettierrc.json create mode 100644 prettier.config.cjs diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 55a45eb397..0000000000 --- a/.prettierrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "printWidth": 125, - "trailingComma": "all", - "plugins": ["prettier-plugin-organize-imports"] -} diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index 24bfd44bcd..8ba2e32d89 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -1,9 +1,9 @@ +import { getAllSessionCookieIds } from "@/lib/cookies"; import { getBrandingSettings, listSessions } from "@/lib/zitadel"; +import DynamicTheme from "@/ui/DynamicTheme"; +import SessionsList from "@/ui/SessionsList"; import { UserPlusIcon } from "@heroicons/react/24/outline"; import Link from "next/link"; -import SessionsList from "@/ui/SessionsList"; -import DynamicTheme from "@/ui/DynamicTheme"; -import { getAllSessionCookieIds } from "@/lib/cookies"; async function loadSessions() { const ids = await getAllSessionCookieIds(); diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index f556db5f48..e86566f690 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -4,7 +4,6 @@ import { getBrandingSettings, getSession, listAuthenticationMethodTypes, - sessionService, } from "@/lib/zitadel"; import Alert from "@/ui/Alert"; import BackButton from "@/ui/BackButton"; @@ -28,7 +27,7 @@ export default async function Page({ loginName?: string, organization?: string, ) { - return loadMostRecentSession(sessionService, { + return loadMostRecentSession({ loginName, organization, }).then((session) => { diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index 6b66b85d32..8af51ef368 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -6,7 +6,6 @@ import { getSession, getUserByID, listAuthenticationMethodTypes, - sessionService, } from "@/lib/zitadel"; import Alert from "@/ui/Alert"; import BackButton from "@/ui/BackButton"; @@ -30,7 +29,7 @@ export default async function Page({ loginName?: string, organization?: string, ) { - return loadMostRecentSession(sessionService, { + return loadMostRecentSession({ loginName, organization, }).then((session) => { diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index 7d7f0fb254..c488b1cf45 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -1,3 +1,4 @@ +import { loadMostRecentSession } from "@/lib/session"; import { addOTPEmail, addOTPSMS, @@ -11,9 +12,8 @@ import { Button, ButtonVariants } from "@/ui/Button"; import DynamicTheme from "@/ui/DynamicTheme"; import TOTPRegister from "@/ui/TOTPRegister"; import UserAvatar from "@/ui/UserAvatar"; -import Link from "next/link"; import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { loadMostRecentSession } from "@/lib/session"; +import Link from "next/link"; export default async function Page({ searchParams, diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index 5bca185b7d..64dc6abe92 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -1,13 +1,13 @@ +import { getMostRecentCookieWithLoginname } from "@/lib/cookies"; import { createCallback, getBrandingSettings, getSession } from "@/lib/zitadel"; import DynamicTheme from "@/ui/DynamicTheme"; import UserAvatar from "@/ui/UserAvatar"; import { create } from "@zitadel/client"; -import { redirect } from "next/navigation"; import { CreateCallbackRequestSchema, SessionSchema, } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; -import { getMostRecentCookieWithLoginname } from "@/lib/cookies"; +import { redirect } from "next/navigation"; async function loadSession(loginName: string, authRequestId?: string) { const recent = await getMostRecentCookieWithLoginname({ loginName }); diff --git a/apps/login/src/app/layout.tsx b/apps/login/src/app/layout.tsx index 5e299faf04..ddf4edb30a 100644 --- a/apps/login/src/app/layout.tsx +++ b/apps/login/src/app/layout.tsx @@ -1,13 +1,10 @@ import "@/styles/globals.scss"; import { AddressBar } from "@/ui/AddressBar"; import { GlobalNav } from "@/ui/GlobalNav"; -import { Lato } from "next/font/google"; -import { LayoutProviders } from "@/ui/LayoutProviders"; -import { Analytics } from "@vercel/analytics/react"; -import ThemeWrapper from "@/ui/ThemeWrapper"; -import { getBrandingSettings } from "@/lib/zitadel"; -import ThemeProvider from "@/ui/ThemeProvider"; import Theme from "@/ui/Theme"; +import ThemeProvider from "@/ui/ThemeProvider"; +import { Analytics } from "@vercel/analytics/react"; +import { Lato } from "next/font/google"; const lato = Lato({ weight: ["400", "700", "900"], diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 92c5d0f123..5046025446 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -7,6 +7,8 @@ export const dynamic = "force-dynamic"; export const revalidate = false; export const fetchCache = "default-no-store"; +import { getAllSessions } from "@/lib/cookies"; +import { idpTypeToSlug } from "@/lib/idp"; import { createCallback, getActiveIdentityProviders, @@ -15,16 +17,13 @@ import { listSessions, startIdentityProviderFlow, } from "@/lib/zitadel"; -import { NextRequest, NextResponse } from "next/server"; -import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { create } from "@zitadel/client"; import { AuthRequest, Prompt, } from "@zitadel/proto/zitadel/oidc/v2/authorization_pb"; -import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { idpTypeToSlug } from "@/lib/idp"; -import { create } from "@zitadel/client"; -import { getAllSessions } from "@/lib/cookies"; +import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { NextRequest, NextResponse } from "next/server"; async function loadSessions(ids: string[]): Promise { const response = await listSessions( diff --git a/apps/login/src/lib/cookies.ts b/apps/login/src/lib/cookies.ts index 3eb1b27422..8317f9e482 100644 --- a/apps/login/src/lib/cookies.ts +++ b/apps/login/src/lib/cookies.ts @@ -26,13 +26,20 @@ function setSessionHttpOnlyCookie(sessions: SessionCookie[]) { }); } -export async function addSessionToCookie(session: SessionCookie, cleanup: boolean = false): Promise { +export async function addSessionToCookie( + session: SessionCookie, + cleanup: boolean = false, +): Promise { const cookiesList = cookies(); const stringifiedCookie = cookiesList.get("sessions"); - let currentSessions: SessionCookie[] = stringifiedCookie?.value ? JSON.parse(stringifiedCookie?.value) : []; + let currentSessions: SessionCookie[] = stringifiedCookie?.value + ? JSON.parse(stringifiedCookie?.value) + : []; - const index = currentSessions.findIndex((s) => s.loginName === session.loginName); + const index = currentSessions.findIndex( + (s) => s.loginName === session.loginName, + ); if (index > -1) { currentSessions[index] = session; @@ -51,11 +58,17 @@ export async function addSessionToCookie(session: SessionCookie, cleanup: } } -export async function updateSessionCookie(id: string, session: SessionCookie, cleanup: boolean = false): Promise { +export async function updateSessionCookie( + id: string, + session: SessionCookie, + cleanup: boolean = false, +): Promise { const cookiesList = cookies(); const stringifiedCookie = cookiesList.get("sessions"); - const sessions: SessionCookie[] = stringifiedCookie?.value ? JSON.parse(stringifiedCookie?.value) : [session]; + const sessions: SessionCookie[] = stringifiedCookie?.value + ? JSON.parse(stringifiedCookie?.value) + : [session]; const foundIndex = sessions.findIndex((session) => session.id === id); @@ -75,11 +88,16 @@ export async function updateSessionCookie(id: string, session: SessionCookie< } } -export async function removeSessionFromCookie(session: SessionCookie, cleanup: boolean = false): Promise { +export async function removeSessionFromCookie( + session: SessionCookie, + cleanup: boolean = false, +): Promise { const cookiesList = cookies(); const stringifiedCookie = cookiesList.get("sessions"); - const sessions: SessionCookie[] = stringifiedCookie?.value ? JSON.parse(stringifiedCookie?.value) : [session]; + const sessions: SessionCookie[] = stringifiedCookie?.value + ? JSON.parse(stringifiedCookie?.value) + : [session]; const reducedSessions = sessions.filter((s) => s.id !== session.id); if (cleanup) { @@ -101,7 +119,10 @@ export async function getMostRecentSessionCookie(): Promise { const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); const latest = sessions.reduce((prev, current) => { - return new Date(prev.changeDate).getTime() > new Date(current.changeDate).getTime() ? prev : current; + return new Date(prev.changeDate).getTime() > + new Date(current.changeDate).getTime() + ? prev + : current; }); return latest; @@ -124,7 +145,9 @@ export async function getSessionCookieById({ const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); const found = sessions.find((s) => - organization ? s.organization === organization && s.id === sessionId : s.id === sessionId, + organization + ? s.organization === organization && s.id === sessionId + : s.id === sessionId, ); if (found) { return found; @@ -149,7 +172,9 @@ export async function getSessionCookieByLoginName({ if (stringifiedCookie?.value) { const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); const found = sessions.find((s) => - organization ? s.organization === organization && s.loginName === loginName : s.loginName === loginName, + organization + ? s.organization === organization && s.loginName === loginName + : s.loginName === loginName, ); if (found) { return found; @@ -166,7 +191,9 @@ export async function getSessionCookieByLoginName({ * @param cleanup when true, removes all expired sessions, default true * @returns Session Cookies */ -export async function getAllSessionCookieIds(cleanup: boolean = false): Promise { +export async function getAllSessionCookieIds( + cleanup: boolean = false, +): Promise { const cookiesList = cookies(); const stringifiedCookie = cookiesList.get("sessions"); @@ -176,7 +203,11 @@ export async function getAllSessionCookieIds(cleanup: boolean = false): Promi if (cleanup) { const now = new Date(); return sessions - .filter((session) => (session.expirationDate ? new Date(session.expirationDate) > now : true)) + .filter((session) => + session.expirationDate + ? new Date(session.expirationDate) > now + : true, + ) .map((session) => session.id); } else { return sessions.map((session) => session.id); @@ -191,7 +222,9 @@ export async function getAllSessionCookieIds(cleanup: boolean = false): Promi * @param cleanup when true, removes all expired sessions, default true * @returns Session Cookies */ -export async function getAllSessions(cleanup: boolean = false): Promise[]> { +export async function getAllSessions( + cleanup: boolean = false, +): Promise[]> { const cookiesList = cookies(); const stringifiedCookie = cookiesList.get("sessions"); @@ -200,7 +233,9 @@ export async function getAllSessions(cleanup: boolean = false): Promise (session.expirationDate ? new Date(session.expirationDate) > now : true)); + return sessions.filter((session) => + session.expirationDate ? new Date(session.expirationDate) > now : true, + ); } else { return sessions; } @@ -240,7 +275,10 @@ export async function getMostRecentCookieWithLoginname({ const latest = filtered && filtered.length ? filtered.reduce((prev, current) => { - return new Date(prev.changeDate).getTime() > new Date(current.changeDate).getTime() ? prev : current; + return new Date(prev.changeDate).getTime() > + new Date(current.changeDate).getTime() + ? prev + : current; }) : undefined; diff --git a/apps/login/src/lib/idp.ts b/apps/login/src/lib/idp.ts index 73a1e40b31..a700c07847 100644 --- a/apps/login/src/lib/idp.ts +++ b/apps/login/src/lib/idp.ts @@ -1,10 +1,10 @@ +import { create } from "@zitadel/client"; +import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { AddHumanUserRequest, AddHumanUserRequestSchema, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb"; -import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { create } from "@zitadel/client"; // This maps the IdentityProviderType to a slug which is used in the /success and /failure routes export function idpTypeToSlug(idpType: IdentityProviderType) { diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 97ad88773b..2ce4b64219 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -1,6 +1,8 @@ "use server"; import { headers } from "next/headers"; +import { redirect } from "next/navigation"; +import { createSessionForUserIdAndUpdateCookie } from "../../utils/session"; import { idpTypeToSlug } from "../idp"; import { getActiveIdentityProviders, @@ -9,8 +11,6 @@ import { listUsers, startIdentityProviderFlow, } from "../zitadel"; -import { createSessionForUserIdAndUpdateCookie } from "../../utils/session"; -import { redirect } from "next/navigation"; export type SendLoginnameCommand = { loginName: string; diff --git a/apps/login/src/lib/server/otp.ts b/apps/login/src/lib/server/otp.ts index 47c3ec5350..55fa878fbd 100644 --- a/apps/login/src/lib/server/otp.ts +++ b/apps/login/src/lib/server/otp.ts @@ -1,12 +1,12 @@ "use server"; import { setSessionAndUpdateCookie } from "@/utils/session"; +import { create } from "@zitadel/client"; import { CheckOTPSchema, ChecksSchema, CheckTOTPSchema, } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; -import { create } from "@zitadel/client"; import { getMostRecentSessionCookie, getSessionCookieById, diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 46773d9cab..3429ce1f16 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -6,11 +6,13 @@ import { registerPasskey, verifyPasskeyRegistration, } from "@/lib/zitadel"; -import { userAgent } from "next/server"; import { create } from "@zitadel/client"; -import { VerifyPasskeyRegistrationRequestSchema } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { + RegisterPasskeyResponse, + VerifyPasskeyRegistrationRequestSchema, +} from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; -import { RegisterPasskeyResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { userAgent } from "next/server"; import { getSessionCookieById } from "../cookies"; type VerifyPasskeyCommand = { diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 083e475e06..3e217316c1 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -1,23 +1,14 @@ "use server"; -import { - deleteSession, - getSession, - getUserByID, - listAuthenticationMethodTypes, -} from "@/lib/zitadel"; +import { deleteSession, listAuthenticationMethodTypes } from "@/lib/zitadel"; import { createSessionAndUpdateCookie, createSessionForIdpAndUpdateCookie, setSessionAndUpdateCookie, } from "@/utils/session"; -import { headers } from "next/headers"; +import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; -import { - RequestChallenges, - RequestChallengesSchema, -} from "@zitadel/proto/zitadel/session/v2/challenge_pb"; -import { create } from "@zitadel/client"; +import { headers } from "next/headers"; import { getMostRecentSessionCookie, getSessionCookieById, diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index b19ca2a1c7..4638336443 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -1,10 +1,10 @@ "use server"; import { getSession, registerU2F, verifyU2FRegistration } from "@/lib/zitadel"; -import { userAgent } from "next/server"; -import { VerifyU2FRegistrationRequestSchema } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { create } from "@zitadel/client"; +import { VerifyU2FRegistrationRequestSchema } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; +import { userAgent } from "next/server"; import { getSessionCookieById } from "../cookies"; type RegisterU2FCommand = { diff --git a/apps/login/src/lib/session.ts b/apps/login/src/lib/session.ts index 32829477aa..29ceb3764b 100644 --- a/apps/login/src/lib/session.ts +++ b/apps/login/src/lib/session.ts @@ -1,11 +1,12 @@ import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { GetSessionResponse } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { getMostRecentCookieWithLoginname } from "./cookies"; +import { sessionService } from "./zitadel"; -export async function loadMostRecentSession( - sessionService: any, // TODO: SessionServiceClient, - sessionParams: { loginName?: string; organization?: string }, -): Promise { +export async function loadMostRecentSession(sessionParams: { + loginName?: string; + organization?: string; +}): Promise { const recent = await getMostRecentCookieWithLoginname({ loginName: sessionParams.loginName, organization: sessionParams.organization, diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index d4d4209a2f..72f2ce1f16 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -1,22 +1,24 @@ import { + createIdpServiceClient, createOIDCServiceClient, + createOrganizationServiceClient, createSessionServiceClient, createSettingsServiceClient, createUserServiceClient, - createIdpServiceClient, makeReqCtx, - createOrganizationServiceClient, } from "@zitadel/client/v2"; import { createServerTransport } from "@zitadel/node"; -import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; +import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { RetrieveIdentityProviderIntentRequest, VerifyPasskeyRegistrationRequest, VerifyU2FRegistrationRequest, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb"; +import { create } from "@zitadel/client"; +import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { @@ -24,8 +26,6 @@ import { SearchQuerySchema, } from "@zitadel/proto/zitadel/user/v2/query_pb"; import { PROVIDER_MAPPING } from "./idp"; -import { create } from "@zitadel/client"; -import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; const SESSION_LIFETIME_S = 3000; diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 965150efbc..3b9f879e30 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -1,5 +1,5 @@ -import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; export const config = { matcher: [ diff --git a/apps/login/src/ui/AddressBar.tsx b/apps/login/src/ui/AddressBar.tsx index a748488e30..e749bcb67a 100644 --- a/apps/login/src/ui/AddressBar.tsx +++ b/apps/login/src/ui/AddressBar.tsx @@ -1,7 +1,7 @@ "use client"; -import React from "react"; import { usePathname } from "next/navigation"; +import React from "react"; type Props = { domain: string; diff --git a/apps/login/src/ui/AuthMethods.tsx b/apps/login/src/ui/AuthMethods.tsx index 1089399742..09e3925075 100644 --- a/apps/login/src/ui/AuthMethods.tsx +++ b/apps/login/src/ui/AuthMethods.tsx @@ -1,8 +1,8 @@ +import { CheckIcon } from "@heroicons/react/24/solid"; import clsx from "clsx"; import Link from "next/link"; -import { BadgeState, StateBadge } from "./StateBadge"; -import { CheckIcon } from "@heroicons/react/24/solid"; import { ReactNode } from "react"; +import { BadgeState, StateBadge } from "./StateBadge"; const cardClasses = (alreadyAdded: boolean) => clsx( diff --git a/apps/login/src/ui/Button.tsx b/apps/login/src/ui/Button.tsx index e16a6a6981..1f1d2061b6 100644 --- a/apps/login/src/ui/Button.tsx +++ b/apps/login/src/ui/Button.tsx @@ -1,10 +1,5 @@ import clsx from "clsx"; -import React, { - ButtonHTMLAttributes, - DetailedHTMLProps, - ReactNode, - forwardRef, -} from "react"; +import { ButtonHTMLAttributes, DetailedHTMLProps, forwardRef } from "react"; export enum ButtonSizes { Small = "Small", diff --git a/apps/login/src/ui/Checkbox.tsx b/apps/login/src/ui/Checkbox.tsx index c879e1aa9a..356ae34abc 100644 --- a/apps/login/src/ui/Checkbox.tsx +++ b/apps/login/src/ui/Checkbox.tsx @@ -1,5 +1,5 @@ import classNames from "clsx"; -import React, { +import { DetailedHTMLProps, forwardRef, InputHTMLAttributes, diff --git a/apps/login/src/ui/ChooseSecondFactor.tsx b/apps/login/src/ui/ChooseSecondFactor.tsx index 9d69b09cd7..26aab1b6af 100644 --- a/apps/login/src/ui/ChooseSecondFactor.tsx +++ b/apps/login/src/ui/ChooseSecondFactor.tsx @@ -1,11 +1,7 @@ "use client"; -import Link from "next/link"; -import { BadgeState, StateBadge } from "./StateBadge"; -import clsx from "clsx"; -import { CheckIcon } from "@heroicons/react/24/outline"; -import { EMAIL, SMS, TOTP, U2F } from "./AuthMethods"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { EMAIL, SMS, TOTP, U2F } from "./AuthMethods"; type Props = { loginName?: string; diff --git a/apps/login/src/ui/ChooseSecondFactorToSetup.tsx b/apps/login/src/ui/ChooseSecondFactorToSetup.tsx index f38e36037b..94315a40e6 100644 --- a/apps/login/src/ui/ChooseSecondFactorToSetup.tsx +++ b/apps/login/src/ui/ChooseSecondFactorToSetup.tsx @@ -1,8 +1,8 @@ "use client"; -import { EMAIL, SMS, TOTP, U2F } from "./AuthMethods"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { EMAIL, SMS, TOTP, U2F } from "./AuthMethods"; type Props = { loginName?: string; diff --git a/apps/login/src/ui/DynamicTheme.tsx b/apps/login/src/ui/DynamicTheme.tsx index 89cfde9fbe..bc98d2bbcb 100644 --- a/apps/login/src/ui/DynamicTheme.tsx +++ b/apps/login/src/ui/DynamicTheme.tsx @@ -1,9 +1,9 @@ "use client"; -import React from "react"; import { Logo } from "@/ui/Logo"; -import ThemeWrapper from "./ThemeWrapper"; import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; +import React from "react"; +import ThemeWrapper from "./ThemeWrapper"; export default function DynamicTheme({ branding, diff --git a/apps/login/src/ui/GlobalNav.tsx b/apps/login/src/ui/GlobalNav.tsx index 6a35d0fd2d..69a976f349 100644 --- a/apps/login/src/ui/GlobalNav.tsx +++ b/apps/login/src/ui/GlobalNav.tsx @@ -2,10 +2,10 @@ import { demos, type Item } from "@/lib/demos"; import { ZitadelLogo } from "@/ui/ZitadelLogo"; -import Link from "next/link"; -import { useSelectedLayoutSegment, usePathname } from "next/navigation"; -import clsx from "clsx"; import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/solid"; +import clsx from "clsx"; +import Link from "next/link"; +import { usePathname, useSelectedLayoutSegment } from "next/navigation"; import { useState } from "react"; import Theme from "./Theme"; diff --git a/apps/login/src/ui/IdpSignin.tsx b/apps/login/src/ui/IdpSignin.tsx index a11bf72a1a..018ddd2aa2 100644 --- a/apps/login/src/ui/IdpSignin.tsx +++ b/apps/login/src/ui/IdpSignin.tsx @@ -1,10 +1,10 @@ "use client"; -import { useEffect, useState } from "react"; -import { Spinner } from "./Spinner"; -import Alert from "./Alert"; -import { useRouter } from "next/navigation"; import { createNewSession } from "@/lib/server/session"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import Alert from "./Alert"; +import { Spinner } from "./Spinner"; type Props = { userId: string; diff --git a/apps/login/src/ui/Input.tsx b/apps/login/src/ui/Input.tsx index b5af102ec3..1af7198995 100644 --- a/apps/login/src/ui/Input.tsx +++ b/apps/login/src/ui/Input.tsx @@ -1,7 +1,7 @@ "use client"; import { CheckCircleIcon } from "@heroicons/react/24/solid"; import clsx from "clsx"; -import React, { +import { ChangeEvent, DetailedHTMLProps, forwardRef, diff --git a/apps/login/src/ui/LoginOTP.tsx b/apps/login/src/ui/LoginOTP.tsx index 3a56598fa6..8caf80a7db 100644 --- a/apps/login/src/ui/LoginOTP.tsx +++ b/apps/login/src/ui/LoginOTP.tsx @@ -1,15 +1,15 @@ "use client"; -import { useEffect, useRef, useState } from "react"; -import { useRouter } from "next/navigation"; -import { Button, ButtonVariants } from "./Button"; -import Alert, { AlertType } from "./Alert"; -import { Spinner } from "./Spinner"; -import { useForm } from "react-hook-form"; -import { TextInput } from "./Input"; -import BackButton from "./BackButton"; -import { ChecksJson } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { ChallengesJson } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; +import { ChecksJson } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { useRouter } from "next/navigation"; +import { useEffect, useRef, useState } from "react"; +import { useForm } from "react-hook-form"; +import Alert, { AlertType } from "./Alert"; +import BackButton from "./BackButton"; +import { Button, ButtonVariants } from "./Button"; +import { TextInput } from "./Input"; +import { Spinner } from "./Spinner"; // either loginName or sessionId must be provided type Props = { diff --git a/apps/login/src/ui/LoginPasskey.tsx b/apps/login/src/ui/LoginPasskey.tsx index 4c6dc12296..3c94feecdb 100644 --- a/apps/login/src/ui/LoginPasskey.tsx +++ b/apps/login/src/ui/LoginPasskey.tsx @@ -1,19 +1,19 @@ "use client"; -import { useEffect, useRef, useState } from "react"; -import { useRouter } from "next/navigation"; -import { coerceToArrayBuffer, coerceToBase64Url } from "@/utils/base64"; -import { Button, ButtonVariants } from "./Button"; -import Alert from "./Alert"; -import { Spinner } from "./Spinner"; -import BackButton from "./BackButton"; -import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { updateSession } from "@/lib/server/session"; +import { coerceToArrayBuffer, coerceToBase64Url } from "@/utils/base64"; +import { create } from "@zitadel/client"; import { RequestChallengesSchema, UserVerificationRequirement, } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; -import { create } from "@zitadel/client"; +import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { useRouter } from "next/navigation"; +import { useEffect, useRef, useState } from "react"; +import Alert from "./Alert"; +import BackButton from "./BackButton"; +import { Button, ButtonVariants } from "./Button"; +import { Spinner } from "./Spinner"; // either loginName or sessionId must be provided type Props = { diff --git a/apps/login/src/ui/PasswordComplexity.test.tsx b/apps/login/src/ui/PasswordComplexity.test.tsx index ba4b1445d7..7c01470447 100644 --- a/apps/login/src/ui/PasswordComplexity.test.tsx +++ b/apps/login/src/ui/PasswordComplexity.test.tsx @@ -1,11 +1,11 @@ -import { expect, describe, test, beforeEach, afterEach } from "vitest"; import { - render, cleanup, + render, screen, waitFor, within, } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, test } from "vitest"; import PasswordComplexity from "./PasswordComplexity"; const matchesTitle = `Matches`; diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index 16c1559815..5449dcd82b 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -1,23 +1,19 @@ "use client"; -import { useState } from "react"; -import { Button, ButtonVariants } from "./Button"; -import { TextInput } from "./Input"; -import { useForm } from "react-hook-form"; +import { resetPassword } from "@/lib/server/password"; +import { updateSession } from "@/lib/server/session"; +import { create } from "@zitadel/client"; +import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { useRouter } from "next/navigation"; -import { Spinner } from "./Spinner"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; import Alert from "./Alert"; import BackButton from "./BackButton"; -import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { - CheckPassword, - Checks, - ChecksSchema, -} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; -import { create } from "@zitadel/client"; -import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { updateSession } from "@/lib/server/session"; -import { resetPassword } from "@/lib/server/password"; +import { Button, ButtonVariants } from "./Button"; +import { TextInput } from "./Input"; +import { Spinner } from "./Spinner"; type Inputs = { password: string; diff --git a/apps/login/src/ui/PrivacyPolicyCheckboxes.tsx b/apps/login/src/ui/PrivacyPolicyCheckboxes.tsx index b09ac1a819..6f2bf62c06 100644 --- a/apps/login/src/ui/PrivacyPolicyCheckboxes.tsx +++ b/apps/login/src/ui/PrivacyPolicyCheckboxes.tsx @@ -1,8 +1,8 @@ "use client"; -import React, { useState } from "react"; -import Link from "next/link"; -import { Checkbox } from "./Checkbox"; import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; +import Link from "next/link"; +import { useState } from "react"; +import { Checkbox } from "./Checkbox"; type Props = { legal: LegalAndSupportSettings; diff --git a/apps/login/src/ui/RegisterFormWithoutPassword.tsx b/apps/login/src/ui/RegisterFormWithoutPassword.tsx index a5db9377fe..9ffcbb0b70 100644 --- a/apps/login/src/ui/RegisterFormWithoutPassword.tsx +++ b/apps/login/src/ui/RegisterFormWithoutPassword.tsx @@ -1,19 +1,19 @@ "use client"; -import { useState } from "react"; -import { Button, ButtonVariants } from "./Button"; -import { TextInput } from "./Input"; -import { PrivacyPolicyCheckboxes } from "./PrivacyPolicyCheckboxes"; -import { FieldValues, useForm } from "react-hook-form"; +import { registerUser } from "@/lib/server/register"; +import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; import { useRouter } from "next/navigation"; -import { Spinner } from "./Spinner"; +import { useState } from "react"; +import { FieldValues, useForm } from "react-hook-form"; +import Alert from "./Alert"; import AuthenticationMethodRadio, { methods, } from "./AuthenticationMethodRadio"; -import Alert from "./Alert"; import BackButton from "./BackButton"; -import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; -import { registerUser } from "@/lib/server/register"; +import { Button, ButtonVariants } from "./Button"; +import { TextInput } from "./Input"; +import { PrivacyPolicyCheckboxes } from "./PrivacyPolicyCheckboxes"; +import { Spinner } from "./Spinner"; type Inputs = | { diff --git a/apps/login/src/ui/RegisterPasskey.tsx b/apps/login/src/ui/RegisterPasskey.tsx index 017ce8c722..2b5c1b12e4 100644 --- a/apps/login/src/ui/RegisterPasskey.tsx +++ b/apps/login/src/ui/RegisterPasskey.tsx @@ -1,15 +1,14 @@ "use client"; -import { useState } from "react"; -import { Button, ButtonVariants } from "./Button"; -import { useForm } from "react-hook-form"; -import { useRouter } from "next/navigation"; -import { Spinner } from "./Spinner"; -import Alert from "./Alert"; -import { coerceToArrayBuffer, coerceToBase64Url } from "@/utils/base64"; -import BackButton from "./BackButton"; -import { RegisterPasskeyResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { registerPasskeyLink, verifyPasskey } from "@/lib/server/passkeys"; +import { coerceToArrayBuffer, coerceToBase64Url } from "@/utils/base64"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import Alert from "./Alert"; +import BackButton from "./BackButton"; +import { Button, ButtonVariants } from "./Button"; +import { Spinner } from "./Spinner"; type Inputs = {}; diff --git a/apps/login/src/ui/RegisterU2F.tsx b/apps/login/src/ui/RegisterU2F.tsx index 5e6de4fd50..9a04120590 100644 --- a/apps/login/src/ui/RegisterU2F.tsx +++ b/apps/login/src/ui/RegisterU2F.tsx @@ -1,15 +1,13 @@ "use client"; -import { useState } from "react"; -import { Button, ButtonVariants } from "./Button"; -import { useForm } from "react-hook-form"; -import { useRouter } from "next/navigation"; -import { Spinner } from "./Spinner"; -import Alert from "./Alert"; -import { coerceToArrayBuffer, coerceToBase64Url } from "@/utils/base64"; -import BackButton from "./BackButton"; -import { RegisterU2FResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { addU2F, verifyU2F } from "@/lib/server/u2f"; +import { coerceToArrayBuffer, coerceToBase64Url } from "@/utils/base64"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import Alert from "./Alert"; +import BackButton from "./BackButton"; +import { Button, ButtonVariants } from "./Button"; +import { Spinner } from "./Spinner"; type Inputs = {}; diff --git a/apps/login/src/ui/SessionItem.tsx b/apps/login/src/ui/SessionItem.tsx index 7ef0c4238c..94aa1f2ef4 100644 --- a/apps/login/src/ui/SessionItem.tsx +++ b/apps/login/src/ui/SessionItem.tsx @@ -1,14 +1,13 @@ "use client"; +import { cleanupSession } from "@/lib/server/session"; +import { XCircleIcon } from "@heroicons/react/24/outline"; +import { timestampDate } from "@zitadel/client"; +import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import moment from "moment"; import Link from "next/link"; import { useState } from "react"; import { Avatar } from "./Avatar"; -import moment from "moment"; -import { XCircleIcon } from "@heroicons/react/24/outline"; -import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { timestampDate } from "@zitadel/client"; -import { deleteSession } from "@/lib/zitadel"; -import { cleanupSession } from "@/lib/server/session"; export default function SessionItem({ session, diff --git a/apps/login/src/ui/SessionsList.tsx b/apps/login/src/ui/SessionsList.tsx index 8a7c794440..f1923256c4 100644 --- a/apps/login/src/ui/SessionsList.tsx +++ b/apps/login/src/ui/SessionsList.tsx @@ -1,9 +1,9 @@ "use client"; -import SessionItem from "./SessionItem"; -import Alert from "./Alert"; -import { useEffect, useState } from "react"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { useState } from "react"; +import Alert from "./Alert"; +import SessionItem from "./SessionItem"; type Props = { sessions: Session[]; diff --git a/apps/login/src/ui/SetPasswordForm.tsx b/apps/login/src/ui/SetPasswordForm.tsx index eb94e210bc..97784c0b4f 100644 --- a/apps/login/src/ui/SetPasswordForm.tsx +++ b/apps/login/src/ui/SetPasswordForm.tsx @@ -1,21 +1,21 @@ "use client"; -import PasswordComplexity from "./PasswordComplexity"; -import { useState } from "react"; -import { Button, ButtonVariants } from "./Button"; -import { TextInput } from "./Input"; -import { FieldValues, useForm } from "react-hook-form"; +import { registerUser } from "@/lib/server/register"; import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator, } from "@/utils/validators"; -import { useRouter } from "next/navigation"; -import { Spinner } from "./Spinner"; -import Alert from "./Alert"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; -import { registerUser } from "@/lib/server/register"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { FieldValues, useForm } from "react-hook-form"; +import Alert from "./Alert"; +import { Button, ButtonVariants } from "./Button"; +import { TextInput } from "./Input"; +import PasswordComplexity from "./PasswordComplexity"; +import { Spinner } from "./Spinner"; type Inputs = | { diff --git a/apps/login/src/ui/SignInWithIDP.tsx b/apps/login/src/ui/SignInWithIDP.tsx index fc00d34ab1..a926ee915c 100644 --- a/apps/login/src/ui/SignInWithIDP.tsx +++ b/apps/login/src/ui/SignInWithIDP.tsx @@ -1,16 +1,18 @@ "use client"; -import { ReactNode, useState } from "react"; -import { useRouter } from "next/navigation"; -import Alert from "./Alert"; -import { IdentityProvider } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { idpTypeToSlug } from "@/lib/idp"; -import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { startIDPFlow } from "@/lib/server/idp"; -import { SignInWithGithub } from "./idps/SignInWithGithub"; +import { + IdentityProvider, + IdentityProviderType, +} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { useRouter } from "next/navigation"; +import { ReactNode, useState } from "react"; +import Alert from "./Alert"; import { SignInWithAzureAD } from "./idps/SignInWithAzureAD"; -import { SignInWithGoogle } from "./idps/SignInWithGoogle"; +import { SignInWithGithub } from "./idps/SignInWithGithub"; import { SignInWithGitlab } from "./idps/SignInWithGitlab"; +import { SignInWithGoogle } from "./idps/SignInWithGoogle"; export interface SignInWithIDPProps { children?: ReactNode; diff --git a/apps/login/src/ui/TOTPRegister.tsx b/apps/login/src/ui/TOTPRegister.tsx index bdec18af5c..ba66d73b57 100644 --- a/apps/login/src/ui/TOTPRegister.tsx +++ b/apps/login/src/ui/TOTPRegister.tsx @@ -1,15 +1,15 @@ "use client"; -import { QRCodeSVG } from "qrcode.react"; -import Alert, { AlertType } from "./Alert"; +import { verifyTOTP } from "@/lib/server-actions"; import Link from "next/link"; -import CopyToClipboard from "./CopyToClipboard"; -import { TextInput } from "./Input"; -import { Button, ButtonVariants } from "./Button"; -import { Spinner } from "./Spinner"; +import { useRouter } from "next/navigation"; +import { QRCodeSVG } from "qrcode.react"; import { useState } from "react"; import { useForm } from "react-hook-form"; -import { useRouter } from "next/navigation"; -import { verifyTOTP } from "@/lib/server-actions"; +import Alert from "./Alert"; +import { Button, ButtonVariants } from "./Button"; +import CopyToClipboard from "./CopyToClipboard"; +import { TextInput } from "./Input"; +import { Spinner } from "./Spinner"; type Inputs = { code: string; diff --git a/apps/login/src/ui/Theme.tsx b/apps/login/src/ui/Theme.tsx index 126d9403ff..bea2202529 100644 --- a/apps/login/src/ui/Theme.tsx +++ b/apps/login/src/ui/Theme.tsx @@ -1,8 +1,8 @@ "use client"; -import React, { useEffect, useState } from "react"; -import { useTheme } from "next-themes"; import { MoonIcon, SunIcon } from "@heroicons/react/24/outline"; +import { useTheme } from "next-themes"; +import { useEffect, useState } from "react"; function Theme() { const { resolvedTheme, setTheme } = useTheme(); diff --git a/apps/login/src/ui/ThemeWrapper.tsx b/apps/login/src/ui/ThemeWrapper.tsx index c927bd943b..14b85de94f 100644 --- a/apps/login/src/ui/ThemeWrapper.tsx +++ b/apps/login/src/ui/ThemeWrapper.tsx @@ -1,8 +1,8 @@ "use client"; import { setTheme } from "@/utils/colors"; -import { ReactNode, useEffect } from "react"; import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; +import { ReactNode, useEffect } from "react"; type Props = { branding: BrandingSettings | undefined; diff --git a/apps/login/src/ui/UsernameForm.tsx b/apps/login/src/ui/UsernameForm.tsx index 3b596c9649..7c22c77fd8 100644 --- a/apps/login/src/ui/UsernameForm.tsx +++ b/apps/login/src/ui/UsernameForm.tsx @@ -1,19 +1,19 @@ "use client"; -import { ReactNode, useEffect, useState } from "react"; -import { Button, ButtonVariants } from "./Button"; -import { TextInput } from "./Input"; -import { useForm } from "react-hook-form"; -import { useRouter } from "next/navigation"; -import { Spinner } from "./Spinner"; -import Alert from "./Alert"; +import { sendLoginname } from "@/lib/server/loginname"; import { LoginSettings, PasskeysType, } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import BackButton from "./BackButton"; -import { sendLoginname } from "@/lib/server/loginname"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { useRouter } from "next/navigation"; +import { ReactNode, useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import Alert from "./Alert"; +import BackButton from "./BackButton"; +import { Button, ButtonVariants } from "./Button"; +import { TextInput } from "./Input"; +import { Spinner } from "./Spinner"; type Inputs = { loginName: string; diff --git a/apps/login/src/ui/VerifyEmailForm.tsx b/apps/login/src/ui/VerifyEmailForm.tsx index d0bda03c1b..abf58be3fb 100644 --- a/apps/login/src/ui/VerifyEmailForm.tsx +++ b/apps/login/src/ui/VerifyEmailForm.tsx @@ -1,13 +1,13 @@ "use client"; +import { resendVerifyEmail, verifyUserByEmail } from "@/lib/server/email"; +import Alert from "@/ui/Alert"; +import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; import { Button, ButtonVariants } from "./Button"; import { TextInput } from "./Input"; -import { useForm } from "react-hook-form"; -import { useRouter } from "next/navigation"; import { Spinner } from "./Spinner"; -import Alert from "@/ui/Alert"; -import { resendVerifyEmail, verifyUserByEmail } from "@/lib/server/email"; type Inputs = { code: string; diff --git a/apps/login/src/ui/ZitadelLogo.tsx b/apps/login/src/ui/ZitadelLogo.tsx index a260424e93..105665fbba 100644 --- a/apps/login/src/ui/ZitadelLogo.tsx +++ b/apps/login/src/ui/ZitadelLogo.tsx @@ -1,6 +1,4 @@ import Image from "next/image"; -import { ZitadelLogoDark } from "./ZitadelLogoDark"; -import { ZitadelLogoLight } from "./ZitadelLogoLight"; type Props = { height?: number; width?: number; diff --git a/apps/login/src/utils/colors.ts b/apps/login/src/utils/colors.ts index 8ba86f9a60..bdb07cecfd 100644 --- a/apps/login/src/utils/colors.ts +++ b/apps/login/src/utils/colors.ts @@ -1,5 +1,5 @@ -import tinycolor from "tinycolor2"; import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; +import tinycolor from "tinycolor2"; export interface Color { name: string; diff --git a/apps/login/src/utils/session.ts b/apps/login/src/utils/session.ts index 2d4e9f52b3..5e9e5c5b67 100644 --- a/apps/login/src/utils/session.ts +++ b/apps/login/src/utils/session.ts @@ -1,11 +1,13 @@ "use server"; +import { addSessionToCookie, updateSessionCookie } from "@/lib/cookies"; import { - createSessionFromChecks, createSessionForUserIdAndIdpIntent, + createSessionFromChecks, getSession, setSession, } from "@/lib/zitadel"; +import { create, timestampDate, toDate } from "@zitadel/client"; import { Challenges, RequestChallenges, @@ -15,9 +17,6 @@ import { Checks, ChecksSchema, } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; -import { timestampDate, toDate } from "@zitadel/client"; -import { create } from "@zitadel/client"; -import { addSessionToCookie, updateSessionCookie } from "@/lib/cookies"; type CustomCookieData = { id: string; diff --git a/apps/login/vitest.config.mts b/apps/login/vitest.config.mts index 161c2d14fc..238c5b8b93 100644 --- a/apps/login/vitest.config.mts +++ b/apps/login/vitest.config.mts @@ -1,6 +1,6 @@ -import { defineConfig } from "vitest/config"; import react from "@vitejs/plugin-react"; import tsconfigPaths from "vite-tsconfig-paths"; +import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [tsconfigPaths(), react()], diff --git a/package.json b/package.json index c22093f9fe..7ebe6a5c45 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@vitejs/plugin-react": "^4.2.1", "eslint": "8.57.0", "eslint-config-zitadel": "workspace:*", + "@zitadel/prettier-config": "workspace:^", "prettier": "^3.2.5", "prettier-plugin-organize-imports": "^4.0.0", "tsup": "^8.2.4", diff --git a/packages/zitadel-prettier-config/index.js b/packages/zitadel-prettier-config/index.js index 4654e85e40..31d8c455e8 100644 --- a/packages/zitadel-prettier-config/index.js +++ b/packages/zitadel-prettier-config/index.js @@ -7,4 +7,5 @@ export default { trailingComma: 'all', bracketSpacing: true, arrowParens: 'always', + plugins: ["prettier-plugin-organize-imports"] }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fce6beabbc..14f788fbee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@vitejs/plugin-react': specifier: ^4.2.1 version: 4.3.1(vite@5.4.2) + '@zitadel/prettier-config': + specifier: workspace:^ + version: link:packages/zitadel-prettier-config eslint: specifier: 8.57.0 version: 8.57.0 diff --git a/prettier.config.cjs b/prettier.config.cjs new file mode 100644 index 0000000000..6df557c2fd --- /dev/null +++ b/prettier.config.cjs @@ -0,0 +1 @@ +export { default } from "@zitadel/prettier-config"; From 82c8d083606994579b15557d78f76acc057f2faf Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 14:02:08 +0200 Subject: [PATCH 111/640] loadMostRecentSession --- apps/login/src/app/(login)/otp/[method]/page.tsx | 4 ++-- apps/login/src/app/(login)/otp/[method]/set/page.tsx | 3 +-- apps/login/src/app/(login)/passkey/add/page.tsx | 4 ++-- apps/login/src/app/(login)/passkey/login/page.tsx | 4 ++-- apps/login/src/app/(login)/password/page.tsx | 8 ++------ apps/login/src/app/(login)/u2f/page.tsx | 4 ++-- apps/login/src/app/(login)/u2f/set/page.tsx | 4 ++-- apps/login/src/lib/server-actions.ts | 4 ++-- 8 files changed, 15 insertions(+), 20 deletions(-) diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index e737a031b4..55af60f66b 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -1,5 +1,5 @@ import { loadMostRecentSession } from "@/lib/session"; -import { getBrandingSettings, sessionService } from "@/lib/zitadel"; +import { getBrandingSettings } from "@/lib/zitadel"; import Alert from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; import LoginOTP from "@/ui/LoginOTP"; @@ -17,7 +17,7 @@ export default async function Page({ const { method } = params; - const session = await loadMostRecentSession(sessionService, { + const session = await loadMostRecentSession({ loginName, organization, }); diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index c488b1cf45..22b2246184 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -4,7 +4,6 @@ import { addOTPSMS, getBrandingSettings, registerTOTP, - sessionService, } from "@/lib/zitadel"; import Alert from "@/ui/Alert"; import BackButton from "@/ui/BackButton"; @@ -27,7 +26,7 @@ export default async function Page({ const { method } = params; const branding = await getBrandingSettings(organization); - const session = await loadMostRecentSession(sessionService, { + const session = await loadMostRecentSession({ loginName, organization, }); diff --git a/apps/login/src/app/(login)/passkey/add/page.tsx b/apps/login/src/app/(login)/passkey/add/page.tsx index b3332839b3..beec6902c6 100644 --- a/apps/login/src/app/(login)/passkey/add/page.tsx +++ b/apps/login/src/app/(login)/passkey/add/page.tsx @@ -1,5 +1,5 @@ import { loadMostRecentSession } from "@/lib/session"; -import { getBrandingSettings, sessionService } from "@/lib/zitadel"; +import { getBrandingSettings } from "@/lib/zitadel"; import Alert, { AlertType } from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; import RegisterPasskey from "@/ui/RegisterPasskey"; @@ -13,7 +13,7 @@ export default async function Page({ const { loginName, promptPasswordless, organization, authRequestId } = searchParams; - const session = await loadMostRecentSession(sessionService, { + const session = await loadMostRecentSession({ loginName, organization, }); diff --git a/apps/login/src/app/(login)/passkey/login/page.tsx b/apps/login/src/app/(login)/passkey/login/page.tsx index 5b3e2e9727..d681b4ef91 100644 --- a/apps/login/src/app/(login)/passkey/login/page.tsx +++ b/apps/login/src/app/(login)/passkey/login/page.tsx @@ -1,6 +1,6 @@ import { getSessionCookieById } from "@/lib/cookies"; import { loadMostRecentSession } from "@/lib/session"; -import { getBrandingSettings, getSession, sessionService } from "@/lib/zitadel"; +import { getBrandingSettings, getSession } from "@/lib/zitadel"; import Alert from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; import LoginPasskey from "@/ui/LoginPasskey"; @@ -20,7 +20,7 @@ export default async function Page({ const sessionFactors = sessionId ? await loadSessionById(sessionId, organization) - : await loadMostRecentSession(sessionService, { loginName, organization }); + : await loadMostRecentSession({ loginName, organization }); async function loadSessionById(sessionId: string, organization?: string) { const recent = await getSessionCookieById({ sessionId, organization }); diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index a486697db0..7476eb3967 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -1,9 +1,5 @@ import { loadMostRecentSession } from "@/lib/session"; -import { - getBrandingSettings, - getLoginSettings, - sessionService, -} from "@/lib/zitadel"; +import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; import Alert from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; import PasswordForm from "@/ui/PasswordForm"; @@ -17,7 +13,7 @@ export default async function Page({ const { loginName, organization, promptPasswordless, authRequestId, alt } = searchParams; - const sessionFactors = await loadMostRecentSession(sessionService, { + const sessionFactors = await loadMostRecentSession({ loginName, organization, }); diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index 27216e0c80..55e99db4d9 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -1,6 +1,6 @@ import { getSessionCookieById } from "@/lib/cookies"; import { loadMostRecentSession } from "@/lib/session"; -import { getBrandingSettings, getSession, sessionService } from "@/lib/zitadel"; +import { getBrandingSettings, getSession } from "@/lib/zitadel"; import Alert from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; import LoginPasskey from "@/ui/LoginPasskey"; @@ -17,7 +17,7 @@ export default async function Page({ const sessionFactors = sessionId ? await loadSessionById(sessionId, organization) - : await loadMostRecentSession(sessionService, { loginName, organization }); + : await loadMostRecentSession({ loginName, organization }); async function loadSessionById(sessionId: string, organization?: string) { const recent = await getSessionCookieById({ sessionId, organization }); diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index 9530bf206f..ac3d24653b 100644 --- a/apps/login/src/app/(login)/u2f/set/page.tsx +++ b/apps/login/src/app/(login)/u2f/set/page.tsx @@ -1,5 +1,5 @@ import { loadMostRecentSession } from "@/lib/session"; -import { getBrandingSettings, sessionService } from "@/lib/zitadel"; +import { getBrandingSettings } from "@/lib/zitadel"; import Alert from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; import RegisterU2F from "@/ui/RegisterU2F"; @@ -12,7 +12,7 @@ export default async function Page({ }) { const { loginName, organization, authRequestId } = searchParams; - const sessionFactors = await loadMostRecentSession(sessionService, { + const sessionFactors = await loadMostRecentSession({ loginName, organization, }); diff --git a/apps/login/src/lib/server-actions.ts b/apps/login/src/lib/server-actions.ts index 39f61c30b7..ce0726075f 100644 --- a/apps/login/src/lib/server-actions.ts +++ b/apps/login/src/lib/server-actions.ts @@ -1,14 +1,14 @@ "use server"; import { loadMostRecentSession } from "./session"; -import { sessionService, verifyTOTPRegistration } from "./zitadel"; +import { verifyTOTPRegistration } from "./zitadel"; export async function verifyTOTP( code: string, loginName?: string, organization?: string, ) { - return loadMostRecentSession(sessionService, { + return loadMostRecentSession({ loginName, organization, }).then((session) => { From 02ea2fc00e50f86dabc4f5d2dd6449774a9f3a8a Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 14:24:19 +0200 Subject: [PATCH 112/640] reorg mfa prompt --- apps/login/src/ui/PasswordForm.tsx | 31 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index 5449dcd82b..b908525b04 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -146,6 +146,22 @@ export default function PasswordForm({ } return router.push(`/mfa?` + params); + } else if (loginSettings?.forceMfa && !availableSecondFactors.length) { + const params = new URLSearchParams({ + loginName: submitted.factors.user.loginName, + checkAfter: "true", // this defines if the check is directly made after the setup + }); + + if (authRequestId) { + params.append("authRequestId", authRequestId); + } + + if (organization) { + params.append("organization", organization); + } + + // TODO: provide a way to setup passkeys on mfa page? + return router.push(`/mfa/set?` + params); } else if ( submitted.factors && !submitted.factors.webAuthN && // if session was not verified with a passkey @@ -166,21 +182,6 @@ export default function PasswordForm({ } return router.push(`/passkey/add?` + params); - } else if (loginSettings?.forceMfa && !availableSecondFactors.length) { - const params = new URLSearchParams({ - loginName: submitted.factors.user.loginName, - checkAfter: "true", // this defines if the check is directly made after the setup - }); - - if (authRequestId) { - params.append("authRequestId", authRequestId); - } - - if (organization) { - params.append("organization", organization); - } - - return router.push(`/mfa/set?` + params); } else if (authRequestId && submitted.sessionId) { const params = new URLSearchParams({ sessionId: submitted.sessionId, From 4a1c2337ef444856d66d2b6e1660704fddf71766 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 14:49:48 +0200 Subject: [PATCH 113/640] fix otp login --- apps/login/src/lib/server/session.ts | 32 -------- apps/login/src/ui/LoginOTP.tsx | 112 +++++++++++++-------------- apps/login/src/ui/PasswordForm.tsx | 2 + 3 files changed, 56 insertions(+), 90 deletions(-) diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 3e217316c1..3220ab408d 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -103,38 +103,6 @@ export async function updateSession(options: UpdateSessionCommand) { const recent = await sessionPromise; - // if ( - // (recent && - // challenges && - // challenges.otpEmail && - // !challenges.otpEmail?.deliveryType) || - // (challenges?.otpSms && !challenges.otpSms.returnCode) - // ) { - // const sessionResponse = await getSession(recent.id, recent.token); - - // if (sessionResponse && sessionResponse?.session?.factors?.user?.id) { - // const userResponse = await getUserByID( - // sessionResponse.session.factors.user.id, - // ); - // const humanUser = - // userResponse.user?.type.case === "human" - // ? userResponse.user.type.value - // : undefined; - - // if (!challenges.otpEmail && humanUser?.email?.email) { - // challenges = create(RequestChallengesSchema, { - // otpEmail: { deliveryType: { case: "sendCode", value: {} } }, - // }); - // } - - // if (!challenges.otpEmail && humanUser?.email?.email) { - // challenges = create(RequestChallengesSchema, { - // otpSms: { returnCode: true }, - // }); - // } - // } - // } - const session = await setSessionAndUpdateCookie( recent, checks, diff --git a/apps/login/src/ui/LoginOTP.tsx b/apps/login/src/ui/LoginOTP.tsx index 8caf80a7db..0abbbdee21 100644 --- a/apps/login/src/ui/LoginOTP.tsx +++ b/apps/login/src/ui/LoginOTP.tsx @@ -1,7 +1,9 @@ "use client"; -import { ChallengesJson } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; -import { ChecksJson } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { + ChecksJson, + ChecksSchema, +} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; @@ -10,6 +12,9 @@ import BackButton from "./BackButton"; import { Button, ButtonVariants } from "./Button"; import { TextInput } from "./Input"; import { Spinner } from "./Spinner"; +import { create } from "@zitadel/client"; +import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; +import { updateSession } from "@/lib/server/session"; // either loginName or sessionId must be provided type Props = { @@ -63,36 +68,35 @@ export default function LoginOTP({ }, []); async function updateSessionForOTPChallenge() { - const challenges: ChallengesJson = {}; + let challenges; if (method === "email") { - challenges.otpEmail = ""; + challenges = create(RequestChallengesSchema, { + otpEmail: { deliveryType: { case: "sendCode", value: {} } }, + }); } if (method === "sms") { - challenges.otpSms = ""; + challenges = create(RequestChallengesSchema, { + otpSms: { returnCode: true }, + }); } + setLoading(true); - const res = await fetch("/api/session", { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - loginName, - sessionId, - organization, - challenges, - authRequestId, - }), + const response = await updateSession({ + loginName, + sessionId, + organization, + challenges, + authRequestId, + }).catch((error) => { + setError(error.message ?? "Could not request OTP challenge"); + setLoading(false); }); setLoading(false); - if (!res.ok) { - const error = await res.json(); - throw error.details.details; - } - return res.json(); + + return response; } async function submitCode(values: Inputs, organization?: string) { @@ -111,41 +115,38 @@ export default function LoginOTP({ body.authRequestId = authRequestId; } - const checks: ChecksJson = {}; + let checks; + if (method === "sms") { - checks.otpSms = { code: values.code }; + checks = create(ChecksSchema, { + otpSms: { code: values.code }, + }); } if (method === "email") { - checks.otpEmail = { code: values.code }; + checks = create(ChecksSchema, { + otpEmail: { code: values.code }, + }); } if (method === "time-based") { - checks.totp = { code: values.code }; + checks = create(ChecksSchema, { + totp: { code: values.code }, + }); } - const res = await fetch("/api/session", { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - loginName, - sessionId, - organization, - checks, - authRequestId, - }), + const response = await updateSession({ + loginName, + sessionId, + organization, + checks, + authRequestId, + }).catch((error) => { + setError(error.message ?? "Could not verify OTP code"); + setLoading(false); }); setLoading(false); - if (!res.ok) { - const response = await res.json(); - setError(response.details.details ?? "An internal error occurred"); - return Promise.reject( - response.details.details ?? "An internal error occurred", - ); - } - return res.json(); + return response; } function setCodeAndContinue(values: Inputs, organization?: string) { @@ -162,16 +163,13 @@ export default function LoginOTP({ return router.push(`/login?` + params); } else { - const params = new URLSearchParams( - authRequestId - ? { - loginName: response.factors.user.loginName, - authRequestId, - } - : { - loginName: response.factors.user.loginName, - }, - ); + const params = new URLSearchParams(); + if (response?.factors?.user?.loginName) { + params.append("loginName", response.factors.user.loginName); + } + if (authRequestId) { + params.append("authRequestId", authRequestId); + } if (organization) { params.append("organization", organization); @@ -182,8 +180,6 @@ export default function LoginOTP({ }); } - const { errors } = formState; - return ( {["email", "sms"].includes(method) && ( diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index b908525b04..4127d8fcaa 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -108,6 +108,8 @@ export default function PasswordForm({ m !== AuthenticationMethodType.PASSKEY, ); + console.log(availableSecondFactors, loginSettings); + if (availableSecondFactors.length == 1) { const params = new URLSearchParams({ loginName: submitted.factors.user.loginName, From 37dd8d4b38dd1caa046f36dbf7b85296ff7d4a64 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 15:21:29 +0200 Subject: [PATCH 114/640] u2f registration --- apps/login/src/lib/server/u2f.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index 4638336443..3e2fe50d67 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -62,11 +62,12 @@ export async function verifyU2F(command: VerifyU2FCommand) { throw new Error("Could not get session"); } - const req = create( - VerifyU2FRegistrationRequestSchema, - // TODO: why did we passed the request instead of body here? - command, - ); + const req = create(VerifyU2FRegistrationRequestSchema, { + u2fId: command.u2fId, + publicKeyCredential: command.publicKeyCredential, + tokenName: passkeyName, + userId, + }); return verifyU2FRegistration(req); } From d2d1676d224e296397ea7ea127fb98ba60d6c63c Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 15:49:36 +0200 Subject: [PATCH 115/640] readme --- README.md | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 9393dafe5a..54ecedc8f2 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ This repository contains all TypeScript and JavaScript packages and applications you need to create your own ZITADEL Login UI. -The repo makes use of the [build system Turbo](https://turbo.build/repo) and -the [Changesets CLI for versioning the packages](https://github.com/changesets/changesets). **⚠️ This repo and packages are in alpha state and subject to change ⚠️** -The scope of functionality of this repo and packages is limited and under active development. -Once the package structure is set and all APIs are fully implemented we'll move this repo to beta state. +The scope of functionality of this repo and packages is under active development. + +The `@zitadel/client` and `@zitadel/node` packages are using [@connectrpc/connect](https://github.com/connectrpc/connect-es#readme) and its [2.0.0-alpha](https://github.com/connectrpc/connect-es/releases/tag/v2.0.0-alpha.1) release which might still change. + You can read the [contribution guide](/CONTRIBUTING.md) on how to contribute. Questions can be raised in our [Discord channel](https://discord.gg/erh5Brh7jE) or as a [GitHub issue](https://github.com/zitadel/typescript/issues). @@ -28,7 +28,7 @@ We think the easiest path of getting up and running, is the following: ## Included Apps And Packages - `login`: The login UI used by ZITADEL Cloud, powered by Next.js -- `@zitadel/node`: core components for establishing node client connection, grpc stub +- `@zitadel/node`: core components for establishing node client connection - `@zitadel/client`: shared client utilities - `@zitadel/proto`: shared protobuf types - `@zitadel/tsconfig`: shared `tsconfig.json`s used throughout the monorepo @@ -51,13 +51,13 @@ You can already use the current state, and extend it with your needs. - [x] Local User Registration (with Password) - [x] User Registration and Login with external Provider - [x] Google - - [ ] GitHub - - [ ] GitHub Enterprise + - [x] GitHub + - [x] GitHub Enterprise - [x] GitLab - - [ ] GitLab Enterprise - - [ ] Azure + - [x] GitLab Enterprise + - [x] Azure - [ ] Apple - - [ ] Generic OIDC + - [x] Generic OIDC - [ ] Generic OAuth - [ ] Generic JWT - [ ] LDAP @@ -68,7 +68,7 @@ You can already use the current state, and extend it with your needs. - [x] OTP: Email Code - [x] OTP: SMS Code - [ ] Password Change/Reset -- [ ] Domain Discovery +- [x] Domain Discovery - [x] Branding - OIDC Standard - [x] Authorization Code Flow with PKCE @@ -82,7 +82,7 @@ You can already use the current state, and extend it with your needs. - Scopes - [x] `openid email profile address`` - [x] `offline access` - - [ ] `urn:zitadel:iam:org:idp:id:{idp_id}` + - [x] `urn:zitadel:iam:org:idp:id:{idp_id}` - [x] `urn:zitadel:iam:org:project:id:zitadel:aud` - [x] `urn:zitadel:iam:org:id:{orgid}` - [x] `urn:zitadel:iam:org:domain:primary:{domain}` @@ -117,22 +117,6 @@ settings. The [Changesets bot](https://github.com/apps/changeset-bot) should als Read the [changesets documentation](https://github.com/changesets/changesets/blob/main/docs/automating-changesets.md) for more information about this automation -### NPM - -If you want to publish a package to the public npm registry and make them publicly available, this is already setup. - -To publish packages to a private npm organization scope, **remove** the following from each of the `package.json`'s - -```diff -- "publishConfig": { -- "access": "public" -- }, -``` - -### GitHub Package Registry - -See [working with the npm registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry#publishing-a-package-using-publishconfig-in-the-packagejson-file) - ### Run Login UI To run the application make sure to install the dependencies with From 9846ef0946f9547a7b3c5bdab9dd1af2c126b3e9 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 15:52:14 +0200 Subject: [PATCH 116/640] favor passkey setup --- apps/login/src/ui/PasswordForm.tsx | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index 4127d8fcaa..29192acccc 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -148,22 +148,6 @@ export default function PasswordForm({ } return router.push(`/mfa?` + params); - } else if (loginSettings?.forceMfa && !availableSecondFactors.length) { - const params = new URLSearchParams({ - loginName: submitted.factors.user.loginName, - checkAfter: "true", // this defines if the check is directly made after the setup - }); - - if (authRequestId) { - params.append("authRequestId", authRequestId); - } - - if (organization) { - params.append("organization", organization); - } - - // TODO: provide a way to setup passkeys on mfa page? - return router.push(`/mfa/set?` + params); } else if ( submitted.factors && !submitted.factors.webAuthN && // if session was not verified with a passkey @@ -184,6 +168,22 @@ export default function PasswordForm({ } return router.push(`/passkey/add?` + params); + } else if (loginSettings?.forceMfa && !availableSecondFactors.length) { + const params = new URLSearchParams({ + loginName: submitted.factors.user.loginName, + checkAfter: "true", // this defines if the check is directly made after the setup + }); + + if (authRequestId) { + params.append("authRequestId", authRequestId); + } + + if (organization) { + params.append("organization", organization); + } + + // TODO: provide a way to setup passkeys on mfa page? + return router.push(`/mfa/set?` + params); } else if (authRequestId && submitted.sessionId) { const params = new URLSearchParams({ sessionId: submitted.sessionId, From 451cdeee7434f9798c01ab51f39550a7c4181e16 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 15:54:01 +0200 Subject: [PATCH 117/640] lint --- apps/login/src/ui/LoginOTP.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/login/src/ui/LoginOTP.tsx b/apps/login/src/ui/LoginOTP.tsx index 0abbbdee21..0493c6af63 100644 --- a/apps/login/src/ui/LoginOTP.tsx +++ b/apps/login/src/ui/LoginOTP.tsx @@ -1,9 +1,9 @@ "use client"; -import { - ChecksJson, - ChecksSchema, -} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { updateSession } from "@/lib/server/session"; +import { create } from "@zitadel/client"; +import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; +import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; @@ -12,9 +12,6 @@ import BackButton from "./BackButton"; import { Button, ButtonVariants } from "./Button"; import { TextInput } from "./Input"; import { Spinner } from "./Spinner"; -import { create } from "@zitadel/client"; -import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; -import { updateSession } from "@/lib/server/session"; // either loginName or sessionId must be provided type Props = { From 01bd270c5a21a669e5bb8d715a8f2a46885d3f70 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 15:56:45 +0200 Subject: [PATCH 118/640] add jsdom for login --- apps/login/package.json | 1 + pnpm-lock.yaml | 200 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) diff --git a/apps/login/package.json b/apps/login/package.json index 6878332d54..1149ecab97 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -72,6 +72,7 @@ "env-cmd": "^10.1.0", "eslint-config-zitadel": "workspace:*", "grpc-tools": "1.12.4", + "jsdom": "^25.0.0", "lint-staged": "15.2.8", "make-dir-cli": "4.0.0", "nodemon": "^3.1.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14f788fbee..36c5491157 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -165,6 +165,9 @@ importers: grpc-tools: specifier: 1.12.4 version: 1.12.4 + jsdom: + specifier: ^25.0.0 + version: 25.0.0 lint-staged: specifier: 15.2.8 version: 15.2.8 @@ -2103,6 +2106,15 @@ packages: - supports-color dev: true + /agent-base@7.1.1: + resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + engines: {node: '>= 14'} + dependencies: + debug: 4.3.6(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -2819,6 +2831,13 @@ packages: engines: {node: '>=4'} hasBin: true + /cssstyle@4.0.1: + resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} + engines: {node: '>=18'} + dependencies: + rrweb-cssom: 0.6.0 + dev: true + /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} dev: true @@ -2884,6 +2903,14 @@ packages: assert-plus: 1.0.0 dev: true + /data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + dev: true + /data-view-buffer@1.0.1: resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} @@ -2976,6 +3003,10 @@ packages: engines: {node: '>=10'} dev: true + /decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + dev: true + /deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -3171,6 +3202,11 @@ packages: strip-ansi: 6.0.1 dev: true + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: true + /env-cmd@10.1.0: resolution: {integrity: sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==} engines: {node: '>=8.0.0'} @@ -4281,6 +4317,23 @@ packages: lru-cache: 6.0.0 dev: true + /html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + dependencies: + whatwg-encoding: 3.1.1 + dev: true + + /http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.1 + debug: 4.3.6(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /http-signature@1.3.6: resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==} engines: {node: '>=0.10'} @@ -4300,6 +4353,16 @@ packages: - supports-color dev: true + /https-proxy-agent@7.0.5: + resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.1 + debug: 4.3.6(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /human-id@1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} dev: true @@ -4326,6 +4389,13 @@ packages: safer-buffer: 2.1.2 dev: true + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: true + /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true @@ -4558,6 +4628,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + dev: true + /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -4730,6 +4804,42 @@ packages: resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} dev: true + /jsdom@25.0.0: + resolution: {integrity: sha512-OhoFVT59T7aEq75TVw9xxEfkXgacpqAhQaYgP9y/fDqWQCMB/b1H66RfmPm/MaeaAIU9nDwMOVTlPN51+ao6CQ==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + dependencies: + cssstyle: 4.0.1 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.5 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.12 + parse5: 7.1.2 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + /jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} @@ -5369,6 +5479,10 @@ packages: set-blocking: 2.0.0 dev: true + /nwsapi@2.2.12: + resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==} + dev: true + /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -5567,6 +5681,12 @@ packages: lines-and-columns: 1.2.4 dev: true + /parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.5.0 + dev: true + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -6192,6 +6312,14 @@ packages: fsevents: 2.3.3 dev: true + /rrweb-cssom@0.6.0: + resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} + dev: true + + /rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + dev: true + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -6239,6 +6367,13 @@ packages: immutable: 4.3.7 source-map-js: 1.2.0 + /saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + dependencies: + xmlchars: 2.2.0 + dev: true + /scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} dependencies: @@ -6694,6 +6829,10 @@ packages: use-sync-external-store: 1.2.2(react@18.3.1) dev: false + /symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + dev: true + /tailwindcss@3.4.9: resolution: {integrity: sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==} engines: {node: '>=14.0.0'} @@ -6842,6 +6981,13 @@ packages: punycode: 2.3.1 dev: true + /tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + dependencies: + punycode: 2.3.1 + dev: true + /tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -7327,6 +7473,13 @@ packages: - terser dev: true + /w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + dependencies: + xml-name-validator: 5.0.0 + dev: true + /wait-on@7.2.0(debug@4.3.6): resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} engines: {node: '>=12.0.0'} @@ -7349,6 +7502,31 @@ packages: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} dev: true + /webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + dev: true + + /whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + dependencies: + iconv-lite: 0.6.3 + dev: true + + /whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + dev: true + + /whatwg-url@14.0.0: + resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + engines: {node: '>=18'} + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + dev: true + /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: @@ -7491,6 +7669,28 @@ packages: /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + /ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + + /xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + dev: true + + /xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + dev: true + /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} From 88ee769c7ef08c41ee2071d9f05ffca16cdbf83f Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 16:02:33 +0200 Subject: [PATCH 119/640] module --- .prettierignore | 1 + apps/login/package.json | 1 + package.json | 1 + packages/zitadel-tsconfig/package.json | 1 + 4 files changed, 4 insertions(+) diff --git a/.prettierignore b/.prettierignore index 062969d5e4..6d030511ab 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,5 @@ .next/ +.changeset/ dist/ packages/zitadel-proto/google packages/zitadel-proto/protoc-gen-openapiv2 diff --git a/apps/login/package.json b/apps/login/package.json index 1149ecab97..7753f75bf6 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -1,6 +1,7 @@ { "name": "@zitadel/login", "private": true, + "type": "module", "scripts": { "dev": "next dev", "test": "concurrently --timings --kill-others-on-fail 'npm:test:unit' 'npm:test:integration'", diff --git a/package.json b/package.json index 7ebe6a5c45..1cb8dc7110 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "packageManager": "pnpm@9.1.2+sha256.19c17528f9ca20bd442e4ca42f00f1b9808a9cb419383cd04ba32ef19322aba7", "private": true, "name": "typescript-monorepo", + "type": "module", "scripts": { "generate": "turbo run generate", "build": "turbo run build", diff --git a/packages/zitadel-tsconfig/package.json b/packages/zitadel-tsconfig/package.json index b20542f468..238d7fbd70 100644 --- a/packages/zitadel-tsconfig/package.json +++ b/packages/zitadel-tsconfig/package.json @@ -2,6 +2,7 @@ "name": "@zitadel/tsconfig", "version": "0.0.0", "private": true, + "type": "module", "license": "MIT", "publishConfig": { "access": "public" From 2647378464d4dc8159849a3f46e1d9e60d4523d8 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 16:04:46 +0200 Subject: [PATCH 120/640] ignore --- .prettierignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierignore b/.prettierignore index 6d030511ab..a37ac4ff79 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,6 @@ .next/ .changeset/ +.github/ dist/ packages/zitadel-proto/google packages/zitadel-proto/protoc-gen-openapiv2 From 3480a58f2608fe94ae4c9b0373fa6abc8a23c136 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 16:15:07 +0200 Subject: [PATCH 121/640] rm format as we have lint --- .github/workflows/test.yml | 1 - package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 91c78a4db2..7015bb2498 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,6 @@ jobs: fail-fast: false matrix: command: - - format --check - lint - test:unit - test:integration diff --git a/package.json b/package.json index 1cb8dc7110..a778ddfadb 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", "clean": "turbo run clean && rm -rf node_modules", - "format": "prettier --write \"**/*.{ts,tsx,md}\"", "changeset": "changeset", "version-packages": "changeset version", "release": "turbo run build --filter=login^... && changeset publish" From c509323f7dcc3a28b97c1c55176dc93226f2c915 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 16:19:34 +0200 Subject: [PATCH 122/640] reintroduce prettier --- .github/workflows/test.yml | 1 + package.json | 1 + packages/eslint-config-zitadel/package.json | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7015bb2498..91c78a4db2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,6 +17,7 @@ jobs: fail-fast: false matrix: command: + - format --check - lint - test:unit - test:integration diff --git a/package.json b/package.json index a778ddfadb..766f321132 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", "clean": "turbo run clean && rm -rf node_modules", + "format": "prettier --check \"**/*.{ts,tsx,md}\"", "changeset": "changeset", "version-packages": "changeset version", "release": "turbo run build --filter=login^... && changeset publish" diff --git a/packages/eslint-config-zitadel/package.json b/packages/eslint-config-zitadel/package.json index 261744ce00..7a24f98494 100644 --- a/packages/eslint-config-zitadel/package.json +++ b/packages/eslint-config-zitadel/package.json @@ -6,6 +6,7 @@ "publishConfig": { "access": "public" }, + "type":"module", "dependencies": { "eslint-config-next": "^14.2.3", "@typescript-eslint/parser": "^7.9.0", From 93640c2b7a51762a96a95c5a84eb346f95f704b4 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 5 Sep 2024 16:42:05 +0200 Subject: [PATCH 123/640] prettier config in root --- .prettierrc | 6 ++++++ prettier.config.cjs | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .prettierrc delete mode 100644 prettier.config.cjs diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..6d0c388d7a --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "printWidth": 125, + "trailingComma": "all", + "plugins": ["prettier-plugin-organize-imports"] +} + \ No newline at end of file diff --git a/prettier.config.cjs b/prettier.config.cjs deleted file mode 100644 index 6df557c2fd..0000000000 --- a/prettier.config.cjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from "@zitadel/prettier-config"; From 4dc6ab6377280745f09104196dbc3f0f6c7befbc Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 6 Sep 2024 11:13:44 +0200 Subject: [PATCH 124/640] cleanup --- apps/login/src/ui/PasswordForm.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index 29192acccc..d9fbbdf0db 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -108,8 +108,6 @@ export default function PasswordForm({ m !== AuthenticationMethodType.PASSKEY, ); - console.log(availableSecondFactors, loginSettings); - if (availableSecondFactors.length == 1) { const params = new URLSearchParams({ loginName: submitted.factors.user.loginName, @@ -224,7 +222,6 @@ export default function PasswordForm({ autoComplete="password" {...register("password", { required: "This field is required" })} label="Password" - // error={errors.username?.message as string} /> + + + + ); +} diff --git a/apps/login/src/ui/SelfServiceMenu.tsx b/apps/login/src/ui/SelfServiceMenu.tsx new file mode 100644 index 0000000000..2f92436e23 --- /dev/null +++ b/apps/login/src/ui/SelfServiceMenu.tsx @@ -0,0 +1,39 @@ +import Link from "next/link"; + +export default function SelfServiceMenu({ sessionId }: { sessionId: string }) { + const list = [ + { + link: + `/me/change-password?` + + new URLSearchParams({ + sessionId: sessionId, + }), + name: "Change password", + }, + ]; + return ( +
+ {list.map((menuitem, index) => { + return ( + + ); + })} +
+ ); +} + +const SelfServiceItem = ({ name, link }: { name: string; link: string }) => { + return ( + + {name} + + ); +}; From 5f1c86142fe077f8e099387492e3f977da0cddd8 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 10 Sep 2024 08:52:32 +0200 Subject: [PATCH 133/640] cleanup session, change password self service --- .../app/(login)/me/change-password/page.tsx | 27 ++++--- apps/login/src/app/(login)/mfa/page.tsx | 5 +- apps/login/src/app/(login)/mfa/set/page.tsx | 5 +- .../src/app/(login)/passkey/login/page.tsx | 5 +- apps/login/src/app/(login)/signedin/page.tsx | 12 +-- apps/login/src/app/(login)/u2f/page.tsx | 5 +- apps/login/src/lib/zitadel.ts | 8 +- apps/login/src/utils/session.ts | 79 ++++++++++--------- 8 files changed, 85 insertions(+), 61 deletions(-) diff --git a/apps/login/src/app/(login)/me/change-password/page.tsx b/apps/login/src/app/(login)/me/change-password/page.tsx index 5c28c61d26..febd9e0b99 100644 --- a/apps/login/src/app/(login)/me/change-password/page.tsx +++ b/apps/login/src/app/(login)/me/change-password/page.tsx @@ -2,6 +2,7 @@ import { getSessionCookieById } from "@/lib/cookies"; import { getBrandingSettings, getPasswordComplexitySettings, + getSession, } from "@/lib/zitadel"; import Alert from "@/ui/Alert"; import ChangePasswordForm from "@/ui/ChangePasswordForm"; @@ -23,20 +24,22 @@ export default async function Page({ ); } - const session = await getSessionCookieById({ + const sessionCookie = await getSessionCookieById({ sessionId, }); - const sessionFactors = await loadMostRecentSession({ - loginName, - organization, + const { session } = await getSession({ + sessionId: sessionCookie.id, + sessionToken: sessionCookie.token, }); const passwordComplexitySettings = await getPasswordComplexitySettings( - session.organization, + session?.factors?.user?.organizationId, ); - const branding = await getBrandingSettings(session.organization); + const branding = await getBrandingSettings( + session?.factors?.user?.organizationId, + ); return ( @@ -44,7 +47,7 @@ export default async function Page({

Set Password

Set the password for your account

- {(!sessionFactors || !loginName) && ( + {!session && (
Could not get the context of the user. Make sure to enter the @@ -53,19 +56,19 @@ export default async function Page({
)} - {sessionFactors && ( + {session && ( )} - {passwordComplexitySettings && ( + {passwordComplexitySettings && session?.factors?.user?.id && ( )} diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index e86566f690..1101bdb58d 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -46,7 +46,10 @@ export default async function Page({ async function loadSessionById(sessionId: string, organization?: string) { const recent = await getSessionCookieById({ sessionId, organization }); - return getSession(recent.id, recent.token).then((response) => { + return getSession({ + sessionId: recent.id, + sessionToken: recent.token, + }).then((response) => { if (response?.session && response.session.factors?.user?.id) { return listAuthenticationMethodTypes( response.session.factors.user.id, diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index 8af51ef368..def34bb066 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -56,7 +56,10 @@ export default async function Page({ async function loadSessionById(sessionId: string, organization?: string) { const recent = await getSessionCookieById({ sessionId, organization }); - return getSession(recent.id, recent.token).then((response) => { + return getSession({ + sessionId: recent.id, + sessionToken: recent.token, + }).then((response) => { if (response?.session && response.session.factors?.user?.id) { const userId = response.session.factors.user.id; return listAuthenticationMethodTypes(userId).then((methods) => { diff --git a/apps/login/src/app/(login)/passkey/login/page.tsx b/apps/login/src/app/(login)/passkey/login/page.tsx index d681b4ef91..eec5389f70 100644 --- a/apps/login/src/app/(login)/passkey/login/page.tsx +++ b/apps/login/src/app/(login)/passkey/login/page.tsx @@ -24,7 +24,10 @@ export default async function Page({ async function loadSessionById(sessionId: string, organization?: string) { const recent = await getSessionCookieById({ sessionId, organization }); - return getSession(recent.id, recent.token).then((response) => { + return getSession({ + sessionId: recent.id, + sessionToken: recent.token, + }).then((response) => { if (response?.session) { return response.session; } diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index 01c409bb87..13715c2c02 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -29,11 +29,13 @@ async function loadSession(loginName: string, authRequestId?: string) { return redirect(callbackUrl); }); } - return getSession(recent.id, recent.token).then((response) => { - if (response?.session) { - return response.session; - } - }); + return getSession({ sessionId: recent.id, sessionToken: recent.token }).then( + (response) => { + if (response?.session) { + return response.session; + } + }, + ); } export default async function Page({ searchParams }: { searchParams: any }) { diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index 55e99db4d9..0390f1c50f 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -21,7 +21,10 @@ export default async function Page({ async function loadSessionById(sessionId: string, organization?: string) { const recent = await getSessionCookieById({ sessionId, organization }); - return getSession(recent.id, recent.token).then((response) => { + return getSession({ + sessionId: recent.id, + sessionToken: recent.token, + }).then((response) => { if (response?.session) { return response.session; } diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index f3ff11b331..4b4d02e8dc 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -188,7 +188,13 @@ export async function setSession( ); } -export async function getSession(sessionId: string, sessionToken: string) { +export async function getSession({ + sessionId, + sessionToken, +}: { + sessionId: string; + sessionToken: string; +}) { return sessionService.getSession({ sessionId, sessionToken }, {}); } diff --git a/apps/login/src/utils/session.ts b/apps/login/src/utils/session.ts index 5e9e5c5b67..e0b4e2e91a 100644 --- a/apps/login/src/utils/session.ts +++ b/apps/login/src/utils/session.ts @@ -49,10 +49,10 @@ export async function createSessionAndUpdateCookie( const createdSession = await createSessionFromChecks(checks, challenges); if (createdSession) { - return getSession( - createdSession.sessionId, - createdSession.sessionToken, - ).then((response) => { + return getSession({ + sessionId: createdSession.sessionId, + sessionToken: createdSession.sessionToken, + }).then((response) => { if (response?.session && response.session?.factors?.user?.loginName) { const sessionCookie: CustomCookieData = { id: createdSession.sessionId, @@ -103,10 +103,10 @@ export async function createSessionForUserIdAndUpdateCookie( const createdSession = await createSessionFromChecks(checks, challenges); if (createdSession) { - return getSession( - createdSession.sessionId, - createdSession.sessionToken, - ).then((response) => { + return getSession({ + sessionId: createdSession.sessionId, + sessionToken: createdSession.sessionToken, + }).then((response) => { if (response?.session && response.session?.factors?.user?.loginName) { const sessionCookie: CustomCookieData = { id: createdSession.sessionId, @@ -159,10 +159,10 @@ export async function createSessionForIdpAndUpdateCookie( ); if (createdSession) { - return getSession( - createdSession.sessionId, - createdSession.sessionToken, - ).then((response) => { + return getSession({ + sessionId: createdSession.sessionId, + sessionToken: createdSession.sessionToken, + }).then((response) => { if (response?.session && response.session?.factors?.user?.loginName) { const sessionCookie: CustomCookieData = { id: createdSession.sessionId, @@ -234,35 +234,36 @@ export async function setSessionAndUpdateCookie( sessionCookie.authRequestId = authRequestId; } - return getSession(sessionCookie.id, sessionCookie.token).then( - (response) => { - if (response?.session && response.session.factors?.user?.loginName) { - const { session } = response; - const newCookie: CustomCookieData = { - id: sessionCookie.id, - token: updatedSession.sessionToken, - creationDate: sessionCookie.creationDate, - expirationDate: sessionCookie.expirationDate, - // just overwrite the changeDate with the new one - changeDate: updatedSession.details?.changeDate - ? `${timestampDate(updatedSession.details.changeDate).toDateString()}` - : "", - loginName: session.factors?.user?.loginName ?? "", - organization: session.factors?.user?.organizationId ?? "", - }; + return getSession({ + sessionId: sessionCookie.id, + sessionToken: sessionCookie.token, + }).then((response) => { + if (response?.session && response.session.factors?.user?.loginName) { + const { session } = response; + const newCookie: CustomCookieData = { + id: sessionCookie.id, + token: updatedSession.sessionToken, + creationDate: sessionCookie.creationDate, + expirationDate: sessionCookie.expirationDate, + // just overwrite the changeDate with the new one + changeDate: updatedSession.details?.changeDate + ? `${timestampDate(updatedSession.details.changeDate).toDateString()}` + : "", + loginName: session.factors?.user?.loginName ?? "", + organization: session.factors?.user?.organizationId ?? "", + }; - if (sessionCookie.authRequestId) { - newCookie.authRequestId = sessionCookie.authRequestId; - } - - return updateSessionCookie(sessionCookie.id, newCookie).then(() => { - return { challenges: updatedSession.challenges, ...session }; - }); - } else { - throw "could not get session or session does not have loginName"; + if (sessionCookie.authRequestId) { + newCookie.authRequestId = sessionCookie.authRequestId; } - }, - ); + + return updateSessionCookie(sessionCookie.id, newCookie).then(() => { + return { challenges: updatedSession.challenges, ...session }; + }); + } else { + throw "could not get session or session does not have loginName"; + } + }); } else { throw "Session not be set"; } From bb27a1967efeb7a24a7096c218b5a1c4a37687ec Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 10 Sep 2024 08:54:21 +0200 Subject: [PATCH 134/640] fix passkeys --- apps/login/src/lib/server/passkeys.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 3429ce1f16..9c507f9577 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -32,7 +32,10 @@ export async function registerPasskeyLink( const { sessionId } = command; const sessionCookie = await getSessionCookieById({ sessionId }); - const session = await getSession(sessionCookie.id, sessionCookie.token); + const session = await getSession({ + sessionId: sessionCookie.id, + sessionToken: sessionCookie.token, + }); const domain = headers().get("host"); @@ -71,7 +74,10 @@ export async function verifyPasskey(command: VerifyPasskeyCommand) { const sessionCookie = await getSessionCookieById({ sessionId: command.sessionId, }); - const session = await getSession(sessionCookie.id, sessionCookie.token); + const session = await getSession({ + sessionId: sessionCookie.id, + sessionToken: sessionCookie.token, + }); const userId = session?.session?.factors?.user?.id; if (!userId) { From 138b690cb8fabadb81445d4784d502a429303a5a Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 10 Sep 2024 08:58:26 +0200 Subject: [PATCH 135/640] u2f --- apps/login/src/lib/server/u2f.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index 3e2fe50d67..a49df455cd 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -54,7 +54,10 @@ export async function verifyU2F(command: VerifyU2FCommand) { sessionId: command.sessionId, }); - const session = await getSession(sessionCookie.id, sessionCookie.token); + const session = await getSession({ + sessionId: sessionCookie.id, + sessionToken: sessionCookie.token, + }); const userId = session?.session?.factors?.user?.id; From 0678c72ae91a25459ac08c201fd99cfebe250674 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 10 Sep 2024 09:00:31 +0200 Subject: [PATCH 136/640] u2f --- apps/login/src/lib/server/u2f.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index a49df455cd..e8a780449c 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -23,7 +23,10 @@ export async function addU2F(command: RegisterU2FCommand) { sessionId: command.sessionId, }); - const session = await getSession(sessionCookie.id, sessionCookie.token); + const session = await getSession({ + sessionId: sessionCookie.id, + sessionToken: sessionCookie.token, + }); const domain = headers().get("host"); From 531b1fc7080ec0d33428ca0e7a08d6bc691c16eb Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 10 Sep 2024 10:04:43 +0200 Subject: [PATCH 137/640] custom configuration --- apps/login/.gitignore | 1 + apps/login/config.ts | 9 +++++++++ apps/login/custom-config.ts | 9 +++++++++ apps/login/default-config.ts | 23 +++++++++++++++++++++++ apps/login/readme.md | 28 ++++++++++++---------------- apps/login/src/lib/zitadel.ts | 3 ++- 6 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 apps/login/.gitignore create mode 100644 apps/login/config.ts create mode 100644 apps/login/custom-config.ts create mode 100644 apps/login/default-config.ts diff --git a/apps/login/.gitignore b/apps/login/.gitignore new file mode 100644 index 0000000000..6900c06720 --- /dev/null +++ b/apps/login/.gitignore @@ -0,0 +1 @@ +custom-config.json \ No newline at end of file diff --git a/apps/login/config.ts b/apps/login/config.ts new file mode 100644 index 0000000000..8760bc84f1 --- /dev/null +++ b/apps/login/config.ts @@ -0,0 +1,9 @@ +import customConfig from "./custom-config"; +import defaultConfig, { Config } from "./default-config"; + +const config: Config = { + ...defaultConfig, + ...customConfig, +}; + +export default config; diff --git a/apps/login/custom-config.ts b/apps/login/custom-config.ts new file mode 100644 index 0000000000..5bca4613ea --- /dev/null +++ b/apps/login/custom-config.ts @@ -0,0 +1,9 @@ +import { Config } from "./default-config"; + +const customConfig: Partial = { + session: { + lifetime_in_seconds: 3600, + }, +}; + +export default customConfig; diff --git a/apps/login/default-config.ts b/apps/login/default-config.ts new file mode 100644 index 0000000000..8088d8298c --- /dev/null +++ b/apps/login/default-config.ts @@ -0,0 +1,23 @@ +export interface Config { + session: { + lifetime_in_seconds: number; + }; + selfservice: { + change_password: { + enabled: boolean; + }; + }; +} + +const defaultConfig: Config = { + session: { + lifetime_in_seconds: 3600, + }, + selfservice: { + change_password: { + enabled: true, + }, + }, +}; + +export default defaultConfig; diff --git a/apps/login/readme.md b/apps/login/readme.md index e9510a7cf5..95dd61f3d4 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -2,24 +2,20 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 and its introduced `app/` directory. -The Login UI should provide the following functionality: +## Custom Configuration -- **Login API:** Uses the new ZITADEL Login API -- **Server Components:** Making server-first components +You can overwrite the default configuration by creating a `custom-config.ts` file in the root of the project. The `custom-config.ts` file should contain the settings you want to overwrite. -## Running Locally +### Example `custom-config.ts` -1. Install dependencies: `yarn install` -1. Start the dev server: `yarn dev` +```typescript +import { Config } from "./default-config"; -## Documentation +const customConfig: Partial = { + session: { + lifetime_in_seconds: 7200, + }, +}; -https://beta.nextjs.org/docs - - +export default customConfig; +``` diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 4b4d02e8dc..e2849e0df2 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -17,6 +17,7 @@ import { VerifyU2FRegistrationRequest, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import config from "@/../config"; import { create } from "@zitadel/client"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; @@ -28,7 +29,7 @@ import { import { unstable_cache } from "next/cache"; import { PROVIDER_MAPPING } from "./idp"; -const SESSION_LIFETIME_S = 3000; +const SESSION_LIFETIME_S = config.session.lifetime_in_seconds; const transport = createServerTransport( process.env.ZITADEL_SERVICE_USER_TOKEN!, From 1bd632ad7bd3077f7296b7633bf3cdf3ee70001b Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 10 Sep 2024 10:09:16 +0200 Subject: [PATCH 138/640] rm custom config --- apps/login/custom-config.ts | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 apps/login/custom-config.ts diff --git a/apps/login/custom-config.ts b/apps/login/custom-config.ts deleted file mode 100644 index 5bca4613ea..0000000000 --- a/apps/login/custom-config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Config } from "./default-config"; - -const customConfig: Partial = { - session: { - lifetime_in_seconds: 3600, - }, -}; - -export default customConfig; From 386ed7d297ac239996fa13e563f92b4a4e8fc261 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 10 Sep 2024 11:57:09 +0200 Subject: [PATCH 139/640] config --- apps/login/.gitignore | 2 +- apps/login/config.ts | 20 +++++++++++++++++++- apps/login/custom-config.js | 12 ++++++++++++ apps/login/src/lib/zitadel.ts | 2 ++ apps/login/src/ui/Boundary.tsx | 2 +- apps/login/src/ui/SelfServiceMenu.tsx | 24 ++++++++++++++---------- apps/login/tsconfig.json | 8 +++++++- 7 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 apps/login/custom-config.js diff --git a/apps/login/.gitignore b/apps/login/.gitignore index 6900c06720..190545517a 100644 --- a/apps/login/.gitignore +++ b/apps/login/.gitignore @@ -1 +1 @@ -custom-config.json \ No newline at end of file +custom-config.ts \ No newline at end of file diff --git a/apps/login/config.ts b/apps/login/config.ts index 8760bc84f1..a7181b074f 100644 --- a/apps/login/config.ts +++ b/apps/login/config.ts @@ -1,6 +1,24 @@ -import customConfig from "./custom-config"; +import fs from "fs"; +import path from "path"; import defaultConfig, { Config } from "./default-config"; +const customConfigPath = path.resolve(process.cwd(), "custom-config.js"); + +let customConfig: Partial = {}; + +if (fs.existsSync(customConfigPath)) { + import(customConfigPath) + .then((module) => { + customConfig = module.default; + console.log("found", customConfig); + }) + .catch((error) => { + console.error("Error loading custom configuration:", error); + }); +} else { + console.info("No custom configuration file found!"); +} + const config: Config = { ...defaultConfig, ...customConfig, diff --git a/apps/login/custom-config.js b/apps/login/custom-config.js new file mode 100644 index 0000000000..e08189f07f --- /dev/null +++ b/apps/login/custom-config.js @@ -0,0 +1,12 @@ +const customConfig = { + session: { + lifetime_in_seconds: 7200, + }, + selfservice: { + change_password: { + enabled: false, + }, + }, +}; + +module.exports = customConfig; diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index e2849e0df2..e61e255b8a 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -31,6 +31,8 @@ import { PROVIDER_MAPPING } from "./idp"; const SESSION_LIFETIME_S = config.session.lifetime_in_seconds; +console.log("Session lifetime", SESSION_LIFETIME_S); + const transport = createServerTransport( process.env.ZITADEL_SERVICE_USER_TOKEN!, { diff --git a/apps/login/src/ui/Boundary.tsx b/apps/login/src/ui/Boundary.tsx index c7487df1a9..ef06be3fdf 100644 --- a/apps/login/src/ui/Boundary.tsx +++ b/apps/login/src/ui/Boundary.tsx @@ -44,7 +44,7 @@ export const Boundary = ({
{list.map((menuitem, index) => { diff --git a/apps/login/tsconfig.json b/apps/login/tsconfig.json index bf3a62f57d..a1efe752c5 100755 --- a/apps/login/tsconfig.json +++ b/apps/login/tsconfig.json @@ -12,6 +12,12 @@ } ] }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "custom-config.js" + ], "exclude": ["node_modules"] } From 1466bb3b7083340f9a8f2feed9c65602d7ff4371 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 10 Sep 2024 13:54:09 +0200 Subject: [PATCH 140/640] rm custom config, fix password reset --- apps/login/.gitignore | 2 +- apps/login/custom-config.js | 12 ------------ apps/login/default-config.ts | 2 +- apps/login/src/lib/server/password.ts | 4 ++-- apps/login/src/ui/PasswordForm.tsx | 15 ++++++++++++++- 5 files changed, 18 insertions(+), 17 deletions(-) delete mode 100644 apps/login/custom-config.js diff --git a/apps/login/.gitignore b/apps/login/.gitignore index 190545517a..e2a663b546 100644 --- a/apps/login/.gitignore +++ b/apps/login/.gitignore @@ -1 +1 @@ -custom-config.ts \ No newline at end of file +custom-config.js \ No newline at end of file diff --git a/apps/login/custom-config.js b/apps/login/custom-config.js deleted file mode 100644 index e08189f07f..0000000000 --- a/apps/login/custom-config.js +++ /dev/null @@ -1,12 +0,0 @@ -const customConfig = { - session: { - lifetime_in_seconds: 7200, - }, - selfservice: { - change_password: { - enabled: false, - }, - }, -}; - -module.exports = customConfig; diff --git a/apps/login/default-config.ts b/apps/login/default-config.ts index 8088d8298c..e5a1388fad 100644 --- a/apps/login/default-config.ts +++ b/apps/login/default-config.ts @@ -15,7 +15,7 @@ const defaultConfig: Config = { }, selfservice: { change_password: { - enabled: true, + enabled: false, }, }, }; diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 756f41f671..020630cf1e 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -15,8 +15,8 @@ export async function resetPassword(command: ResetPasswordCommand) { if ( !users.details || - Number(users.details.totalResult) !== 1 || - users.result[0].userId + users.details.totalResult !== BigInt(1) || + !users.result[0].userId ) { throw Error("Could not find user"); } diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index d9fbbdf0db..4afa89c320 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -9,7 +9,7 @@ import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_se import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; -import Alert from "./Alert"; +import Alert, { AlertType } from "./Alert"; import BackButton from "./BackButton"; import { Button, ButtonVariants } from "./Button"; import { TextInput } from "./Input"; @@ -40,6 +40,7 @@ export default function PasswordForm({ mode: "onBlur", }); + const [info, setInfo] = useState(""); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); @@ -69,6 +70,7 @@ export default function PasswordForm({ async function resetPasswordAndContinue() { setError(""); + setInfo(""); setLoading(true); const response = await resetPassword({ @@ -81,6 +83,10 @@ export default function PasswordForm({ setLoading(false); + if (response) { + setInfo("Password was reset. Please check your email."); + } + return response; } @@ -88,6 +94,7 @@ export default function PasswordForm({ value: Inputs, ): Promise { const submitted = await submitPassword(value); + setInfo(""); // if user has mfa -> /otp/[method] or /u2f // if mfa is forced and user has no mfa -> /mfa/set // if no passwordless -> /passkey/add @@ -242,6 +249,12 @@ export default function PasswordForm({ )}
+ {info && ( +
+ {info} +
+ )} + {error && (
{error} From 3d33b36473e8bbde80f17312bf777a7e55332dc7 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 10 Sep 2024 13:56:44 +0200 Subject: [PATCH 141/640] warning --- apps/login/config.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/login/config.ts b/apps/login/config.ts index a7181b074f..b5f8500f5b 100644 --- a/apps/login/config.ts +++ b/apps/login/config.ts @@ -10,10 +10,9 @@ if (fs.existsSync(customConfigPath)) { import(customConfigPath) .then((module) => { customConfig = module.default; - console.log("found", customConfig); }) .catch((error) => { - console.error("Error loading custom configuration:", error); + console.warn("Error loading custom configuration:", error); }); } else { console.info("No custom configuration file found!"); From e4739b8812ee546d38ee90fa1317334bb154634b Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 10 Sep 2024 13:57:09 +0200 Subject: [PATCH 142/640] readme --- apps/login/readme.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 95dd61f3d4..91cca2b6cc 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -8,14 +8,17 @@ You can overwrite the default configuration by creating a `custom-config.ts` fil ### Example `custom-config.ts` -```typescript -import { Config } from "./default-config"; - -const customConfig: Partial = { +```js +const customConfig = { session: { lifetime_in_seconds: 7200, }, + selfservice: { + change_password: { + enabled: false, + }, + }, }; -export default customConfig; +module.exports = customConfig; ``` From 32e1e4bfb608bef6dc36d5e3e48625b49ebb1a68 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 10 Sep 2024 14:32:20 +0200 Subject: [PATCH 143/640] register with params --- apps/login/src/ui/LoginPasskey.tsx | 1 + apps/login/src/ui/UsernameForm.tsx | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/login/src/ui/LoginPasskey.tsx b/apps/login/src/ui/LoginPasskey.tsx index 3c94feecdb..448d0a8220 100644 --- a/apps/login/src/ui/LoginPasskey.tsx +++ b/apps/login/src/ui/LoginPasskey.tsx @@ -76,6 +76,7 @@ export default function LoginPasskey({ ? UserVerificationRequirement.REQUIRED : UserVerificationRequirement.DISCOURAGED, ) { + setError(""); setLoading(true); const session = await updateSession({ loginName, diff --git a/apps/login/src/ui/UsernameForm.tsx b/apps/login/src/ui/UsernameForm.tsx index 335b949abe..7d95226d13 100644 --- a/apps/login/src/ui/UsernameForm.tsx +++ b/apps/login/src/ui/UsernameForm.tsx @@ -205,7 +205,17 @@ export default function UsernameForm({ {allowRegister && ( + +
diff --git a/apps/login/src/ui/ChangePasswordForm.tsx b/apps/login/src/ui/ChangePasswordForm.tsx index 68ca8caee0..72d8929894 100644 --- a/apps/login/src/ui/ChangePasswordForm.tsx +++ b/apps/login/src/ui/ChangePasswordForm.tsx @@ -12,6 +12,7 @@ import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; import Alert from "./Alert"; +import BackButton from "./BackButton"; import { Button, ButtonVariants } from "./Button"; import { TextInput } from "./Input"; import PasswordComplexity from "./PasswordComplexity"; @@ -129,9 +130,7 @@ export default function ChangePasswordForm({ {error && {error}}
- + + ), +); + +SignInWithApple.displayName = "SignInWithApple"; diff --git a/apps/login/src/ui/idps/SignInWithGeneric.tsx b/apps/login/src/ui/idps/SignInWithGeneric.tsx new file mode 100644 index 0000000000..1ebc2d9489 --- /dev/null +++ b/apps/login/src/ui/idps/SignInWithGeneric.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { ReactNode, forwardRef } from "react"; +import { IdpButtonClasses, SignInWithIdentityProviderProps } from "./classes"; + +export const SignInWithGeneric = forwardRef< + HTMLButtonElement, + SignInWithIdentityProviderProps +>( + ( + { children, className = "h-[50px] pl-20", name = "", ...props }, + ref, + ): ReactNode => ( + + ), +); + +SignInWithGeneric.displayName = "SignInWithGeneric"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ff6d66353..8f3ceeddc7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true @@ -289,1516 +289,851 @@ importers: packages: - /@adobe/css-tools@4.4.0: + '@adobe/css-tools@4.4.0': resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} - dev: true - /@alloc/quick-lru@5.2.0: + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - /@ampproject/remapping@2.3.0: + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - /@babel/code-frame@7.24.7: + '@babel/code-frame@7.24.7': resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.0.1 - /@babel/compat-data@7.25.4: + '@babel/compat-data@7.25.4': resolution: {integrity: sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==} engines: {node: '>=6.9.0'} - /@babel/core@7.25.2: + '@babel/core@7.25.2': resolution: {integrity: sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==} engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.6 - '@babel/helper-compilation-targets': 7.25.2 - '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) - '@babel/helpers': 7.25.6 - '@babel/parser': 7.25.6 - '@babel/template': 7.25.0 - '@babel/traverse': 7.25.6 - '@babel/types': 7.25.6 - convert-source-map: 2.0.0 - debug: 4.3.6(supports-color@5.5.0) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - /@babel/generator@7.25.6: + '@babel/generator@7.25.6': resolution: {integrity: sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.25.6 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 - /@babel/helper-compilation-targets@7.25.2: + '@babel/helper-compilation-targets@7.25.2': resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/compat-data': 7.25.4 - '@babel/helper-validator-option': 7.24.8 - browserslist: 4.23.3 - lru-cache: 5.1.1 - semver: 6.3.1 - /@babel/helper-module-imports@7.24.7: + '@babel/helper-module-imports@7.24.7': resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/traverse': 7.25.6 - '@babel/types': 7.25.6 - transitivePeerDependencies: - - supports-color - /@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2): + '@babel/helper-module-transforms@7.25.2': resolution: {integrity: sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - '@babel/traverse': 7.25.6 - transitivePeerDependencies: - - supports-color - /@babel/helper-plugin-utils@7.24.8: + '@babel/helper-plugin-utils@7.24.8': resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==} engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-simple-access@7.24.7: + '@babel/helper-simple-access@7.24.7': resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/traverse': 7.25.6 - '@babel/types': 7.25.6 - transitivePeerDependencies: - - supports-color - /@babel/helper-string-parser@7.24.8: + '@babel/helper-string-parser@7.24.8': resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-identifier@7.24.7: + '@babel/helper-validator-identifier@7.24.7': resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-option@7.24.8: + '@babel/helper-validator-option@7.24.8': resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} engines: {node: '>=6.9.0'} - /@babel/helpers@7.25.6: + '@babel/helpers@7.25.6': resolution: {integrity: sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.25.0 - '@babel/types': 7.25.6 - /@babel/highlight@7.24.7: + '@babel/highlight@7.24.7': resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.24.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.0.1 - /@babel/parser@7.25.6: + '@babel/parser@7.25.6': resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} engines: {node: '>=6.0.0'} hasBin: true - dependencies: - '@babel/types': 7.25.6 - /@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.2): + '@babel/plugin-transform-react-jsx-self@7.24.7': resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - dev: true - /@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.2): + '@babel/plugin-transform-react-jsx-source@7.24.7': resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - dev: true - /@babel/runtime@7.25.6: + '@babel/runtime@7.25.6': resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.14.1 - dev: true - /@babel/template@7.25.0: + '@babel/template@7.25.0': resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 - /@babel/traverse@7.25.6: + '@babel/traverse@7.25.6': resolution: {integrity: sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.6 - '@babel/parser': 7.25.6 - '@babel/template': 7.25.0 - '@babel/types': 7.25.6 - debug: 4.3.6(supports-color@5.5.0) - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - /@babel/types@7.25.6: + '@babel/types@7.25.6': resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.24.8 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 - /@bufbuild/buf-darwin-arm64@1.39.0: + '@bufbuild/buf-darwin-arm64@1.39.0': resolution: {integrity: sha512-Ptl0uAGssLxQTzoZhGwv1FFTbzUfcstIpEwMhN+XrwiuqsSxOg9eq/n3yXoci5VJsHokjDUHnWkR3y+j5P/5KA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@bufbuild/buf-darwin-x64@1.39.0: + '@bufbuild/buf-darwin-x64@1.39.0': resolution: {integrity: sha512-XNCuy9sjQwVJ4NIZqxaTIyzUtlyquSkp/Uuoh5W5thJ3nzZ5RSgvXKF5iXHhZmesrfRGApktwoCx5Am8runsfQ==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@bufbuild/buf-linux-aarch64@1.39.0: + '@bufbuild/buf-linux-aarch64@1.39.0': resolution: {integrity: sha512-Am+hrw94awp/eY027ROXwRQBuwAzOpQ/4zI4dgmgsyhzeWZ8w1LWC8z2SSr8T2cqd0cm52KxtoWMW+B3b2qzbw==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@bufbuild/buf-linux-x64@1.39.0: + '@bufbuild/buf-linux-x64@1.39.0': resolution: {integrity: sha512-JXVkHoMrTvmpseqdoQPJJ6MRV7/vlloYtvXHHACEzVytYjljOYCNoVET/E5gLBco/edeXFMNc40cCi1KgL3rSw==} engines: {node: '>=12'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@bufbuild/buf-win32-arm64@1.39.0: + '@bufbuild/buf-win32-arm64@1.39.0': resolution: {integrity: sha512-akdGW02mo04wbLfjNMBQqxC4mPQ/L/vTU8/o79I67GSxyFYt7bKifvYIYhAA39C2gibHyB7ZLmoeRPbaU8wbYA==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@bufbuild/buf-win32-x64@1.39.0: + '@bufbuild/buf-win32-x64@1.39.0': resolution: {integrity: sha512-jos08UMg9iUZsGjPrNpLXP+FNk6q6GizO+bjee/GcI0kSijIzXYMg14goQr0TKlvqs/+IRAM5vZIokQBYlAENQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@bufbuild/buf@1.39.0: + '@bufbuild/buf@1.39.0': resolution: {integrity: sha512-lm7xb9pc7X04rRjCQ69o9byAAZ7/dsUQGoH+iJ9uBSXQWiwQ1Ts8gneBnuUVsAH2vdW73NFBpmNQGE9XtFauVQ==} engines: {node: '>=12'} hasBin: true - requiresBuild: true - optionalDependencies: - '@bufbuild/buf-darwin-arm64': 1.39.0 - '@bufbuild/buf-darwin-x64': 1.39.0 - '@bufbuild/buf-linux-aarch64': 1.39.0 - '@bufbuild/buf-linux-x64': 1.39.0 - '@bufbuild/buf-win32-arm64': 1.39.0 - '@bufbuild/buf-win32-x64': 1.39.0 - dev: true - /@bufbuild/protobuf@2.0.0: + '@bufbuild/protobuf@2.0.0': resolution: {integrity: sha512-sw2JhwJyvyL0zlhG61aDzOVryEfJg2PDZFSV7i7IdC7nAE41WuXCru3QWLGiP87At0BMzKOoKO/FqEGoKygGZQ==} - dev: false - /@changesets/apply-release-plan@7.0.4: + '@changesets/apply-release-plan@7.0.4': resolution: {integrity: sha512-HLFwhKWayKinWAul0Vj+76jVx1Pc2v55MGPVjZ924Y/ROeSsBMFutv9heHmCUj48lJyRfOTJG5+ar+29FUky/A==} - dependencies: - '@babel/runtime': 7.25.6 - '@changesets/config': 3.0.2 - '@changesets/get-version-range-type': 0.4.0 - '@changesets/git': 3.0.0 - '@changesets/should-skip-package': 0.1.0 - '@changesets/types': 6.0.0 - '@manypkg/get-packages': 1.1.3 - detect-indent: 6.1.0 - fs-extra: 7.0.1 - lodash.startcase: 4.4.0 - outdent: 0.5.0 - prettier: 2.8.8 - resolve-from: 5.0.0 - semver: 7.6.3 - dev: true - /@changesets/assemble-release-plan@6.0.3: + '@changesets/assemble-release-plan@6.0.3': resolution: {integrity: sha512-bLNh9/Lgl1VwkjWZTq8JmRqH+hj7/Yzfz0jsQ/zJJ+FTmVqmqPj3szeKOri8O/hEM8JmHW019vh2gTO9iq5Cuw==} - dependencies: - '@babel/runtime': 7.25.6 - '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.1 - '@changesets/should-skip-package': 0.1.0 - '@changesets/types': 6.0.0 - '@manypkg/get-packages': 1.1.3 - semver: 7.6.3 - dev: true - /@changesets/changelog-git@0.2.0: + '@changesets/changelog-git@0.2.0': resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} - dependencies: - '@changesets/types': 6.0.0 - dev: true - /@changesets/cli@2.27.7: + '@changesets/cli@2.27.7': resolution: {integrity: sha512-6lr8JltiiXPIjDeYg4iM2MeePP6VN/JkmqBsVA5XRiy01hGS3y629LtSDvKcycj/w/5Eur1rEwby/MjcYS+e2A==} hasBin: true - dependencies: - '@babel/runtime': 7.25.6 - '@changesets/apply-release-plan': 7.0.4 - '@changesets/assemble-release-plan': 6.0.3 - '@changesets/changelog-git': 0.2.0 - '@changesets/config': 3.0.2 - '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.1 - '@changesets/get-release-plan': 4.0.3 - '@changesets/git': 3.0.0 - '@changesets/logger': 0.1.0 - '@changesets/pre': 2.0.0 - '@changesets/read': 0.6.0 - '@changesets/should-skip-package': 0.1.0 - '@changesets/types': 6.0.0 - '@changesets/write': 0.3.1 - '@manypkg/get-packages': 1.1.3 - '@types/semver': 7.5.8 - ansi-colors: 4.1.3 - chalk: 2.4.2 - ci-info: 3.9.0 - enquirer: 2.4.1 - external-editor: 3.1.0 - fs-extra: 7.0.1 - human-id: 1.0.2 - mri: 1.2.0 - outdent: 0.5.0 - p-limit: 2.3.0 - preferred-pm: 3.1.4 - resolve-from: 5.0.0 - semver: 7.6.3 - spawndamnit: 2.0.0 - term-size: 2.2.1 - dev: true - /@changesets/config@3.0.2: + '@changesets/config@3.0.2': resolution: {integrity: sha512-cdEhS4t8woKCX2M8AotcV2BOWnBp09sqICxKapgLHf9m5KdENpWjyrFNMjkLqGJtUys9U+w93OxWT0czorVDfw==} - dependencies: - '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.1 - '@changesets/logger': 0.1.0 - '@changesets/types': 6.0.0 - '@manypkg/get-packages': 1.1.3 - fs-extra: 7.0.1 - micromatch: 4.0.8 - dev: true - /@changesets/errors@0.2.0: + '@changesets/errors@0.2.0': resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} - dependencies: - extendable-error: 0.1.7 - dev: true - /@changesets/get-dependents-graph@2.1.1: + '@changesets/get-dependents-graph@2.1.1': resolution: {integrity: sha512-LRFjjvigBSzfnPU2n/AhFsuWR5DK++1x47aq6qZ8dzYsPtS/I5mNhIGAS68IAxh1xjO9BTtz55FwefhANZ+FCA==} - dependencies: - '@changesets/types': 6.0.0 - '@manypkg/get-packages': 1.1.3 - chalk: 2.4.2 - fs-extra: 7.0.1 - semver: 7.6.3 - dev: true - /@changesets/get-release-plan@4.0.3: + '@changesets/get-release-plan@4.0.3': resolution: {integrity: sha512-6PLgvOIwTSdJPTtpdcr3sLtGatT+Jr22+cQwEBJBy6wP0rjB4yJ9lv583J9fVpn1bfQlBkDa8JxbS2g/n9lIyA==} - dependencies: - '@babel/runtime': 7.25.6 - '@changesets/assemble-release-plan': 6.0.3 - '@changesets/config': 3.0.2 - '@changesets/pre': 2.0.0 - '@changesets/read': 0.6.0 - '@changesets/types': 6.0.0 - '@manypkg/get-packages': 1.1.3 - dev: true - /@changesets/get-version-range-type@0.4.0: + '@changesets/get-version-range-type@0.4.0': resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} - dev: true - /@changesets/git@3.0.0: + '@changesets/git@3.0.0': resolution: {integrity: sha512-vvhnZDHe2eiBNRFHEgMiGd2CT+164dfYyrJDhwwxTVD/OW0FUD6G7+4DIx1dNwkwjHyzisxGAU96q0sVNBns0w==} - dependencies: - '@babel/runtime': 7.25.6 - '@changesets/errors': 0.2.0 - '@changesets/types': 6.0.0 - '@manypkg/get-packages': 1.1.3 - is-subdir: 1.2.0 - micromatch: 4.0.8 - spawndamnit: 2.0.0 - dev: true - /@changesets/logger@0.1.0: + '@changesets/logger@0.1.0': resolution: {integrity: sha512-pBrJm4CQm9VqFVwWnSqKEfsS2ESnwqwH+xR7jETxIErZcfd1u2zBSqrHbRHR7xjhSgep9x2PSKFKY//FAshA3g==} - dependencies: - chalk: 2.4.2 - dev: true - /@changesets/parse@0.4.0: + '@changesets/parse@0.4.0': resolution: {integrity: sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==} - dependencies: - '@changesets/types': 6.0.0 - js-yaml: 3.14.1 - dev: true - /@changesets/pre@2.0.0: + '@changesets/pre@2.0.0': resolution: {integrity: sha512-HLTNYX/A4jZxc+Sq8D1AMBsv+1qD6rmmJtjsCJa/9MSRybdxh0mjbTvE6JYZQ/ZiQ0mMlDOlGPXTm9KLTU3jyw==} - dependencies: - '@babel/runtime': 7.25.6 - '@changesets/errors': 0.2.0 - '@changesets/types': 6.0.0 - '@manypkg/get-packages': 1.1.3 - fs-extra: 7.0.1 - dev: true - /@changesets/read@0.6.0: + '@changesets/read@0.6.0': resolution: {integrity: sha512-ZypqX8+/im1Fm98K4YcZtmLKgjs1kDQ5zHpc2U1qdtNBmZZfo/IBiG162RoP0CUF05tvp2y4IspH11PLnPxuuw==} - dependencies: - '@babel/runtime': 7.25.6 - '@changesets/git': 3.0.0 - '@changesets/logger': 0.1.0 - '@changesets/parse': 0.4.0 - '@changesets/types': 6.0.0 - chalk: 2.4.2 - fs-extra: 7.0.1 - p-filter: 2.1.0 - dev: true - /@changesets/should-skip-package@0.1.0: + '@changesets/should-skip-package@0.1.0': resolution: {integrity: sha512-FxG6Mhjw7yFStlSM7Z0Gmg3RiyQ98d/9VpQAZ3Fzr59dCOM9G6ZdYbjiSAt0XtFr9JR5U2tBaJWPjrkGGc618g==} - dependencies: - '@babel/runtime': 7.25.6 - '@changesets/types': 6.0.0 - '@manypkg/get-packages': 1.1.3 - dev: true - /@changesets/types@4.1.0: + '@changesets/types@4.1.0': resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} - dev: true - /@changesets/types@6.0.0: + '@changesets/types@6.0.0': resolution: {integrity: sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==} - dev: true - /@changesets/write@0.3.1: + '@changesets/write@0.3.1': resolution: {integrity: sha512-SyGtMXzH3qFqlHKcvFY2eX+6b0NGiFcNav8AFsYwy5l8hejOeoeTDemu5Yjmke2V5jpzY+pBvM0vCCQ3gdZpfw==} - dependencies: - '@babel/runtime': 7.25.6 - '@changesets/types': 6.0.0 - fs-extra: 7.0.1 - human-id: 1.0.2 - prettier: 2.8.8 - dev: true - /@colors/colors@1.5.0: + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - requiresBuild: true - dev: true - optional: true - /@connectrpc/connect-node@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1): + '@connectrpc/connect-node@2.0.0-alpha.1': resolution: {integrity: sha512-8V4iVs0nBU3lQCaKt6KmJeaGRvfjyUqJ4XZbMQDxP45xyvEZOOSsUCzeoJCMMhRAWEKzguPkYZI8B4RmepPREQ==} engines: {node: '>=16.0.0'} peerDependencies: '@bufbuild/protobuf': ^2.0.0-beta.2 '@connectrpc/connect': 2.0.0-alpha.1 - dependencies: - '@bufbuild/protobuf': 2.0.0 - '@connectrpc/connect': 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0) - undici: 5.28.4 - dev: false - /@connectrpc/connect-web@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1): + '@connectrpc/connect-web@2.0.0-alpha.1': resolution: {integrity: sha512-Mj/mTulRuFppcy8kRyIbyXP5Pm31Pe6br2ExCyJkk47FtsBL6iarbBAUOoeEv9GLdJlkuLngZoQb4xK86VAp5Q==} peerDependencies: '@bufbuild/protobuf': ^2.0.0-beta.2 '@connectrpc/connect': 2.0.0-alpha.1 - dependencies: - '@bufbuild/protobuf': 2.0.0 - '@connectrpc/connect': 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0) - dev: false - /@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0): + '@connectrpc/connect@2.0.0-alpha.1': resolution: {integrity: sha512-eAjezEy1m+7bDtWt/TeXY2qIayq7R+WW8Jqwq3p8Q7ZTHpFJu0SBQivhRGQbNXSYR+QmqMk+j/FLAVlgB6CfHA==} peerDependencies: '@bufbuild/protobuf': ^2.0.0-beta.2 - dependencies: - '@bufbuild/protobuf': 2.0.0 - dev: false - /@cypress/request@3.0.1: + '@cypress/request@3.0.1': resolution: {integrity: sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==} engines: {node: '>= 6'} - dependencies: - aws-sign2: 0.7.0 - aws4: 1.13.2 - caseless: 0.12.0 - combined-stream: 1.0.8 - extend: 3.0.2 - forever-agent: 0.6.1 - form-data: 2.3.3 - http-signature: 1.3.6 - is-typedarray: 1.0.0 - isstream: 0.1.2 - json-stringify-safe: 5.0.1 - mime-types: 2.1.35 - performance-now: 2.1.0 - qs: 6.10.4 - safe-buffer: 5.2.1 - tough-cookie: 4.1.4 - tunnel-agent: 0.6.0 - uuid: 8.3.2 - dev: true - /@cypress/xvfb@1.2.4(supports-color@8.1.1): + '@cypress/xvfb@1.2.4': resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} - dependencies: - debug: 3.2.7(supports-color@8.1.1) - lodash.once: 4.1.1 - transitivePeerDependencies: - - supports-color - dev: true - /@esbuild/aix-ppc64@0.21.5: + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - requiresBuild: true - dev: true - optional: true - /@esbuild/aix-ppc64@0.23.1: + '@esbuild/aix-ppc64@0.23.1': resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm64@0.21.5: + '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm64@0.23.1: + '@esbuild/android-arm64@0.23.1': resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} engines: {node: '>=18'} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm@0.21.5: + '@esbuild/android-arm@0.21.5': resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm@0.23.1: + '@esbuild/android-arm@0.23.1': resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-x64@0.21.5: + '@esbuild/android-x64@0.21.5': resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-x64@0.23.1: + '@esbuild/android-x64@0.23.1': resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} engines: {node: '>=18'} cpu: [x64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-arm64@0.21.5: + '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-arm64@0.23.1: + '@esbuild/darwin-arm64@0.23.1': resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-x64@0.21.5: + '@esbuild/darwin-x64@0.21.5': resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-x64@0.23.1: + '@esbuild/darwin-x64@0.23.1': resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-arm64@0.21.5: + '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-arm64@0.23.1: + '@esbuild/freebsd-arm64@0.23.1': resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-x64@0.21.5: + '@esbuild/freebsd-x64@0.21.5': resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-x64@0.23.1: + '@esbuild/freebsd-x64@0.23.1': resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm64@0.21.5: + '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm64@0.23.1: + '@esbuild/linux-arm64@0.23.1': resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm@0.21.5: + '@esbuild/linux-arm@0.21.5': resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm@0.23.1: + '@esbuild/linux-arm@0.23.1': resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ia32@0.21.5: + '@esbuild/linux-ia32@0.21.5': resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ia32@0.23.1: + '@esbuild/linux-ia32@0.23.1': resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-loong64@0.21.5: + '@esbuild/linux-loong64@0.21.5': resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-loong64@0.23.1: + '@esbuild/linux-loong64@0.23.1': resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-mips64el@0.21.5: + '@esbuild/linux-mips64el@0.21.5': resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-mips64el@0.23.1: + '@esbuild/linux-mips64el@0.23.1': resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ppc64@0.21.5: + '@esbuild/linux-ppc64@0.21.5': resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ppc64@0.23.1: + '@esbuild/linux-ppc64@0.23.1': resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-riscv64@0.21.5: + '@esbuild/linux-riscv64@0.21.5': resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-riscv64@0.23.1: + '@esbuild/linux-riscv64@0.23.1': resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-s390x@0.21.5: + '@esbuild/linux-s390x@0.21.5': resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-s390x@0.23.1: + '@esbuild/linux-s390x@0.23.1': resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-x64@0.21.5: + '@esbuild/linux-x64@0.21.5': resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-x64@0.23.1: + '@esbuild/linux-x64@0.23.1': resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-x64@0.21.5: + '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-x64@0.23.1: + '@esbuild/netbsd-x64@0.23.1': resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-arm64@0.23.1: + '@esbuild/openbsd-arm64@0.23.1': resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-x64@0.21.5: + '@esbuild/openbsd-x64@0.21.5': resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-x64@0.23.1: + '@esbuild/openbsd-x64@0.23.1': resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/sunos-x64@0.21.5: + '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - requiresBuild: true - dev: true - optional: true - /@esbuild/sunos-x64@0.23.1: + '@esbuild/sunos-x64@0.23.1': resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-arm64@0.21.5: + '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-arm64@0.23.1: + '@esbuild/win32-arm64@0.23.1': resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-ia32@0.21.5: + '@esbuild/win32-ia32@0.21.5': resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-ia32@0.23.1: + '@esbuild/win32-ia32@0.23.1': resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-x64@0.21.5: + '@esbuild/win32-x64@0.21.5': resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-x64@0.23.1: + '@esbuild/win32-x64@0.23.1': resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} engines: {node: '>=18'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.57.0 - eslint-visitor-keys: 3.4.3 - /@eslint-community/regexpp@4.11.0: + '@eslint-community/regexpp@4.11.0': resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - /@eslint/eslintrc@2.1.4: + '@eslint/eslintrc@2.1.4': resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.3.6(supports-color@5.5.0) - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.2 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - /@eslint/js@8.57.0: + '@eslint/js@8.57.0': resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - /@fastify/busboy@2.1.1: + '@fastify/busboy@2.1.1': resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} - dev: false - /@grpc/grpc-js@1.11.1: + '@grpc/grpc-js@1.11.1': resolution: {integrity: sha512-gyt/WayZrVPH2w/UTLansS7F9Nwld472JxxaETamrM8HNlsa+jSLNyKAZmhxI2Me4c3mQHFiS1wWHDY1g1Kthw==} engines: {node: '>=12.10.0'} - dependencies: - '@grpc/proto-loader': 0.7.13 - '@js-sdsl/ordered-map': 4.4.2 - dev: false - /@grpc/proto-loader@0.7.13: + '@grpc/proto-loader@0.7.13': resolution: {integrity: sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==} engines: {node: '>=6'} hasBin: true - dependencies: - lodash.camelcase: 4.3.0 - long: 5.2.3 - protobufjs: 7.4.0 - yargs: 17.7.2 - dev: false - /@hapi/hoek@9.3.0: + '@hapi/hoek@9.3.0': resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} - dev: true - /@hapi/topo@5.1.0: + '@hapi/topo@5.1.0': resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} - dependencies: - '@hapi/hoek': 9.3.0 - dev: true - /@headlessui/react@1.7.19(react-dom@18.3.1)(react@18.3.1): + '@headlessui/react@1.7.19': resolution: {integrity: sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==} engines: {node: '>=10'} peerDependencies: react: ^16 || ^17 || ^18 react-dom: ^16 || ^17 || ^18 - dependencies: - '@tanstack/react-virtual': 3.10.6(react-dom@18.3.1)(react@18.3.1) - client-only: 0.0.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - dev: false - /@heroicons/react@2.1.3(react@18.3.1): + '@heroicons/react@2.1.3': resolution: {integrity: sha512-fEcPfo4oN345SoqdlCDdSa4ivjaKbk0jTd+oubcgNxnNgAfzysfwWfQUr+51wigiWHQQRiZNd1Ao0M5Y3M2EGg==} peerDependencies: react: '>= 16' - dependencies: - react: 18.3.1 - dev: false - /@humanwhocodes/config-array@0.11.14: + '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} deprecated: Use @eslint/config-array instead - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.6(supports-color@5.5.0) - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - /@humanwhocodes/module-importer@1.0.1: + '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - /@humanwhocodes/object-schema@2.0.3: + '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead - /@isaacs/cliui@8.0.2: + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - dependencies: - string-width: 5.1.2 - string-width-cjs: /string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: /strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: /wrap-ansi@7.0.0 - /@jridgewell/gen-mapping@0.3.5: + '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - /@jridgewell/resolve-uri@3.1.2: + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - /@jridgewell/set-array@1.2.1: + '@jridgewell/set-array@1.2.1': resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} - /@jridgewell/sourcemap-codec@1.5.0: + '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - /@jridgewell/trace-mapping@0.3.25: + '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - /@js-sdsl/ordered-map@4.4.2: + '@js-sdsl/ordered-map@4.4.2': resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} - dev: false - /@manypkg/find-root@1.1.0: + '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} - dependencies: - '@babel/runtime': 7.25.6 - '@types/node': 12.20.55 - find-up: 4.1.0 - fs-extra: 8.1.0 - dev: true - /@manypkg/get-packages@1.1.3: + '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - dependencies: - '@babel/runtime': 7.25.6 - '@changesets/types': 4.1.0 - '@manypkg/find-root': 1.1.0 - fs-extra: 8.1.0 - globby: 11.1.0 - read-yaml-file: 1.1.0 - dev: true - /@mapbox/node-pre-gyp@1.0.11: + '@mapbox/node-pre-gyp@1.0.11': resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true - dependencies: - detect-libc: 2.0.3 - https-proxy-agent: 5.0.1 - make-dir: 3.1.0 - node-fetch: 2.7.0 - nopt: 5.0.0 - npmlog: 5.0.1 - rimraf: 3.0.2 - semver: 7.6.3 - tar: 6.2.1 - transitivePeerDependencies: - - encoding - - supports-color - dev: true - /@next/env@14.2.5: + '@next/env@14.2.5': resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} - dev: false - /@next/eslint-plugin-next@14.2.7: + '@next/eslint-plugin-next@14.2.7': resolution: {integrity: sha512-+7xh142AdhZGjY9/L0iFo7mqRBMJHe+q+uOL+hto1Lfo9DeWCGcR6no4StlFbVSVcA6fQLKEX6y6qhMsSKbgNQ==} - dependencies: - glob: 10.3.10 - dev: false - /@next/swc-darwin-arm64@14.2.5: + '@next/swc-darwin-arm64@14.2.5': resolution: {integrity: sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: false - optional: true - /@next/swc-darwin-x64@14.2.5: + '@next/swc-darwin-x64@14.2.5': resolution: {integrity: sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-arm64-gnu@14.2.5: + '@next/swc-linux-arm64-gnu@14.2.5': resolution: {integrity: sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-arm64-musl@14.2.5: + '@next/swc-linux-arm64-musl@14.2.5': resolution: {integrity: sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-x64-gnu@14.2.5: + '@next/swc-linux-x64-gnu@14.2.5': resolution: {integrity: sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-x64-musl@14.2.5: + '@next/swc-linux-x64-musl@14.2.5': resolution: {integrity: sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-arm64-msvc@14.2.5: + '@next/swc-win32-arm64-msvc@14.2.5': resolution: {integrity: sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-ia32-msvc@14.2.5: + '@next/swc-win32-ia32-msvc@14.2.5': resolution: {integrity: sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-x64-msvc@14.2.5: + '@next/swc-win32-x64-msvc@14.2.5': resolution: {integrity: sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - requiresBuild: true - dev: false - optional: true - /@nodelib/fs.scandir@2.1.5: + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - /@nodelib/fs.stat@2.0.5: + '@nodelib/fs.stat@2.0.5': resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} - /@nodelib/fs.walk@1.2.8: + '@nodelib/fs.walk@1.2.8': resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 - /@nolyfill/is-core-module@1.0.39: + '@nolyfill/is-core-module@1.0.39': resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} - dev: false - /@pkgjs/parseargs@0.11.0: + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - requiresBuild: true - optional: true - /@protobufjs/aspromise@1.1.2: + '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - /@protobufjs/base64@1.1.2: + '@protobufjs/base64@1.1.2': resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - /@protobufjs/codegen@2.0.4: + '@protobufjs/codegen@2.0.4': resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} - /@protobufjs/eventemitter@1.1.0: + '@protobufjs/eventemitter@1.1.0': resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} - /@protobufjs/fetch@1.1.0: + '@protobufjs/fetch@1.1.0': resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.0 - /@protobufjs/float@1.0.2: + '@protobufjs/float@1.0.2': resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - /@protobufjs/inquire@1.1.0: + '@protobufjs/inquire@1.1.0': resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} - /@protobufjs/path@1.1.2: + '@protobufjs/path@1.1.2': resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} - /@protobufjs/pool@1.1.0: + '@protobufjs/pool@1.1.0': resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - /@protobufjs/utf8@1.1.0: + '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - /@rollup/rollup-android-arm-eabi@4.21.1: + '@rollup/rollup-android-arm-eabi@4.21.1': resolution: {integrity: sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-android-arm64@4.21.1: + '@rollup/rollup-android-arm64@4.21.1': resolution: {integrity: sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-arm64@4.21.1: + '@rollup/rollup-darwin-arm64@4.21.1': resolution: {integrity: sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-x64@4.21.1: + '@rollup/rollup-darwin-x64@4.21.1': resolution: {integrity: sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.21.1: + '@rollup/rollup-linux-arm-gnueabihf@4.21.1': resolution: {integrity: sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm-musleabihf@4.21.1: + '@rollup/rollup-linux-arm-musleabihf@4.21.1': resolution: {integrity: sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-gnu@4.21.1: + '@rollup/rollup-linux-arm64-gnu@4.21.1': resolution: {integrity: sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-musl@4.21.1: + '@rollup/rollup-linux-arm64-musl@4.21.1': resolution: {integrity: sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-powerpc64le-gnu@4.21.1: + '@rollup/rollup-linux-powerpc64le-gnu@4.21.1': resolution: {integrity: sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-riscv64-gnu@4.21.1: + '@rollup/rollup-linux-riscv64-gnu@4.21.1': resolution: {integrity: sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-s390x-gnu@4.21.1: + '@rollup/rollup-linux-s390x-gnu@4.21.1': resolution: {integrity: sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-gnu@4.21.1: + '@rollup/rollup-linux-x64-gnu@4.21.1': resolution: {integrity: sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-musl@4.21.1: + '@rollup/rollup-linux-x64-musl@4.21.1': resolution: {integrity: sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-arm64-msvc@4.21.1: + '@rollup/rollup-win32-arm64-msvc@4.21.1': resolution: {integrity: sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-ia32-msvc@4.21.1: + '@rollup/rollup-win32-ia32-msvc@4.21.1': resolution: {integrity: sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-x64-msvc@4.21.1: + '@rollup/rollup-win32-x64-msvc@4.21.1': resolution: {integrity: sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rushstack/eslint-patch@1.10.4: + '@rushstack/eslint-patch@1.10.4': resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==} - dev: false - /@sideway/address@4.1.5: + '@sideway/address@4.1.5': resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} - dependencies: - '@hapi/hoek': 9.3.0 - dev: true - /@sideway/formula@3.0.1: + '@sideway/formula@3.0.1': resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} - dev: true - /@sideway/pinpoint@2.0.0: + '@sideway/pinpoint@2.0.0': resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} - dev: true - /@swc/counter@0.1.3: + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - dev: false - /@swc/helpers@0.5.5: + '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} - dependencies: - '@swc/counter': 0.1.3 - tslib: 2.7.0 - dev: false - /@tailwindcss/forms@0.5.3(tailwindcss@3.4.9): + '@tailwindcss/forms@0.5.3': resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==} peerDependencies: tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' - dependencies: - mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.9 - dev: true - /@tailwindcss/forms@0.5.7(tailwindcss@3.4.9): + '@tailwindcss/forms@0.5.7': resolution: {integrity: sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==} peerDependencies: tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' - dependencies: - mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.9 - dev: false - /@tanstack/react-virtual@3.10.6(react-dom@18.3.1)(react@18.3.1): + '@tanstack/react-virtual@3.10.6': resolution: {integrity: sha512-xaSy6uUxB92O8mngHZ6CvbhGuqxQ5lIZWCBy+FjhrbHmOwc6BnOnKkYm2FsB1/BpKw/+FVctlMbEtI+F6I1aJg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - '@tanstack/virtual-core': 3.10.6 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - dev: false - /@tanstack/virtual-core@3.10.6: + '@tanstack/virtual-core@3.10.6': resolution: {integrity: sha512-1giLc4dzgEKLMx5pgKjL6HlG5fjZMgCjzlKAlpr7yoUtetVPELgER1NtephAI910nMwfPTHNyWKSFmJdHkz2Cw==} - dev: false - /@testing-library/dom@10.4.0: + '@testing-library/dom@10.4.0': resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/runtime': 7.25.6 - '@types/aria-query': 5.0.4 - aria-query: 5.3.0 - chalk: 4.1.2 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - pretty-format: 27.5.1 - dev: true - /@testing-library/jest-dom@6.5.0: + '@testing-library/jest-dom@6.5.0': resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} - dependencies: - '@adobe/css-tools': 4.4.0 - aria-query: 5.3.0 - chalk: 3.0.0 - css.escape: 1.5.1 - dom-accessibility-api: 0.6.3 - lodash: 4.17.21 - redent: 3.0.0 - dev: true - /@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1): + '@testing-library/react@16.0.1': resolution: {integrity: sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg==} engines: {node: '>=18'} peerDependencies: @@ -1812,123 +1147,71 @@ packages: optional: true '@types/react-dom': optional: true - dependencies: - '@babel/runtime': 7.25.6 - '@testing-library/dom': 10.4.0 - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - dev: true - /@types/aria-query@5.0.4: + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - dev: true - /@types/babel__core@7.20.5: + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - dependencies: - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 - '@types/babel__generator': 7.6.8 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.6 - dev: true - /@types/babel__generator@7.6.8: + '@types/babel__generator@7.6.8': resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - dependencies: - '@babel/types': 7.25.6 - dev: true - /@types/babel__template@7.4.4: + '@types/babel__template@7.4.4': resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - dependencies: - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 - dev: true - /@types/babel__traverse@7.20.6: + '@types/babel__traverse@7.20.6': resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - dependencies: - '@babel/types': 7.25.6 - dev: true - /@types/estree@1.0.5: + '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true - /@types/json5@0.0.29: + '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - dev: false - /@types/minimist@1.2.5: + '@types/minimist@1.2.5': resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - dev: true - /@types/ms@0.7.34: + '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - dev: true - /@types/node@12.20.55: + '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - dev: true - /@types/node@22.1.0: + '@types/node@22.1.0': resolution: {integrity: sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==} - dependencies: - undici-types: 6.13.0 - /@types/normalize-package-data@2.4.4: + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - dev: true - /@types/prop-types@15.7.12: + '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - dev: true - /@types/react-dom@18.3.0: + '@types/react-dom@18.3.0': resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} - dependencies: - '@types/react': 18.3.3 - dev: true - /@types/react@18.3.3: + '@types/react@18.3.3': resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} - dependencies: - '@types/prop-types': 15.7.12 - csstype: 3.1.3 - dev: true - /@types/semver@7.5.8: + '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - dev: true - /@types/sinonjs__fake-timers@8.1.1: + '@types/sinonjs__fake-timers@8.1.1': resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==} - dev: true - /@types/sizzle@2.3.8: + '@types/sizzle@2.3.8': resolution: {integrity: sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==} - dev: true - /@types/tinycolor2@1.4.3: + '@types/tinycolor2@1.4.3': resolution: {integrity: sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==} - dev: true - /@types/uuid@10.0.0: + '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} - dev: true - /@types/yauzl@2.10.3: + '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - requiresBuild: true - dependencies: - '@types/node': 22.1.0 - dev: true - optional: true - /@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4): + '@typescript-eslint/parser@7.18.0': resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: @@ -1937,32 +1220,16 @@ packages: peerDependenciesMeta: typescript: optional: true - dependencies: - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) - '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.6(supports-color@5.5.0) - eslint: 8.57.0 - typescript: 5.5.4 - transitivePeerDependencies: - - supports-color - dev: false - /@typescript-eslint/scope-manager@7.18.0: + '@typescript-eslint/scope-manager@7.18.0': resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} engines: {node: ^18.18.0 || >=20.0.0} - dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 - dev: false - /@typescript-eslint/types@7.18.0: + '@typescript-eslint/types@7.18.0': resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} engines: {node: ^18.18.0 || >=20.0.0} - dev: false - /@typescript-eslint/typescript-estree@7.18.0(typescript@5.5.4): + '@typescript-eslint/typescript-estree@7.18.0': resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: @@ -1970,32 +1237,15 @@ packages: peerDependenciesMeta: typescript: optional: true - dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.6(supports-color@5.5.0) - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.5.4) - typescript: 5.5.4 - transitivePeerDependencies: - - supports-color - dev: false - /@typescript-eslint/visitor-keys@7.18.0: + '@typescript-eslint/visitor-keys@7.18.0': resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} - dependencies: - '@typescript-eslint/types': 7.18.0 - eslint-visitor-keys: 3.4.3 - dev: false - /@ungap/structured-clone@1.2.0: + '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - /@vercel/analytics@1.3.1(next@14.2.5)(react@18.3.1): + '@vercel/analytics@1.3.1': resolution: {integrity: sha512-xhSlYgAuJ6Q4WQGkzYTLmXwhYl39sWjoMA3nHxfkvG+WdBT25c563a7QhwwKivEOZtPJXifYHR1m2ihoisbWyA==} peerDependencies: next: '>= 13' @@ -2005,962 +1255,537 @@ packages: optional: true react: optional: true - dependencies: - next: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8) - react: 18.3.1 - server-only: 0.0.1 - dev: false - /@vercel/git-hooks@1.0.0: + '@vercel/git-hooks@1.0.0': resolution: {integrity: sha512-OxDFAAdyiJ/H0b8zR9rFCu3BIb78LekBXOphOYG3snV4ULhKFX387pBPpqZ9HLiRTejBWBxYEahkw79tuIgdAA==} - requiresBuild: true - dev: true - /@vitejs/plugin-react@4.3.1(vite@5.4.2): + '@vitejs/plugin-react@4.3.1': resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 - dependencies: - '@babel/core': 7.25.2 - '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) - '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 5.4.2 - transitivePeerDependencies: - - supports-color - dev: true - /@vitest/expect@2.0.5: + '@vitest/expect@2.0.5': resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} - dependencies: - '@vitest/spy': 2.0.5 - '@vitest/utils': 2.0.5 - chai: 5.1.1 - tinyrainbow: 1.2.0 - dev: true - /@vitest/pretty-format@2.0.5: + '@vitest/pretty-format@2.0.5': resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} - dependencies: - tinyrainbow: 1.2.0 - dev: true - /@vitest/runner@2.0.5: + '@vitest/runner@2.0.5': resolution: {integrity: sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==} - dependencies: - '@vitest/utils': 2.0.5 - pathe: 1.1.2 - dev: true - /@vitest/snapshot@2.0.5: + '@vitest/snapshot@2.0.5': resolution: {integrity: sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==} - dependencies: - '@vitest/pretty-format': 2.0.5 - magic-string: 0.30.11 - pathe: 1.1.2 - dev: true - /@vitest/spy@2.0.5: + '@vitest/spy@2.0.5': resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} - dependencies: - tinyspy: 3.0.0 - dev: true - /@vitest/utils@2.0.5: + '@vitest/utils@2.0.5': resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} - dependencies: - '@vitest/pretty-format': 2.0.5 - estree-walker: 3.0.3 - loupe: 3.1.1 - tinyrainbow: 1.2.0 - dev: true - /abbrev@1.1.1: + abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} - dev: true - /abort-controller-x@0.4.3: + abort-controller-x@0.4.3: resolution: {integrity: sha512-VtUwTNU8fpMwvWGn4xE93ywbogTYsuT+AUxAXOeelbXuQVIwNmC5YLeho9sH4vZ4ITW8414TTAOG1nW6uIVHCA==} - dev: false - /acorn-jsx@5.3.2(acorn@8.12.1): + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.12.1 - /acorn@8.12.1: + acorn@8.12.1: resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} engines: {node: '>=0.4.0'} hasBin: true - /agent-base@6.0.2: + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} - dependencies: - debug: 4.3.6(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - dev: true - /agent-base@7.1.1: + agent-base@7.1.1: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} - dependencies: - debug: 4.3.6(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - dev: true - /aggregate-error@3.1.0: + aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 - dev: true - /aggregate-error@4.0.1: + aggregate-error@4.0.1: resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} engines: {node: '>=12'} - dependencies: - clean-stack: 4.2.0 - indent-string: 5.0.0 - dev: true - /ajv@6.12.6: + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - /ansi-colors@4.1.3: + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} - dev: true - /ansi-escapes@4.3.2: + ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} - dependencies: - type-fest: 0.21.3 - dev: true - /ansi-escapes@7.0.0: + ansi-escapes@7.0.0: resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} engines: {node: '>=18'} - dependencies: - environment: 1.1.0 - dev: true - /ansi-regex@5.0.1: + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - /ansi-regex@6.0.1: + ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - /ansi-styles@3.2.1: + ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 - /ansi-styles@4.3.0: + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - /ansi-styles@5.2.0: + ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - dev: true - /ansi-styles@6.2.1: + ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - /any-promise@1.3.0: + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - /anymatch@3.1.3: + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - /aproba@2.0.0: + aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} - dev: true - /arch@2.2.0: + arch@2.2.0: resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} - dev: true - /are-we-there-yet@2.0.0: + are-we-there-yet@2.0.0: resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} engines: {node: '>=10'} deprecated: This package is no longer supported. - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 - dev: true - /arg@5.0.2: + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - /argparse@1.0.10: + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - dependencies: - sprintf-js: 1.0.3 - dev: true - /argparse@2.0.1: + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - /aria-query@5.1.3: + aria-query@5.1.3: resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} - dependencies: - deep-equal: 2.2.3 - dev: false - /aria-query@5.3.0: + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - dependencies: - dequal: 2.0.3 - dev: true - /array-buffer-byte-length@1.0.1: + array-buffer-byte-length@1.0.1: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - is-array-buffer: 3.0.4 - dev: false - /array-includes@3.1.8: + array-includes@3.1.8: resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 - is-string: 1.0.7 - dev: false - /array-union@2.1.0: + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - /array.prototype.findlast@1.2.5: + array.prototype.findlast@1.2.5: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-shim-unscopables: 1.0.2 - dev: false - /array.prototype.findlastindex@1.2.5: + array.prototype.findlastindex@1.2.5: resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-shim-unscopables: 1.0.2 - dev: false - /array.prototype.flat@1.3.2: + array.prototype.flat@1.3.2: resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-shim-unscopables: 1.0.2 - dev: false - /array.prototype.flatmap@1.3.2: + array.prototype.flatmap@1.3.2: resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-shim-unscopables: 1.0.2 - dev: false - /array.prototype.tosorted@1.1.4: + array.prototype.tosorted@1.1.4: resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-shim-unscopables: 1.0.2 - dev: false - /arraybuffer.prototype.slice@1.0.3: + arraybuffer.prototype.slice@1.0.3: resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} - dependencies: - array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - is-array-buffer: 3.0.4 - is-shared-array-buffer: 1.0.3 - dev: false - /arrify@1.0.1: + arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} - dev: true - /asn1@0.2.6: + asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} - dependencies: - safer-buffer: 2.1.2 - dev: true - /assert-plus@1.0.0: + assert-plus@1.0.0: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} engines: {node: '>=0.8'} - dev: true - /assertion-error@2.0.1: + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - dev: true - /ast-types-flow@0.0.8: + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} - dev: false - /astral-regex@2.0.0: + astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} - dev: true - /async@3.2.6: + async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - dev: true - /asynckit@0.4.0: + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: true - /at-least-node@1.0.0: + at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} - dev: true - /autoprefixer@10.4.20(postcss@8.4.41): + autoprefixer@10.4.20: resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 - dependencies: - browserslist: 4.23.3 - caniuse-lite: 1.0.30001654 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.0.1 - postcss: 8.4.41 - postcss-value-parser: 4.2.0 - dev: true - /available-typed-arrays@1.0.7: + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - dependencies: - possible-typed-array-names: 1.0.0 - dev: false - /aws-sign2@0.7.0: + aws-sign2@0.7.0: resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} - dev: true - /aws4@1.13.2: + aws4@1.13.2: resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} - dev: true - /axe-core@4.10.0: + axe-core@4.10.0: resolution: {integrity: sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==} engines: {node: '>=4'} - dev: false - /axios@1.7.5(debug@4.3.6): + axios@1.7.5: resolution: {integrity: sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==} - dependencies: - follow-redirects: 1.15.6(debug@4.3.6) - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - dev: true - /axobject-query@3.1.1: + axobject-query@3.1.1: resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} - dependencies: - deep-equal: 2.2.3 - dev: false - /balanced-match@1.0.2: + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - /base64-js@1.5.1: + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: true - /bcrypt-pbkdf@1.0.2: + bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - dependencies: - tweetnacl: 0.14.5 - dev: true - /better-path-resolve@1.0.0: + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} - dependencies: - is-windows: 1.0.2 - dev: true - /binary-extensions@2.3.0: + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - /blob-util@2.0.2: + blob-util@2.0.2: resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==} - dev: true - /bluebird@3.7.2: + bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - dev: true - /brace-expansion@1.1.11: + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - /brace-expansion@2.0.1: + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - dependencies: - balanced-match: 1.0.2 - /braces@3.0.3: + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - dependencies: - fill-range: 7.1.1 - /browserslist@4.23.3: + browserslist@4.23.3: resolution: {integrity: sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - dependencies: - caniuse-lite: 1.0.30001654 - electron-to-chromium: 1.5.13 - node-releases: 2.0.18 - update-browserslist-db: 1.1.0(browserslist@4.23.3) - /buffer-crc32@0.2.13: + buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - dev: true - /buffer@5.7.1: + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: true - /bundle-require@5.0.0(esbuild@0.23.1): + bundle-require@5.0.0: resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: esbuild: '>=0.18' - dependencies: - esbuild: 0.23.1 - load-tsconfig: 0.2.5 - dev: true - /busboy@1.6.0: + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} - dependencies: - streamsearch: 1.1.0 - dev: false - /cac@6.7.14: + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - dev: true - /cachedir@2.4.0: + cachedir@2.4.0: resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} engines: {node: '>=6'} - dev: true - /call-bind@1.0.7: + call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} - dependencies: - es-define-property: 1.0.0 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.2.4 - set-function-length: 1.2.2 - /callsites@3.1.0: + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - /camelcase-css@2.0.1: + camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - /camelcase-keys@7.0.2: + camelcase-keys@7.0.2: resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==} engines: {node: '>=12'} - dependencies: - camelcase: 6.3.0 - map-obj: 4.3.0 - quick-lru: 5.1.1 - type-fest: 1.4.0 - dev: true - /camelcase@6.3.0: + camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - dev: true - /caniuse-lite@1.0.30001654: + caniuse-lite@1.0.30001654: resolution: {integrity: sha512-wLJc602fW0OdrUR+PqsBUH3dgrjDcT+mWs/Kw86zPvgjiqOiI2TXMkBFK4KihYzZclmJxrFwgYhZDSEogFai/g==} - /case-anything@2.1.13: + case-anything@2.1.13: resolution: {integrity: sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==} engines: {node: '>=12.13'} - dev: true - /caseless@0.12.0: + caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} - dev: true - /chai@5.1.1: + chai@5.1.1: resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} engines: {node: '>=12'} - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.1.1 - pathval: 2.0.0 - dev: true - /chalk@2.4.2: + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - /chalk@3.0.0: + chalk@3.0.0: resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} engines: {node: '>=8'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: true - /chalk@4.1.2: + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - /chalk@5.3.0: + chalk@5.3.0: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: true - /chardet@0.7.0: + chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - dev: true - /check-error@2.1.1: + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} - dev: true - /check-more-types@2.24.0: + check-more-types@2.24.0: resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} engines: {node: '>= 0.8.0'} - dev: true - /chokidar@3.6.0: + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - /chownr@2.0.0: + chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} - dev: true - /ci-info@3.9.0: + ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - dev: true - /clean-stack@2.2.0: + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - dev: true - /clean-stack@4.2.0: + clean-stack@4.2.0: resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} engines: {node: '>=12'} - dependencies: - escape-string-regexp: 5.0.0 - dev: true - /cli-cursor@3.1.0: + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} - dependencies: - restore-cursor: 3.1.0 - dev: true - /cli-cursor@5.0.0: + cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} - dependencies: - restore-cursor: 5.1.0 - dev: true - /cli-table3@0.6.5: + cli-table3@0.6.5: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} - dependencies: - string-width: 4.2.3 - optionalDependencies: - '@colors/colors': 1.5.0 - dev: true - /cli-truncate@2.1.0: + cli-truncate@2.1.0: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} engines: {node: '>=8'} - dependencies: - slice-ansi: 3.0.0 - string-width: 4.2.3 - dev: true - /cli-truncate@4.0.0: + cli-truncate@4.0.0: resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} engines: {node: '>=18'} - dependencies: - slice-ansi: 5.0.0 - string-width: 7.2.0 - dev: true - /client-only@0.0.1: + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - dev: false - /cliui@8.0.1: + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - /clsx@1.2.1: + clsx@1.2.1: resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} engines: {node: '>=6'} - dev: false - /color-convert@1.9.3: + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 - /color-convert@2.0.1: + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - /color-name@1.1.3: + color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - /color-name@1.1.4: + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - /color-support@1.1.3: + color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true - dev: true - /colorette@2.0.20: + colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - dev: true - /combined-stream@1.0.8: + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} - dependencies: - delayed-stream: 1.0.0 - dev: true - /commander@12.1.0: + commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} - dev: true - /commander@4.1.1: + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - /commander@6.2.1: + commander@6.2.1: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} engines: {node: '>= 6'} - dev: true - /common-tags@1.8.2: + common-tags@1.8.2: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} - dev: true - /concat-map@0.0.1: + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - /concurrently@8.2.2: + concurrently@8.2.2: resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} engines: {node: ^14.13.0 || >=16.0.0} hasBin: true - dependencies: - chalk: 4.1.2 - date-fns: 2.30.0 - lodash: 4.17.21 - rxjs: 7.8.1 - shell-quote: 1.8.1 - spawn-command: 0.0.2 - supports-color: 8.1.1 - tree-kill: 1.2.2 - yargs: 17.7.2 - dev: true - /consola@3.2.3: + consola@3.2.3: resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} engines: {node: ^14.18.0 || >=16.10.0} - dev: true - /console-control-strings@1.1.0: + console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - dev: true - /convert-source-map@2.0.0: + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - /copy-to-clipboard@3.3.3: + copy-to-clipboard@3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} - dependencies: - toggle-selection: 1.0.6 - dev: false - /core-util-is@1.0.2: + core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} - dev: true - /cross-spawn@5.1.0: + cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} - dependencies: - lru-cache: 4.1.5 - shebang-command: 1.2.0 - which: 1.3.1 - dev: true - /cross-spawn@7.0.3: + cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - /css.escape@1.5.1: + css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - dev: true - /cssesc@3.0.0: + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true - /cssstyle@4.0.1: + cssstyle@4.0.1: resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} engines: {node: '>=18'} - dependencies: - rrweb-cssom: 0.6.0 - dev: true - /csstype@3.1.3: + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - dev: true - /cypress@13.14.1: + cypress@13.14.1: resolution: {integrity: sha512-Wo+byPmjps66hACEH5udhXINEiN3qS3jWNGRzJOjrRJF3D0+YrcP2LVB1T7oYaVQM/S+eanqEvBWYc8cf7Vcbg==} engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} hasBin: true - requiresBuild: true - dependencies: - '@cypress/request': 3.0.1 - '@cypress/xvfb': 1.2.4(supports-color@8.1.1) - '@types/sinonjs__fake-timers': 8.1.1 - '@types/sizzle': 2.3.8 - arch: 2.2.0 - blob-util: 2.0.2 - bluebird: 3.7.2 - buffer: 5.7.1 - cachedir: 2.4.0 - chalk: 4.1.2 - check-more-types: 2.24.0 - cli-cursor: 3.1.0 - cli-table3: 0.6.5 - commander: 6.2.1 - common-tags: 1.8.2 - dayjs: 1.11.13 - debug: 4.3.6(supports-color@8.1.1) - enquirer: 2.4.1 - eventemitter2: 6.4.7 - execa: 4.1.0 - executable: 4.1.1 - extract-zip: 2.0.1(supports-color@8.1.1) - figures: 3.2.0 - fs-extra: 9.1.0 - getos: 3.2.1 - is-ci: 3.0.1 - is-installed-globally: 0.4.0 - lazy-ass: 1.6.0 - listr2: 3.14.0(enquirer@2.4.1) - lodash: 4.17.21 - log-symbols: 4.1.0 - minimist: 1.2.8 - ospath: 1.2.2 - pretty-bytes: 5.6.0 - process: 0.11.10 - proxy-from-env: 1.0.0 - request-progress: 3.0.0 - semver: 7.6.3 - supports-color: 8.1.1 - tmp: 0.2.3 - untildify: 4.0.0 - yauzl: 2.10.0 - dev: true - /damerau-levenshtein@1.0.8: + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - dev: false - /dashdash@1.14.1: + dashdash@1.14.1: resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} engines: {node: '>=0.10'} - dependencies: - assert-plus: 1.0.0 - dev: true - /data-urls@5.0.0: + data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} - dependencies: - whatwg-mimetype: 4.0.0 - whatwg-url: 14.0.0 - dev: true - /data-view-buffer@1.0.1: + data-view-buffer@1.0.1: resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - dev: false - /data-view-byte-length@1.0.1: + data-view-byte-length@1.0.1: resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - dev: false - /data-view-byte-offset@1.0.0: + data-view-byte-offset@1.0.0: resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - dev: false - /date-fns@2.30.0: + date-fns@2.30.0: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} - dependencies: - '@babel/runtime': 7.25.6 - dev: true - /dayjs@1.11.13: + dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - dev: true - /debug@3.2.7(supports-color@8.1.1): + debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: supports-color: '*' peerDependenciesMeta: supports-color: optional: true - dependencies: - ms: 2.1.3 - supports-color: 8.1.1 - /debug@4.3.6(supports-color@5.5.0): + debug@4.3.6: resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} engines: {node: '>=6.0'} peerDependencies: @@ -2968,473 +1793,214 @@ packages: peerDependenciesMeta: supports-color: optional: true - dependencies: - ms: 2.1.2 - supports-color: 5.5.0 - /debug@4.3.6(supports-color@8.1.1): - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - supports-color: 8.1.1 - dev: true - - /decamelize-keys@1.1.1: + decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - dev: true - /decamelize@1.2.0: + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} - dev: true - /decamelize@5.0.1: + decamelize@5.0.1: resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} engines: {node: '>=10'} - dev: true - /decimal.js@10.4.3: + decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} - dev: true - /deep-eql@5.0.2: + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - dev: true - /deep-equal@2.2.3: + deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} - dependencies: - array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 - es-get-iterator: 1.1.3 - get-intrinsic: 1.2.4 - is-arguments: 1.1.1 - is-array-buffer: 3.0.4 - is-date-object: 1.0.5 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.3 - isarray: 2.0.5 - object-is: 1.1.6 - object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.2 - side-channel: 1.0.6 - which-boxed-primitive: 1.0.2 - which-collection: 1.0.2 - which-typed-array: 1.1.15 - dev: false - /deep-is@0.1.4: + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - /define-data-property@1.1.4: + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} - dependencies: - es-define-property: 1.0.0 - es-errors: 1.3.0 - gopd: 1.0.1 - /define-properties@1.2.1: + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - dev: false - /del-cli@5.1.0: + del-cli@5.1.0: resolution: {integrity: sha512-xwMeh2acluWeccsfzE7VLsG3yTr7nWikbfw+xhMnpRrF15pGSkw+3/vJZWlGoE4I86UiLRNHicmKt4tkIX9Jtg==} engines: {node: '>=14.16'} hasBin: true - dependencies: - del: 7.1.0 - meow: 10.1.5 - dev: true - /del@7.1.0: + del@7.1.0: resolution: {integrity: sha512-v2KyNk7efxhlyHpjEvfyxaAihKKK0nWCuf6ZtqZcFFpQRG0bJ12Qsr0RpvsICMjAAZ8DOVCxrlqpxISlMHC4Kg==} engines: {node: '>=14.16'} - dependencies: - globby: 13.2.2 - graceful-fs: 4.2.11 - is-glob: 4.0.3 - is-path-cwd: 3.0.0 - is-path-inside: 4.0.0 - p-map: 5.5.0 - rimraf: 3.0.2 - slash: 4.0.0 - dev: true - /delayed-stream@1.0.0: + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - dev: true - /delegates@1.0.0: + delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - dev: true - /dequal@2.0.3: + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - dev: true - /detect-indent@6.1.0: + detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} - dev: true - /detect-libc@1.0.3: + detect-libc@1.0.3: resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} engines: {node: '>=0.10'} hasBin: true - dev: true - /detect-libc@2.0.3: + detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} - dev: true - /didyoumean@1.2.2: + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - /dir-glob@3.0.1: + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - dependencies: - path-type: 4.0.0 - /dlv@1.1.3: + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - /doctrine@2.1.0: + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} - dependencies: - esutils: 2.0.3 - dev: false - /doctrine@3.0.0: + doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} - dependencies: - esutils: 2.0.3 - /dom-accessibility-api@0.5.16: + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - dev: true - /dom-accessibility-api@0.6.3: + dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} - dev: true - /dotenv@16.0.3: + dotenv@16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} - dev: false - /dprint-node@1.0.8: + dprint-node@1.0.8: resolution: {integrity: sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==} - dependencies: - detect-libc: 1.0.3 - dev: true - /duplexer@0.1.2: + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} - dev: true - /eastasianwidth@0.2.0: + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - /ecc-jsbn@0.1.2: + ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} - dependencies: - jsbn: 0.1.1 - safer-buffer: 2.1.2 - dev: true - /electron-to-chromium@1.5.13: + electron-to-chromium@1.5.13: resolution: {integrity: sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==} - /emoji-regex@10.4.0: + emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} - dev: true - /emoji-regex@8.0.0: + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - /emoji-regex@9.2.2: + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - /end-of-stream@1.4.4: + end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - dependencies: - once: 1.4.0 - dev: true - /enhanced-resolve@5.17.1: + enhanced-resolve@5.17.1: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.1 - dev: false - /enquirer@2.4.1: + enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - dev: true - /entities@4.5.0: + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - dev: true - /env-cmd@10.1.0: + env-cmd@10.1.0: resolution: {integrity: sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==} engines: {node: '>=8.0.0'} hasBin: true - dependencies: - commander: 4.1.1 - cross-spawn: 7.0.3 - dev: true - /environment@1.1.0: + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} - dev: true - /error-ex@1.3.2: + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - dependencies: - is-arrayish: 0.2.1 - dev: true - /es-abstract@1.23.3: + es-abstract@1.23.3: resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} engines: {node: '>= 0.4'} - dependencies: - array-buffer-byte-length: 1.0.1 - arraybuffer.prototype.slice: 1.0.3 - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - data-view-buffer: 1.0.1 - data-view-byte-length: 1.0.1 - data-view-byte-offset: 1.0.0 - es-define-property: 1.0.0 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-set-tostringtag: 2.0.3 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.6 - get-intrinsic: 1.2.4 - get-symbol-description: 1.0.2 - globalthis: 1.0.4 - gopd: 1.0.1 - has-property-descriptors: 1.0.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - hasown: 2.0.2 - internal-slot: 1.0.7 - is-array-buffer: 3.0.4 - is-callable: 1.2.7 - is-data-view: 1.0.1 - is-negative-zero: 2.0.3 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.3 - is-string: 1.0.7 - is-typed-array: 1.1.13 - is-weakref: 1.0.2 - object-inspect: 1.13.2 - object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.2 - safe-array-concat: 1.1.2 - safe-regex-test: 1.0.3 - string.prototype.trim: 1.2.9 - string.prototype.trimend: 1.0.8 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.2 - typed-array-byte-length: 1.0.1 - typed-array-byte-offset: 1.0.2 - typed-array-length: 1.0.6 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.15 - dev: false - /es-define-property@1.0.0: + es-define-property@1.0.0: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.4 - /es-errors@1.3.0: + es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - /es-get-iterator@1.1.3: + es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - is-arguments: 1.1.1 - is-map: 2.0.3 - is-set: 2.0.3 - is-string: 1.0.7 - isarray: 2.0.5 - stop-iteration-iterator: 1.0.0 - dev: false - /es-iterator-helpers@1.0.19: + es-iterator-helpers@1.0.19: resolution: {integrity: sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-set-tostringtag: 2.0.3 - function-bind: 1.1.2 - get-intrinsic: 1.2.4 - globalthis: 1.0.4 - has-property-descriptors: 1.0.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - internal-slot: 1.0.7 - iterator.prototype: 1.1.2 - safe-array-concat: 1.1.2 - dev: false - /es-object-atoms@1.0.0: + es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} - dependencies: - es-errors: 1.3.0 - dev: false - /es-set-tostringtag@2.0.3: + es-set-tostringtag@2.0.3: resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.4 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - dev: false - /es-shim-unscopables@1.0.2: + es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} - dependencies: - hasown: 2.0.2 - dev: false - /es-to-primitive@1.2.1: + es-to-primitive@1.2.1: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} - dependencies: - is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 - dev: false - /esbuild@0.21.5: + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - dev: true - /esbuild@0.23.1: + esbuild@0.23.1: resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} engines: {node: '>=18'} hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.23.1 - '@esbuild/android-arm': 0.23.1 - '@esbuild/android-arm64': 0.23.1 - '@esbuild/android-x64': 0.23.1 - '@esbuild/darwin-arm64': 0.23.1 - '@esbuild/darwin-x64': 0.23.1 - '@esbuild/freebsd-arm64': 0.23.1 - '@esbuild/freebsd-x64': 0.23.1 - '@esbuild/linux-arm': 0.23.1 - '@esbuild/linux-arm64': 0.23.1 - '@esbuild/linux-ia32': 0.23.1 - '@esbuild/linux-loong64': 0.23.1 - '@esbuild/linux-mips64el': 0.23.1 - '@esbuild/linux-ppc64': 0.23.1 - '@esbuild/linux-riscv64': 0.23.1 - '@esbuild/linux-s390x': 0.23.1 - '@esbuild/linux-x64': 0.23.1 - '@esbuild/netbsd-x64': 0.23.1 - '@esbuild/openbsd-arm64': 0.23.1 - '@esbuild/openbsd-x64': 0.23.1 - '@esbuild/sunos-x64': 0.23.1 - '@esbuild/win32-arm64': 0.23.1 - '@esbuild/win32-ia32': 0.23.1 - '@esbuild/win32-x64': 0.23.1 - dev: true - /escalade@3.2.0: + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - /escape-string-regexp@1.0.5: + escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} - /escape-string-regexp@4.0.0: + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - /escape-string-regexp@5.0.0: + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - dev: true - /eslint-config-next@14.2.7(eslint@8.57.0)(typescript@5.5.4): + eslint-config-next@14.2.7: resolution: {integrity: sha512-ppmy+QdQ7qkuCHGDlPjWaoSbJvjGpWSBD4zEW8f1eWlxYXYpZK7QzBOer1EcHKT3uKhlY1JjUus9g7Kvv712rw==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 @@ -3442,53 +2008,22 @@ packages: peerDependenciesMeta: typescript: optional: true - dependencies: - '@next/eslint-plugin-next': 14.2.7 - '@rushstack/eslint-patch': 1.10.4 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) - eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) - eslint-plugin-react: 7.35.0(eslint@8.57.0) - eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) - typescript: 5.5.4 - transitivePeerDependencies: - - eslint-import-resolver-webpack - - eslint-plugin-import-x - - supports-color - dev: false - /eslint-config-prettier@9.1.0(eslint@8.57.0): + eslint-config-prettier@9.1.0: resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} hasBin: true peerDependencies: eslint: '>=7.0.0' - dependencies: - eslint: 8.57.0 - dev: false - /eslint-config-turbo@2.1.0(eslint@8.57.0): + eslint-config-turbo@2.1.0: resolution: {integrity: sha512-3SeE2OCWnkA/84adGJXABm++966LNGxRdXtXKBcplJdIe4PmERkov1z6Kzp2PrPKT13wGu/bwoLV5h1rm7v9ug==} peerDependencies: eslint: '>6.6.0' - dependencies: - eslint: 8.57.0 - eslint-plugin-turbo: 2.1.0(eslint@8.57.0) - dev: false - /eslint-import-resolver-node@0.3.9: + eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - dependencies: - debug: 3.2.7(supports-color@8.1.1) - is-core-module: 2.15.1 - resolve: 1.22.8 - transitivePeerDependencies: - - supports-color - dev: false - /eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.3: resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -3500,25 +2035,8 @@ packages: optional: true eslint-plugin-import-x: optional: true - dependencies: - '@nolyfill/is-core-module': 1.0.39 - debug: 4.3.6(supports-color@5.5.0) - enhanced-resolve: 5.17.1 - eslint: 8.57.0 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) - fast-glob: 3.3.2 - get-tsconfig: 4.8.0 - is-bun-module: 1.1.0 - is-glob: 4.0.3 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - supports-color - dev: false - /eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): + eslint-module-utils@2.8.2: resolution: {integrity: sha512-3XnC5fDyc8M4J2E8pt8pmSVRX2M+5yWMCfI/kDZwauQeFgzQOuhcRBFKjTeJagqgk4sFKxe1mvNVnaWwImx/Tg==} engines: {node: '>=4'} peerDependencies: @@ -3538,17 +2056,8 @@ packages: optional: true eslint-import-resolver-webpack: optional: true - dependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) - debug: 3.2.7(supports-color@8.1.1) - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - transitivePeerDependencies: - - supports-color - dev: false - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): + eslint-plugin-import@2.29.1: resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} engines: {node: '>=4'} peerDependencies: @@ -3557,390 +2066,165 @@ packages: peerDependenciesMeta: '@typescript-eslint/parser': optional: true - dependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7(supports-color@8.1.1) - doctrine: 2.1.0 - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) - hasown: 2.0.2 - is-core-module: 2.15.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.0 - semver: 6.3.1 - tsconfig-paths: 3.15.0 - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - dev: false - /eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.0): + eslint-plugin-jsx-a11y@6.9.0: resolution: {integrity: sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g==} engines: {node: '>=4.0'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - dependencies: - aria-query: 5.1.3 - array-includes: 3.1.8 - array.prototype.flatmap: 1.3.2 - ast-types-flow: 0.0.8 - axe-core: 4.10.0 - axobject-query: 3.1.1 - damerau-levenshtein: 1.0.8 - emoji-regex: 9.2.2 - es-iterator-helpers: 1.0.19 - eslint: 8.57.0 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - language-tags: 1.0.9 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - safe-regex-test: 1.0.3 - string.prototype.includes: 2.0.0 - dev: false - /eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): + eslint-plugin-react-hooks@4.6.2: resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - dependencies: - eslint: 8.57.0 - dev: false - /eslint-plugin-react@7.35.0(eslint@8.57.0): + eslint-plugin-react@7.35.0: resolution: {integrity: sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - dependencies: - array-includes: 3.1.8 - array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.2 - array.prototype.tosorted: 1.1.4 - doctrine: 2.1.0 - es-iterator-helpers: 1.0.19 - eslint: 8.57.0 - estraverse: 5.3.0 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.2 - object.entries: 1.1.8 - object.fromentries: 2.0.8 - object.values: 1.2.0 - prop-types: 15.8.1 - resolve: 2.0.0-next.5 - semver: 6.3.1 - string.prototype.matchall: 4.0.11 - string.prototype.repeat: 1.0.0 - dev: false - /eslint-plugin-turbo@2.1.0(eslint@8.57.0): + eslint-plugin-turbo@2.1.0: resolution: {integrity: sha512-+CWVY29y7Qa+gvrKSzP+TOYrHAlNLCh/97K5VtDdnpH54h/JFmnd3U0aSG6WANe0HgAK8NHQfeWFDdRzfDqbKA==} peerDependencies: eslint: '>6.6.0' - dependencies: - dotenv: 16.0.3 - eslint: 8.57.0 - dev: false - /eslint-scope@7.2.2: + eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - /eslint-visitor-keys@3.4.3: + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - /eslint@8.57.0: + eslint@8.57.0: resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@eslint-community/regexpp': 4.11.0 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.0 - '@humanwhocodes/config-array': 0.11.14 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.6(supports-color@5.5.0) - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - /espree@9.6.1: + espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) - eslint-visitor-keys: 3.4.3 - /esprima@4.0.1: + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true - dev: true - /esquery@1.6.0: + esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 - /esrecurse@4.3.0: + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} - dependencies: - estraverse: 5.3.0 - /estraverse@5.3.0: + estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} - /estree-walker@3.0.3: + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - dependencies: - '@types/estree': 1.0.5 - dev: true - /esutils@2.0.3: + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - /event-stream@3.3.4: + event-stream@3.3.4: resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} - dependencies: - duplexer: 0.1.2 - from: 0.1.7 - map-stream: 0.1.0 - pause-stream: 0.0.11 - split: 0.3.3 - stream-combiner: 0.0.4 - through: 2.3.8 - dev: true - /eventemitter2@6.4.7: + eventemitter2@6.4.7: resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==} - dev: true - /eventemitter3@5.0.1: + eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - dev: true - /execa@4.1.0: + execa@4.1.0: resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} engines: {node: '>=10'} - dependencies: - cross-spawn: 7.0.3 - get-stream: 5.2.0 - human-signals: 1.1.1 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - dev: true - /execa@5.1.1: + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - dev: true - /execa@8.0.1: + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} - dependencies: - cross-spawn: 7.0.3 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 - dev: true - /executable@4.1.1: + executable@4.1.1: resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} engines: {node: '>=4'} - dependencies: - pify: 2.3.0 - dev: true - /extend@3.0.2: + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - dev: true - /extendable-error@0.1.7: + extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} - dev: true - /external-editor@3.1.0: + external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} - dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 - dev: true - /extract-zip@2.0.1(supports-color@8.1.1): + extract-zip@2.0.1: resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} engines: {node: '>= 10.17.0'} hasBin: true - dependencies: - debug: 4.3.6(supports-color@8.1.1) - get-stream: 5.2.0 - yauzl: 2.10.0 - optionalDependencies: - '@types/yauzl': 2.10.3 - transitivePeerDependencies: - - supports-color - dev: true - /extsprintf@1.3.0: + extsprintf@1.3.0: resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} engines: {'0': node >=0.6.0} - dev: true - /fast-deep-equal@3.1.3: + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - /fast-glob@3.3.2: + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - /fast-json-stable-stringify@2.1.0: + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - /fast-levenshtein@2.0.6: + fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - /fastq@1.17.1: + fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - dependencies: - reusify: 1.0.4 - /fd-slicer@1.1.0: + fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - dependencies: - pend: 1.2.0 - dev: true - /figures@3.2.0: + figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} - dependencies: - escape-string-regexp: 1.0.5 - dev: true - /file-entry-cache@6.0.1: + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flat-cache: 3.2.0 - /fill-range@7.1.1: + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - /find-up@4.1.0: + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - dev: true - /find-up@5.0.0: + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - /find-yarn-workspace-root2@1.2.16: + find-yarn-workspace-root2@1.2.16: resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} - dependencies: - micromatch: 4.0.8 - pkg-dir: 4.2.0 - dev: true - /flat-cache@3.2.0: + flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flatted: 3.3.1 - keyv: 4.5.4 - rimraf: 3.0.2 - /flatted@3.3.1: + flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - /follow-redirects@1.15.6(debug@4.3.6): + follow-redirects@1.15.6: resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} engines: {node: '>=4.0'} peerDependencies: @@ -3948,863 +2232,524 @@ packages: peerDependenciesMeta: debug: optional: true - dependencies: - debug: 4.3.6(supports-color@5.5.0) - dev: true - /for-each@0.3.3: + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - dependencies: - is-callable: 1.2.7 - dev: false - /foreground-child@3.3.0: + foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} - dependencies: - cross-spawn: 7.0.3 - signal-exit: 4.1.0 - /forever-agent@0.6.1: + forever-agent@0.6.1: resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} - dev: true - /form-data@2.3.3: + form-data@2.3.3: resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} engines: {node: '>= 0.12'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - dev: true - /form-data@4.0.0: + form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - dev: true - /fraction.js@4.3.7: + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - dev: true - /from@0.1.7: + from@0.1.7: resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} - dev: true - /fs-extra@7.0.1: + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - dev: true - /fs-extra@8.1.0: + fs-extra@8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - dev: true - /fs-extra@9.1.0: + fs-extra@9.1.0: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} - dependencies: - at-least-node: 1.0.0 - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - dev: true - /fs-minipass@2.1.0: + fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} - dependencies: - minipass: 3.3.6 - dev: true - /fs.realpath@1.0.0: + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - /fsevents@2.3.3: + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - requiresBuild: true - optional: true - /function-bind@1.1.2: + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - /function.prototype.name@1.1.6: + function.prototype.name@1.1.6: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - functions-have-names: 1.2.3 - dev: false - /functions-have-names@1.2.3: + functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - dev: false - /gauge@3.0.2: + gauge@3.0.2: resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} engines: {node: '>=10'} deprecated: This package is no longer supported. - dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - object-assign: 4.1.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - dev: true - /gensync@1.0.0-beta.2: + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} - /get-caller-file@2.0.5: + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - /get-east-asian-width@1.2.0: + get-east-asian-width@1.2.0: resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} engines: {node: '>=18'} - dev: true - /get-func-name@2.0.2: + get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - dev: true - /get-intrinsic@1.2.4: + get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - hasown: 2.0.2 - /get-stream@5.2.0: + get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} - dependencies: - pump: 3.0.0 - dev: true - /get-stream@6.0.1: + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - dev: true - /get-stream@8.0.1: + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} - dev: true - /get-symbol-description@1.0.2: + get-symbol-description@1.0.2: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - dev: false - /get-tsconfig@4.8.0: + get-tsconfig@4.8.0: resolution: {integrity: sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==} - dependencies: - resolve-pkg-maps: 1.0.0 - dev: false - /getos@3.2.1: + getos@3.2.1: resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==} - dependencies: - async: 3.2.6 - dev: true - /getpass@0.1.7: + getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} - dependencies: - assert-plus: 1.0.0 - dev: true - /glob-parent@5.1.2: + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - /glob-parent@6.0.2: + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - dependencies: - is-glob: 4.0.3 - /glob@10.3.10: + glob@10.3.10: resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true - dependencies: - foreground-child: 3.3.0 - jackspeak: 2.3.6 - minimatch: 9.0.5 - minipass: 7.1.2 - path-scurry: 1.11.1 - dev: false - /glob@10.4.5: + glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true - dependencies: - foreground-child: 3.3.0 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.0 - path-scurry: 1.11.1 - /glob@7.2.3: + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - /global-dirs@3.0.1: + global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} - dependencies: - ini: 2.0.0 - dev: true - /globals@11.12.0: + globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - /globals@13.24.0: + globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 - /globalthis@1.0.4: + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} - dependencies: - define-properties: 1.2.1 - gopd: 1.0.1 - dev: false - /globby@11.1.0: + globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - /globby@13.2.2: + globby@13.2.2: resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 4.0.0 - dev: true - /globrex@0.1.2: + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - dev: true - /gopd@1.0.1: + gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - dependencies: - get-intrinsic: 1.2.4 - /graceful-fs@4.2.11: + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - /graphemer@1.4.0: + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - /grpc-tools@1.12.4: + grpc-tools@1.12.4: resolution: {integrity: sha512-5+mLAJJma3BjnW/KQp6JBjUMgvu7Mu3dBvBPd1dcbNIb+qiR0817zDpgPjS7gRb+l/8EVNIa3cB02xI9JLToKg==} hasBin: true - requiresBuild: true - dependencies: - '@mapbox/node-pre-gyp': 1.0.11 - transitivePeerDependencies: - - encoding - - supports-color - dev: true - /hard-rejection@2.1.0: + hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} - dev: true - /has-bigints@1.0.2: + has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: false - /has-flag@3.0.0: + has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} - /has-flag@4.0.0: + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - /has-property-descriptors@1.0.2: + has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - dependencies: - es-define-property: 1.0.0 - /has-proto@1.0.3: + has-proto@1.0.3: resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} engines: {node: '>= 0.4'} - /has-symbols@1.0.3: + has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} - /has-tostringtag@1.0.2: + has-tostringtag@1.0.2: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - dev: false - /has-unicode@2.0.1: + has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - dev: true - /hasown@2.0.2: + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - dependencies: - function-bind: 1.1.2 - /hosted-git-info@4.1.0: + hosted-git-info@4.1.0: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} - dependencies: - lru-cache: 6.0.0 - dev: true - /html-encoding-sniffer@4.0.0: + html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} - dependencies: - whatwg-encoding: 3.1.1 - dev: true - /http-proxy-agent@7.0.2: + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} - dependencies: - agent-base: 7.1.1 - debug: 4.3.6(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - dev: true - /http-signature@1.3.6: + http-signature@1.3.6: resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==} engines: {node: '>=0.10'} - dependencies: - assert-plus: 1.0.0 - jsprim: 2.0.2 - sshpk: 1.18.0 - dev: true - /https-proxy-agent@5.0.1: + https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} - dependencies: - agent-base: 6.0.2 - debug: 4.3.6(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - dev: true - /https-proxy-agent@7.0.5: + https-proxy-agent@7.0.5: resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} - dependencies: - agent-base: 7.1.1 - debug: 4.3.6(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - dev: true - /human-id@1.0.2: + human-id@1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} - dev: true - /human-signals@1.1.1: + human-signals@1.1.1: resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} engines: {node: '>=8.12.0'} - dev: true - /human-signals@2.1.0: + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} - dev: true - /human-signals@5.0.0: + human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - dev: true - /iconv-lite@0.4.24: + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 - dev: true - /iconv-lite@0.6.3: + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 - dev: true - /ieee754@1.2.1: + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: true - /ignore-by-default@1.0.1: + ignore-by-default@1.0.1: resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} - dev: true - /ignore@5.3.2: + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - /immutable@4.3.7: + immutable@4.3.7: resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} - /import-fresh@3.3.0: + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - /imurmurhash@0.1.4: + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - /indent-string@4.0.0: + indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - dev: true - /indent-string@5.0.0: + indent-string@5.0.0: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} - dev: true - /inflight@1.0.6: + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - /inherits@2.0.4: + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - /ini@2.0.0: + ini@2.0.0: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} engines: {node: '>=10'} - dev: true - /internal-slot@1.0.7: + internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} - dependencies: - es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.0.6 - dev: false - /is-arguments@1.1.1: + is-arguments@1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - dev: false - /is-array-buffer@3.0.4: + is-array-buffer@3.0.4: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - dev: false - /is-arrayish@0.2.1: + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - dev: true - /is-async-function@2.0.0: + is-async-function@2.0.0: resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.2 - dev: false - /is-bigint@1.0.4: + is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} - dependencies: - has-bigints: 1.0.2 - dev: false - /is-binary-path@2.1.0: + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - dependencies: - binary-extensions: 2.3.0 - /is-boolean-object@1.1.2: + is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - dev: false - /is-bun-module@1.1.0: + is-bun-module@1.1.0: resolution: {integrity: sha512-4mTAVPlrXpaN3jtF0lsnPCMGnq4+qZjVIKq0HCpfcqf8OC1SM5oATCIAPM5V5FN05qp2NNnFndphmdZS9CV3hA==} - dependencies: - semver: 7.6.3 - dev: false - /is-callable@1.2.7: + is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - dev: false - /is-ci@3.0.1: + is-ci@3.0.1: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true - dependencies: - ci-info: 3.9.0 - dev: true - /is-core-module@2.15.1: + is-core-module@2.15.1: resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} engines: {node: '>= 0.4'} - dependencies: - hasown: 2.0.2 - /is-data-view@1.0.1: + is-data-view@1.0.1: resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} engines: {node: '>= 0.4'} - dependencies: - is-typed-array: 1.1.13 - dev: false - /is-date-object@1.0.5: + is-date-object@1.0.5: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.2 - dev: false - /is-extglob@2.1.1: + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - /is-finalizationregistry@1.0.2: + is-finalizationregistry@1.0.2: resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} - dependencies: - call-bind: 1.0.7 - dev: false - /is-fullwidth-code-point@3.0.0: + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - /is-fullwidth-code-point@4.0.0: + is-fullwidth-code-point@4.0.0: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} engines: {node: '>=12'} - dev: true - /is-fullwidth-code-point@5.0.0: + is-fullwidth-code-point@5.0.0: resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} engines: {node: '>=18'} - dependencies: - get-east-asian-width: 1.2.0 - dev: true - /is-generator-function@1.0.10: + is-generator-function@1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.2 - dev: false - /is-glob@4.0.3: + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - /is-installed-globally@0.4.0: + is-installed-globally@0.4.0: resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} engines: {node: '>=10'} - dependencies: - global-dirs: 3.0.1 - is-path-inside: 3.0.3 - dev: true - /is-map@2.0.3: + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} - dev: false - /is-negative-zero@2.0.3: + is-negative-zero@2.0.3: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} - dev: false - /is-number-object@1.0.7: + is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.2 - dev: false - /is-number@7.0.0: + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - /is-path-cwd@3.0.0: + is-path-cwd@3.0.0: resolution: {integrity: sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true - /is-path-inside@3.0.3: + is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} - /is-path-inside@4.0.0: + is-path-inside@4.0.0: resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} engines: {node: '>=12'} - dev: true - /is-plain-obj@1.1.0: + is-plain-obj@1.1.0: resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} engines: {node: '>=0.10.0'} - dev: true - /is-potential-custom-element-name@1.0.1: + is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - dev: true - /is-regex@1.1.4: + is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - dev: false - /is-set@2.0.3: + is-set@2.0.3: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} - dev: false - /is-shared-array-buffer@1.0.3: + is-shared-array-buffer@1.0.3: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - dev: false - /is-stream@2.0.1: + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} - dev: true - /is-stream@3.0.0: + is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true - /is-string@1.0.7: + is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.2 - dev: false - /is-subdir@1.2.0: + is-subdir@1.2.0: resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} engines: {node: '>=4'} - dependencies: - better-path-resolve: 1.0.0 - dev: true - /is-symbol@1.0.4: + is-symbol@1.0.4: resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - dev: false - /is-typed-array@1.1.13: + is-typed-array@1.1.13: resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} - dependencies: - which-typed-array: 1.1.15 - dev: false - /is-typedarray@1.0.0: + is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - dev: true - /is-unicode-supported@0.1.0: + is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - dev: true - /is-weakmap@2.0.2: + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} - dev: false - /is-weakref@1.0.2: + is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - dependencies: - call-bind: 1.0.7 - dev: false - /is-weakset@2.0.3: + is-weakset@2.0.3: resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - dev: false - /is-windows@1.0.2: + is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} - dev: true - /isarray@2.0.5: + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - dev: false - /isexe@2.0.0: + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - /isstream@0.1.2: + isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} - dev: true - /iterator.prototype@1.1.2: + iterator.prototype@1.1.2: resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} - dependencies: - define-properties: 1.2.1 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - reflect.getprototypeof: 1.0.6 - set-function-name: 2.0.2 - dev: false - /jackspeak@2.3.6: + jackspeak@2.3.6: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - dev: false - /jackspeak@3.4.3: + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - /jiti@1.21.6: + jiti@1.21.6: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true - /joi@17.13.3: + joi@17.13.3: resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} - dependencies: - '@hapi/hoek': 9.3.0 - '@hapi/topo': 5.1.0 - '@sideway/address': 4.1.5 - '@sideway/formula': 3.0.1 - '@sideway/pinpoint': 2.0.0 - dev: true - /jose@5.8.0: + jose@5.8.0: resolution: {integrity: sha512-E7CqYpL/t7MMnfGnK/eg416OsFCVUrU/Y3Vwe7QjKhu/BkS1Ms455+2xsqZQVN57/U2MHMBvEb5SrmAZWAIntA==} - dev: false - /joycon@3.1.1: + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} - dev: true - /js-tokens@4.0.0: + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - /js-yaml@3.14.1: + js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - dev: true - /js-yaml@4.1.0: + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - dependencies: - argparse: 2.0.1 - /jsbn@0.1.1: + jsbn@0.1.1: resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} - dev: true - /jsdom@25.0.0: + jsdom@25.0.0: resolution: {integrity: sha512-OhoFVT59T7aEq75TVw9xxEfkXgacpqAhQaYgP9y/fDqWQCMB/b1H66RfmPm/MaeaAIU9nDwMOVTlPN51+ao6CQ==} engines: {node: '>=18'} peerDependencies: @@ -4812,170 +2757,92 @@ packages: peerDependenciesMeta: canvas: optional: true - dependencies: - cssstyle: 4.0.1 - data-urls: 5.0.0 - decimal.js: 10.4.3 - form-data: 4.0.0 - html-encoding-sniffer: 4.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.12 - parse5: 7.1.2 - rrweb-cssom: 0.7.1 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 4.1.4 - w3c-xmlserializer: 5.0.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 3.1.1 - whatwg-mimetype: 4.0.0 - whatwg-url: 14.0.0 - ws: 8.18.0 - xml-name-validator: 5.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: true - /jsesc@2.5.2: + jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} hasBin: true - /json-buffer@3.0.1: + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - /json-parse-even-better-errors@2.3.1: + json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - dev: true - /json-schema-traverse@0.4.1: + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - /json-schema@0.4.0: + json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - dev: true - /json-stable-stringify-without-jsonify@1.0.1: + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - /json-stringify-safe@5.0.1: + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - dev: true - /json5@1.0.2: + json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true - dependencies: - minimist: 1.2.8 - dev: false - /json5@2.2.3: + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true - /jsonfile@4.0.0: + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - optionalDependencies: - graceful-fs: 4.2.11 - dev: true - /jsonfile@6.1.0: + jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - dev: true - /jsprim@2.0.2: + jsprim@2.0.2: resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==} engines: {'0': node >=0.6.0} - dependencies: - assert-plus: 1.0.0 - extsprintf: 1.3.0 - json-schema: 0.4.0 - verror: 1.10.0 - dev: true - /jsx-ast-utils@3.3.5: + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} - dependencies: - array-includes: 3.1.8 - array.prototype.flat: 1.3.2 - object.assign: 4.1.5 - object.values: 1.2.0 - dev: false - /keyv@4.5.4: + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - dependencies: - json-buffer: 3.0.1 - /kind-of@6.0.3: + kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - dev: true - /language-subtag-registry@0.3.23: + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} - dev: false - /language-tags@1.0.9: + language-tags@1.0.9: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} - dependencies: - language-subtag-registry: 0.3.23 - dev: false - /lazy-ass@1.6.0: + lazy-ass@1.6.0: resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==} engines: {node: '> 0.8'} - dev: true - /levn@0.4.1: + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - /lilconfig@2.1.0: + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} - /lilconfig@3.1.2: + lilconfig@3.1.2: resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} engines: {node: '>=14'} - /lines-and-columns@1.2.4: + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - /lint-staged@15.2.8: + lint-staged@15.2.8: resolution: {integrity: sha512-PUWFf2zQzsd9EFU+kM1d7UP+AZDbKFKuj+9JNVTBkhUFhbg4MAt6WfyMMwBfM4lYqd4D2Jwac5iuTu9rVj4zCQ==} engines: {node: '>=18.12.0'} hasBin: true - dependencies: - chalk: 5.3.0 - commander: 12.1.0 - debug: 4.3.6(supports-color@5.5.0) - execa: 8.0.1 - lilconfig: 3.1.2 - listr2: 8.2.4 - micromatch: 4.0.8 - pidtree: 0.6.0 - string-argv: 0.3.2 - yaml: 2.5.0 - transitivePeerDependencies: - - supports-color - dev: true - /listr2@3.14.0(enquirer@2.4.1): + listr2@3.14.0: resolution: {integrity: sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==} engines: {node: '>=10.0.0'} peerDependencies: @@ -4983,363 +2850,225 @@ packages: peerDependenciesMeta: enquirer: optional: true - dependencies: - cli-truncate: 2.1.0 - colorette: 2.0.20 - enquirer: 2.4.1 - log-update: 4.0.0 - p-map: 4.0.0 - rfdc: 1.4.1 - rxjs: 7.8.1 - through: 2.3.8 - wrap-ansi: 7.0.0 - dev: true - /listr2@8.2.4: + listr2@8.2.4: resolution: {integrity: sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==} engines: {node: '>=18.0.0'} - dependencies: - cli-truncate: 4.0.0 - colorette: 2.0.20 - eventemitter3: 5.0.1 - log-update: 6.1.0 - rfdc: 1.4.1 - wrap-ansi: 9.0.0 - dev: true - /load-tsconfig@0.2.5: + load-tsconfig@0.2.5: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true - /load-yaml-file@0.2.0: + load-yaml-file@0.2.0: resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} engines: {node: '>=6'} - dependencies: - graceful-fs: 4.2.11 - js-yaml: 3.14.1 - pify: 4.0.1 - strip-bom: 3.0.0 - dev: true - /locate-path@5.0.0: + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} - dependencies: - p-locate: 4.1.0 - dev: true - /locate-path@6.0.0: + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - dependencies: - p-locate: 5.0.0 - /lodash.camelcase@4.3.0: + lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - dev: false - /lodash.merge@4.6.2: + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - /lodash.once@4.1.1: + lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - dev: true - /lodash.sortby@4.7.0: + lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - dev: true - /lodash.startcase@4.4.0: + lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - dev: true - /lodash@4.17.21: + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: true - /log-symbols@4.1.0: + log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - dev: true - /log-update@4.0.0: + log-update@4.0.0: resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} engines: {node: '>=10'} - dependencies: - ansi-escapes: 4.3.2 - cli-cursor: 3.1.0 - slice-ansi: 4.0.0 - wrap-ansi: 6.2.0 - dev: true - /log-update@6.1.0: + log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} - dependencies: - ansi-escapes: 7.0.0 - cli-cursor: 5.0.0 - slice-ansi: 7.1.0 - strip-ansi: 7.1.0 - wrap-ansi: 9.0.0 - dev: true - /long@5.2.3: + long@5.2.3: resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} - /loose-envify@1.4.0: + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - dependencies: - js-tokens: 4.0.0 - /loupe@3.1.1: + loupe@3.1.1: resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} - dependencies: - get-func-name: 2.0.2 - dev: true - /lru-cache@10.4.3: + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - /lru-cache@4.1.5: + lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} - dependencies: - pseudomap: 1.0.2 - yallist: 2.1.2 - dev: true - /lru-cache@5.1.1: + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - dependencies: - yallist: 3.1.1 - /lru-cache@6.0.0: + lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} - dependencies: - yallist: 4.0.0 - dev: true - /lz-string@1.5.0: + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true - dev: true - /magic-string@0.30.11: + magic-string@0.30.11: resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - dev: true - /make-dir-cli@4.0.0: + make-dir-cli@4.0.0: resolution: {integrity: sha512-9BBC2CaGH0hUAx+tQthgxqYypwkTs+7oXmPdiWyDpHGo4mGB3kdudUKQGivK59C1aJroo4QLlXF7Chu/kdhYiw==} engines: {node: '>=18'} hasBin: true - dependencies: - make-dir: 5.0.0 - meow: 13.2.0 - dev: true - /make-dir@3.1.0: + make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} - dependencies: - semver: 6.3.1 - dev: true - /make-dir@5.0.0: + make-dir@5.0.0: resolution: {integrity: sha512-G0yBotnlWVonPClw+tq+xi4K7DZC9n96HjGTBDdHkstAVsDkfZhi1sTvZypXLpyQTbISBkDtK0E5XlUqDsShQg==} engines: {node: '>=18'} - dev: true - /map-obj@1.0.1: + map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} - dev: true - /map-obj@4.3.0: + map-obj@4.3.0: resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} engines: {node: '>=8'} - dev: true - /map-stream@0.1.0: + map-stream@0.1.0: resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} - dev: true - /meow@10.1.5: + meow@10.1.5: resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - '@types/minimist': 1.2.5 - camelcase-keys: 7.0.2 - decamelize: 5.0.1 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 8.0.0 - redent: 4.0.0 - trim-newlines: 4.1.1 - type-fest: 1.4.0 - yargs-parser: 20.2.9 - dev: true - /meow@13.2.0: + meow@13.2.0: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} - dev: true - /merge-stream@2.0.0: + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true - /merge2@1.4.1: + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - /micromatch@4.0.8: + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - /mime-db@1.52.0: + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - dev: true - /mime-types@2.1.35: + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.52.0 - dev: true - /mimic-fn@2.1.0: + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - dev: true - /mimic-fn@4.0.0: + mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} - dev: true - /mimic-function@5.0.1: + mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - dev: true - /min-indent@1.0.1: + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - dev: true - /mini-svg-data-uri@1.4.4: + mini-svg-data-uri@1.4.4: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true - /minimatch@3.1.2: + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - /minimatch@9.0.5: + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - dependencies: - brace-expansion: 2.0.1 - /minimist-options@4.1.0: + minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} engines: {node: '>= 6'} - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - dev: true - /minimist@1.2.8: + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - /minipass@3.3.6: + minipass@3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} - dependencies: - yallist: 4.0.0 - dev: true - /minipass@5.0.0: + minipass@5.0.0: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} - dev: true - /minipass@7.1.2: + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - /minizlib@2.1.2: + minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - dev: true - /mkdirp@1.0.4: + mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true - dev: true - /moment@2.30.1: + moment@2.30.1: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - dev: false - /mri@1.2.0: + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} - dev: true - /ms@2.1.2: + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - /ms@2.1.3: + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - /mz@2.7.0: + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - /nanoid@3.3.7: + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - /natural-compare@1.4.0: + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - /next-themes@0.2.1(next@14.2.5)(react-dom@18.3.1)(react@18.3.1): + next-themes@0.2.1: resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} peerDependencies: next: '*' react: '*' react-dom: '*' - dependencies: - next: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - dev: false - /next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8): + next@14.2.5: resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} engines: {node: '>=18.17.0'} hasBin: true @@ -5356,47 +3085,14 @@ packages: optional: true sass: optional: true - dependencies: - '@next/env': 14.2.5 - '@swc/helpers': 0.5.5 - busboy: 1.6.0 - caniuse-lite: 1.0.30001654 - graceful-fs: 4.2.11 - postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - sass: 1.77.8 - styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) - optionalDependencies: - '@next/swc-darwin-arm64': 14.2.5 - '@next/swc-darwin-x64': 14.2.5 - '@next/swc-linux-arm64-gnu': 14.2.5 - '@next/swc-linux-arm64-musl': 14.2.5 - '@next/swc-linux-x64-gnu': 14.2.5 - '@next/swc-linux-x64-musl': 14.2.5 - '@next/swc-win32-arm64-msvc': 14.2.5 - '@next/swc-win32-ia32-msvc': 14.2.5 - '@next/swc-win32-x64-msvc': 14.2.5 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - dev: false - /nice-grpc-common@2.0.2: + nice-grpc-common@2.0.2: resolution: {integrity: sha512-7RNWbls5kAL1QVUOXvBsv1uO0wPQK3lHv+cY1gwkTzirnG1Nop4cBJZubpgziNbaVc/bl9QJcyvsf/NQxa3rjQ==} - dependencies: - ts-error: 1.0.6 - dev: false - /nice-grpc@2.0.1: + nice-grpc@2.0.1: resolution: {integrity: sha512-Q5CGXO08STsv+HAkXeFgRayANT62X1LnIDhNXdCf+LP0XaP7EiHM0Cr3QefnoFjDZAx/Kxq+qiQfY66BrtKcNQ==} - dependencies: - '@grpc/grpc-js': 1.11.1 - abort-controller-x: 0.4.3 - nice-grpc-common: 2.0.2 - dev: false - /node-fetch@2.7.0: + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} peerDependencies: @@ -5404,402 +3100,254 @@ packages: peerDependenciesMeta: encoding: optional: true - dependencies: - whatwg-url: 5.0.0 - dev: true - /node-releases@2.0.18: + node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - /nodemon@3.1.4: + nodemon@3.1.4: resolution: {integrity: sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==} engines: {node: '>=10'} hasBin: true - dependencies: - chokidar: 3.6.0 - debug: 4.3.6(supports-color@5.5.0) - ignore-by-default: 1.0.1 - minimatch: 3.1.2 - pstree.remy: 1.1.8 - semver: 7.6.3 - simple-update-notifier: 2.0.0 - supports-color: 5.5.0 - touch: 3.1.1 - undefsafe: 2.0.5 - dev: true - /nopt@5.0.0: + nopt@5.0.0: resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} engines: {node: '>=6'} hasBin: true - dependencies: - abbrev: 1.1.1 - dev: true - /normalize-package-data@3.0.3: + normalize-package-data@3.0.3: resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} engines: {node: '>=10'} - dependencies: - hosted-git-info: 4.1.0 - is-core-module: 2.15.1 - semver: 7.6.3 - validate-npm-package-license: 3.0.4 - dev: true - /normalize-path@3.0.0: + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - /normalize-range@0.1.2: + normalize-range@0.1.2: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} - dev: true - /npm-run-path@4.0.1: + npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} - dependencies: - path-key: 3.1.1 - dev: true - /npm-run-path@5.3.0: + npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - path-key: 4.0.0 - dev: true - /npmlog@5.0.1: + npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} deprecated: This package is no longer supported. - dependencies: - are-we-there-yet: 2.0.0 - console-control-strings: 1.1.0 - gauge: 3.0.2 - set-blocking: 2.0.0 - dev: true - /nwsapi@2.2.12: + nwsapi@2.2.12: resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==} - dev: true - /object-assign@4.1.1: + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - /object-hash@3.0.0: + object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - /object-inspect@1.13.2: + object-inspect@1.13.2: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} engines: {node: '>= 0.4'} - /object-is@1.1.6: + object-is@1.1.6: resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - dev: false - /object-keys@1.1.1: + object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - dev: false - /object.assign@4.1.5: + object.assign@4.1.5: resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - has-symbols: 1.0.3 - object-keys: 1.1.1 - dev: false - /object.entries@1.1.8: + object.entries@1.1.8: resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - dev: false - /object.fromentries@2.0.8: + object.fromentries@2.0.8: resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - dev: false - /object.groupby@1.0.3: + object.groupby@1.0.3: resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - dev: false - /object.values@1.2.0: + object.values@1.2.0: resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - dev: false - /once@1.4.0: + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - /onetime@5.1.2: + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - dependencies: - mimic-fn: 2.1.0 - dev: true - /onetime@6.0.0: + onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} - dependencies: - mimic-fn: 4.0.0 - dev: true - /onetime@7.0.0: + onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - dependencies: - mimic-function: 5.0.1 - dev: true - /optionator@0.9.4: + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - /os-tmpdir@1.0.2: + os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} - dev: true - /ospath@1.2.2: + ospath@1.2.2: resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==} - dev: true - /outdent@0.5.0: + outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} - dev: true - /p-filter@2.1.0: + p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} - dependencies: - p-map: 2.1.0 - dev: true - /p-limit@2.3.0: + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} - dependencies: - p-try: 2.2.0 - dev: true - /p-limit@3.1.0: + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - dependencies: - yocto-queue: 0.1.0 - /p-locate@4.1.0: + p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} - dependencies: - p-limit: 2.3.0 - dev: true - /p-locate@5.0.0: + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - dependencies: - p-limit: 3.1.0 - /p-map@2.1.0: + p-map@2.1.0: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} - dev: true - /p-map@4.0.0: + p-map@4.0.0: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} - dependencies: - aggregate-error: 3.1.0 - dev: true - /p-map@5.5.0: + p-map@5.5.0: resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} engines: {node: '>=12'} - dependencies: - aggregate-error: 4.0.1 - dev: true - /p-try@2.2.0: + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - dev: true - /package-json-from-dist@1.0.0: + package-json-from-dist@1.0.0: resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} - /parent-module@1.0.1: + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - /parse-json@5.2.0: + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} - dependencies: - '@babel/code-frame': 7.24.7 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - dev: true - /parse5@7.1.2: + parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} - dependencies: - entities: 4.5.0 - dev: true - /path-exists@4.0.0: + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - /path-is-absolute@1.0.1: + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} - /path-key@3.1.1: + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - /path-key@4.0.0: + path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} - dev: true - /path-parse@1.0.7: + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - /path-scurry@1.11.1: + path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - /path-type@4.0.0: + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - /pathe@1.1.2: + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - dev: true - /pathval@2.0.0: + pathval@2.0.0: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} - dev: true - /pause-stream@0.0.11: + pause-stream@0.0.11: resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} - dependencies: - through: 2.3.8 - dev: true - /pend@1.2.0: + pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - dev: true - /performance-now@2.1.0: + performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - dev: true - /picocolors@1.0.1: + picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} - /picomatch@2.3.1: + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - /pidtree@0.6.0: + pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} hasBin: true - dev: true - /pify@2.3.0: + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - /pify@4.0.1: + pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} - dev: true - /pirates@4.0.6: + pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - /pkg-dir@4.2.0: + pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} - dependencies: - find-up: 4.1.0 - dev: true - /possible-typed-array-names@1.0.0: + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} - dev: false - /postcss-import@15.1.0(postcss@8.4.41): + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} peerDependencies: postcss: ^8.0.0 - dependencies: - postcss: 8.4.41 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.8 - /postcss-js@4.0.1(postcss@8.4.41): + postcss-js@4.0.1: resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} engines: {node: ^12 || ^14 || >= 16} peerDependencies: postcss: ^8.4.21 - dependencies: - camelcase-css: 2.0.1 - postcss: 8.4.41 - /postcss-load-config@4.0.2(postcss@8.4.41): + postcss-load-config@4.0.2: resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} engines: {node: '>= 14'} peerDependencies: @@ -5810,12 +3358,8 @@ packages: optional: true ts-node: optional: true - dependencies: - lilconfig: 3.1.2 - postcss: 8.4.41 - yaml: 2.5.0 - /postcss-load-config@6.0.1: + postcss-load-config@6.0.1: resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} engines: {node: '>= 18'} peerDependencies: @@ -5832,61 +3376,37 @@ packages: optional: true yaml: optional: true - dependencies: - lilconfig: 3.1.2 - dev: true - /postcss-nested@6.2.0(postcss@8.4.41): + postcss-nested@6.2.0: resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 - dependencies: - postcss: 8.4.41 - postcss-selector-parser: 6.1.2 - /postcss-selector-parser@6.1.2: + postcss-selector-parser@6.1.2: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - /postcss-value-parser@4.2.0: + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - /postcss@8.4.31: + postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 - dev: false - /postcss@8.4.41: + postcss@8.4.41: resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 - /preferred-pm@3.1.4: + preferred-pm@3.1.4: resolution: {integrity: sha512-lEHd+yEm22jXdCphDrkvIJQU66EuLojPPtvZkpKIkiD+l0DMThF/niqZKJSoU8Vl7iuvtmzyMhir9LdVy5WMnA==} engines: {node: '>=10'} - dependencies: - find-up: 5.0.0 - find-yarn-workspace-root2: 1.2.16 - path-exists: 4.0.0 - which-pm: 2.2.0 - dev: true - /prelude-ls@1.2.1: + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - /prettier-plugin-organize-imports@4.0.0(prettier@3.3.3)(typescript@5.5.4): + prettier-plugin-organize-imports@4.0.0: resolution: {integrity: sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==} peerDependencies: '@vue/language-plugin-pug': ^2.0.24 @@ -5898,12 +3418,8 @@ packages: optional: true vue-tsc: optional: true - dependencies: - prettier: 3.3.3 - typescript: 5.5.4 - dev: true - /prettier-plugin-tailwindcss@0.6.5(prettier-plugin-organize-imports@4.0.0)(prettier@3.3.3): + prettier-plugin-tailwindcss@0.6.5: resolution: {integrity: sha512-axfeOArc/RiGHjOIy9HytehlC0ZLeMaqY09mm8YCkMzznKiDkwFzOpBvtuhuv3xG5qB73+Mj7OCe2j/L1ryfuQ==} engines: {node: '>=14.21.3'} peerDependencies: @@ -5954,819 +3470,457 @@ packages: optional: true prettier-plugin-svelte: optional: true - dependencies: - prettier: 3.3.3 - prettier-plugin-organize-imports: 4.0.0(prettier@3.3.3)(typescript@5.5.4) - dev: true - /prettier@2.8.8: + prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true - dev: true - /prettier@3.3.3: + prettier@3.3.3: resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} engines: {node: '>=14'} hasBin: true - dev: true - /pretty-bytes@5.6.0: + pretty-bytes@5.6.0: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} engines: {node: '>=6'} - dev: true - /pretty-format@27.5.1: + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - dependencies: - ansi-regex: 5.0.1 - ansi-styles: 5.2.0 - react-is: 17.0.2 - dev: true - /process@0.11.10: + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} - dev: true - /prop-types@15.8.1: + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - dev: false - /protobufjs@7.4.0: + protobufjs@7.4.0: resolution: {integrity: sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==} engines: {node: '>=12.0.0'} - requiresBuild: true - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 - '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 - '@protobufjs/path': 1.1.2 - '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 - '@types/node': 22.1.0 - long: 5.2.3 - /proxy-from-env@1.0.0: + proxy-from-env@1.0.0: resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} - dev: true - /proxy-from-env@1.1.0: + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - dev: true - /ps-tree@1.2.0: + ps-tree@1.2.0: resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} engines: {node: '>= 0.10'} hasBin: true - dependencies: - event-stream: 3.3.4 - dev: true - /pseudomap@1.0.2: + pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} - dev: true - /psl@1.9.0: + psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} - dev: true - /pstree.remy@1.1.8: + pstree.remy@1.1.8: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} - dev: true - /pump@3.0.0: + pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - dev: true - /punycode@2.3.1: + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - /qrcode.react@3.1.0(react@18.3.1): + qrcode.react@3.1.0: resolution: {integrity: sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.3.1 - dev: false - /qs@6.10.4: + qs@6.10.4: resolution: {integrity: sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==} engines: {node: '>=0.6'} - dependencies: - side-channel: 1.0.6 - dev: true - /querystringify@2.2.0: + querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - dev: true - /queue-microtask@1.2.3: + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - /quick-lru@5.1.1: + quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} - dev: true - /react-dom@18.3.1(react@18.3.1): + react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: react: ^18.3.1 - dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 - /react-hook-form@7.39.5(react@18.3.1): + react-hook-form@7.39.5: resolution: {integrity: sha512-OE0HKyz5IPc6svN2wd+e+evidZrw4O4WZWAWYzQVZuHi+hYnHFSLnxOq0ddjbdmaLIsLHut/ab7j72y2QT3+KA==} engines: {node: '>=12.22.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 - dependencies: - react: 18.3.1 - dev: false - /react-is@16.13.1: + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - dev: false - /react-is@17.0.2: + react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - dev: true - /react-refresh@0.14.2: + react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} - dev: true - /react@18.3.1: + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} - dependencies: - loose-envify: 1.4.0 - /read-cache@1.0.0: + read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - dependencies: - pify: 2.3.0 - /read-pkg-up@8.0.0: + read-pkg-up@8.0.0: resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} engines: {node: '>=12'} - dependencies: - find-up: 5.0.0 - read-pkg: 6.0.0 - type-fest: 1.4.0 - dev: true - /read-pkg@6.0.0: + read-pkg@6.0.0: resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==} engines: {node: '>=12'} - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 3.0.3 - parse-json: 5.2.0 - type-fest: 1.4.0 - dev: true - /read-yaml-file@1.1.0: + read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} - dependencies: - graceful-fs: 4.2.11 - js-yaml: 3.14.1 - pify: 4.0.1 - strip-bom: 3.0.0 - dev: true - /readable-stream@3.6.2: + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - dev: true - /readdirp@3.6.0: + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 - /redent@3.0.0: + redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 - dev: true - /redent@4.0.0: + redent@4.0.0: resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} engines: {node: '>=12'} - dependencies: - indent-string: 5.0.0 - strip-indent: 4.0.0 - dev: true - /reflect.getprototypeof@1.0.6: + reflect.getprototypeof@1.0.6: resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - globalthis: 1.0.4 - which-builtin-type: 1.1.4 - dev: false - /regenerator-runtime@0.14.1: + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - dev: true - /regexp.prototype.flags@1.5.2: + regexp.prototype.flags@1.5.2: resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-errors: 1.3.0 - set-function-name: 2.0.2 - dev: false - /request-progress@3.0.0: + request-progress@3.0.0: resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==} - dependencies: - throttleit: 1.0.1 - dev: true - /require-directory@2.1.1: + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - /requires-port@1.0.0: + requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - dev: true - /resolve-from@4.0.0: + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - /resolve-from@5.0.0: + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - dev: true - /resolve-pkg-maps@1.0.0: + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - dev: false - /resolve@1.22.8: + resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true - dependencies: - is-core-module: 2.15.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - /resolve@2.0.0-next.5: + resolve@2.0.0-next.5: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true - dependencies: - is-core-module: 2.15.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: false - /restore-cursor@3.1.0: + restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - dev: true - /restore-cursor@5.1.0: + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} - dependencies: - onetime: 7.0.0 - signal-exit: 4.1.0 - dev: true - /reusify@1.0.4: + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - /rfdc@1.4.1: + rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - dev: true - /rimraf@3.0.2: + rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - dependencies: - glob: 7.2.3 - /rollup@4.21.1: + rollup@4.21.1: resolution: {integrity: sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - dependencies: - '@types/estree': 1.0.5 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.21.1 - '@rollup/rollup-android-arm64': 4.21.1 - '@rollup/rollup-darwin-arm64': 4.21.1 - '@rollup/rollup-darwin-x64': 4.21.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.21.1 - '@rollup/rollup-linux-arm-musleabihf': 4.21.1 - '@rollup/rollup-linux-arm64-gnu': 4.21.1 - '@rollup/rollup-linux-arm64-musl': 4.21.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.21.1 - '@rollup/rollup-linux-riscv64-gnu': 4.21.1 - '@rollup/rollup-linux-s390x-gnu': 4.21.1 - '@rollup/rollup-linux-x64-gnu': 4.21.1 - '@rollup/rollup-linux-x64-musl': 4.21.1 - '@rollup/rollup-win32-arm64-msvc': 4.21.1 - '@rollup/rollup-win32-ia32-msvc': 4.21.1 - '@rollup/rollup-win32-x64-msvc': 4.21.1 - fsevents: 2.3.3 - dev: true - /rrweb-cssom@0.6.0: + rrweb-cssom@0.6.0: resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} - dev: true - /rrweb-cssom@0.7.1: + rrweb-cssom@0.7.1: resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} - dev: true - /run-parallel@1.2.0: + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - /rxjs@7.8.1: + rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - dependencies: - tslib: 2.7.0 - dev: true - /safe-array-concat@1.1.2: + safe-array-concat@1.1.2: resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} engines: {node: '>=0.4'} - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - isarray: 2.0.5 - dev: false - /safe-buffer@5.2.1: + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: true - /safe-regex-test@1.0.3: + safe-regex-test@1.0.3: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-regex: 1.1.4 - dev: false - /safer-buffer@2.1.2: + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - dev: true - /sass@1.77.8: + sass@1.77.8: resolution: {integrity: sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==} engines: {node: '>=14.0.0'} hasBin: true - dependencies: - chokidar: 3.6.0 - immutable: 4.3.7 - source-map-js: 1.2.0 - /saxes@6.0.0: + saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - dependencies: - xmlchars: 2.2.0 - dev: true - /scheduler@0.23.2: + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - dependencies: - loose-envify: 1.4.0 - /semver@6.3.1: + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - /semver@7.6.3: + semver@7.6.3: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} hasBin: true - /server-only@0.0.1: + server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} - dev: false - /set-blocking@2.0.0: + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - dev: true - /set-function-length@1.2.2: + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.2.4 - gopd: 1.0.1 - has-property-descriptors: 1.0.2 - /set-function-name@2.0.2: + set-function-name@2.0.2: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - dev: false - /shebang-command@1.2.0: + shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} - dependencies: - shebang-regex: 1.0.0 - dev: true - /shebang-command@2.0.0: + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - /shebang-regex@1.0.0: + shebang-regex@1.0.0: resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} engines: {node: '>=0.10.0'} - dev: true - /shebang-regex@3.0.0: + shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - /shell-quote@1.8.1: + shell-quote@1.8.1: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} - dev: true - /side-channel@1.0.6: + side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - object-inspect: 1.13.2 - /siginfo@2.0.0: + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - dev: true - /signal-exit@3.0.7: + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: true - /signal-exit@4.1.0: + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - /simple-update-notifier@2.0.0: + simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} - dependencies: - semver: 7.6.3 - dev: true - /slash@3.0.0: + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - /slash@4.0.0: + slash@4.0.0: resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} engines: {node: '>=12'} - dev: true - /slice-ansi@3.0.0: + slice-ansi@3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - dev: true - /slice-ansi@4.0.0: + slice-ansi@4.0.0: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - dev: true - /slice-ansi@5.0.0: + slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} - dependencies: - ansi-styles: 6.2.1 - is-fullwidth-code-point: 4.0.0 - dev: true - /slice-ansi@7.1.0: + slice-ansi@7.1.0: resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} engines: {node: '>=18'} - dependencies: - ansi-styles: 6.2.1 - is-fullwidth-code-point: 5.0.0 - dev: true - /source-map-js@1.2.0: + source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} - /source-map@0.8.0-beta.0: + source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} - dependencies: - whatwg-url: 7.1.0 - dev: true - /spawn-command@0.0.2: + spawn-command@0.0.2: resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} - dev: true - /spawndamnit@2.0.0: + spawndamnit@2.0.0: resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} - dependencies: - cross-spawn: 5.1.0 - signal-exit: 3.0.7 - dev: true - /spdx-correct@3.2.0: + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.20 - dev: true - /spdx-exceptions@2.5.0: + spdx-exceptions@2.5.0: resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - dev: true - /spdx-expression-parse@3.0.1: + spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.20 - dev: true - /spdx-license-ids@3.0.20: + spdx-license-ids@3.0.20: resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} - dev: true - /split@0.3.3: + split@0.3.3: resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} - dependencies: - through: 2.3.8 - dev: true - /sprintf-js@1.0.3: + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - dev: true - /sshpk@1.18.0: + sshpk@1.18.0: resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} engines: {node: '>=0.10.0'} hasBin: true - dependencies: - asn1: 0.2.6 - assert-plus: 1.0.0 - bcrypt-pbkdf: 1.0.2 - dashdash: 1.14.1 - ecc-jsbn: 0.1.2 - getpass: 0.1.7 - jsbn: 0.1.1 - safer-buffer: 2.1.2 - tweetnacl: 0.14.5 - dev: true - /stackback@0.0.2: + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - dev: true - /start-server-and-test@2.0.5: + start-server-and-test@2.0.5: resolution: {integrity: sha512-2CV4pz69NJVJKQmJeSr+O+SPtOreu0yxvhPmSXclzmAKkPREuMabyMh+Txpzemjx0RDzXOcG2XkhiUuxjztSQw==} engines: {node: '>=16'} hasBin: true - dependencies: - arg: 5.0.2 - bluebird: 3.7.2 - check-more-types: 2.24.0 - debug: 4.3.6(supports-color@5.5.0) - execa: 5.1.1 - lazy-ass: 1.6.0 - ps-tree: 1.2.0 - wait-on: 7.2.0(debug@4.3.6) - transitivePeerDependencies: - - supports-color - dev: true - /std-env@3.7.0: + std-env@3.7.0: resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - dev: true - /stop-iteration-iterator@1.0.0: + stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} engines: {node: '>= 0.4'} - dependencies: - internal-slot: 1.0.7 - dev: false - /stream-combiner@0.0.4: + stream-combiner@0.0.4: resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} - dependencies: - duplexer: 0.1.2 - dev: true - /streamsearch@1.1.0: + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} - dev: false - /string-argv@0.3.2: + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} - dev: true - /string-width@4.2.3: + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - /string-width@5.1.2: + string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - /string-width@7.2.0: + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - dependencies: - emoji-regex: 10.4.0 - get-east-asian-width: 1.2.0 - strip-ansi: 7.1.0 - dev: true - /string.prototype.includes@2.0.0: + string.prototype.includes@2.0.0: resolution: {integrity: sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==} - dependencies: - define-properties: 1.2.1 - es-abstract: 1.23.3 - dev: false - /string.prototype.matchall@4.0.11: + string.prototype.matchall@4.0.11: resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 - gopd: 1.0.1 - has-symbols: 1.0.3 - internal-slot: 1.0.7 - regexp.prototype.flags: 1.5.2 - set-function-name: 2.0.2 - side-channel: 1.0.6 - dev: false - /string.prototype.repeat@1.0.0: + string.prototype.repeat@1.0.0: resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - dependencies: - define-properties: 1.2.1 - es-abstract: 1.23.3 - dev: false - /string.prototype.trim@1.2.9: + string.prototype.trim@1.2.9: resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - dev: false - /string.prototype.trimend@1.0.8: + string.prototype.trimend@1.0.8: resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - dev: false - /string.prototype.trimstart@1.0.8: + string.prototype.trimstart@1.0.8: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - dev: false - /string_decoder@1.3.0: + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - dependencies: - safe-buffer: 5.2.1 - dev: true - /strip-ansi@6.0.1: + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - /strip-ansi@7.1.0: + strip-ansi@7.1.0: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - dependencies: - ansi-regex: 6.0.1 - /strip-bom@3.0.0: + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - /strip-final-newline@2.0.0: + strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} - dev: true - /strip-final-newline@3.0.0: + strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} - dev: true - /strip-indent@3.0.0: + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} - dependencies: - min-indent: 1.0.1 - dev: true - /strip-indent@4.0.0: + strip-indent@4.0.0: resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} engines: {node: '>=12'} - dependencies: - min-indent: 1.0.1 - dev: true - /strip-json-comments@3.1.1: + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - /styled-jsx@5.1.1(@babel/core@7.25.2)(react@18.3.1): + styled-jsx@5.1.1: resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} peerDependencies: @@ -6778,266 +3932,155 @@ packages: optional: true babel-plugin-macros: optional: true - dependencies: - '@babel/core': 7.25.2 - client-only: 0.0.1 - react: 18.3.1 - dev: false - /sucrase@3.35.0: + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - commander: 4.1.1 - glob: 10.4.5 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.6 - ts-interface-checker: 0.1.13 - /supports-color@5.5.0: + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} - dependencies: - has-flag: 3.0.0 - /supports-color@7.2.0: + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - /supports-color@8.1.1: + supports-color@8.1.1: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} - dependencies: - has-flag: 4.0.0 - /supports-preserve-symlinks-flag@1.0.0: + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - /swr@2.2.5(react@18.3.1): + swr@2.2.5: resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==} peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 - dependencies: - client-only: 0.0.1 - react: 18.3.1 - use-sync-external-store: 1.2.2(react@18.3.1) - dev: false - /symbol-tree@3.2.4: + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - dev: true - /tailwindcss@3.4.9: + tailwindcss@3.4.9: resolution: {integrity: sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==} engines: {node: '>=14.0.0'} hasBin: true - dependencies: - '@alloc/quick-lru': 5.2.0 - arg: 5.0.2 - chokidar: 3.6.0 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.2 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.21.6 - lilconfig: 2.1.0 - micromatch: 4.0.8 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.0.1 - postcss: 8.4.41 - postcss-import: 15.1.0(postcss@8.4.41) - postcss-js: 4.0.1(postcss@8.4.41) - postcss-load-config: 4.0.2(postcss@8.4.41) - postcss-nested: 6.2.0(postcss@8.4.41) - postcss-selector-parser: 6.1.2 - resolve: 1.22.8 - sucrase: 3.35.0 - transitivePeerDependencies: - - ts-node - /tapable@2.2.1: + tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - dev: false - /tar@6.2.1: + tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - dev: true - /term-size@2.2.1: + term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} - dev: true - /text-table@0.2.0: + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - /thenify-all@1.6.0: + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} - dependencies: - thenify: 3.3.1 - /thenify@3.3.1: + thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - dependencies: - any-promise: 1.3.0 - /throttleit@1.0.1: + throttleit@1.0.1: resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==} - dev: true - /through@2.3.8: + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - dev: true - /tinybench@2.9.0: + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - dev: true - /tinycolor2@1.4.2: + tinycolor2@1.4.2: resolution: {integrity: sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==} - dev: false - /tinypool@1.0.1: + tinypool@1.0.1: resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} engines: {node: ^18.0.0 || >=20.0.0} - dev: true - /tinyrainbow@1.2.0: + tinyrainbow@1.2.0: resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} - dev: true - /tinyspy@3.0.0: + tinyspy@3.0.0: resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==} engines: {node: '>=14.0.0'} - dev: true - /tmp@0.0.33: + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} - dependencies: - os-tmpdir: 1.0.2 - dev: true - /tmp@0.2.3: + tmp@0.2.3: resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} engines: {node: '>=14.14'} - dev: true - /to-fast-properties@2.0.0: + to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} - /to-regex-range@5.0.1: + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - /toggle-selection@1.0.6: + toggle-selection@1.0.6: resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} - dev: false - /touch@3.1.1: + touch@3.1.1: resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} hasBin: true - dev: true - /tough-cookie@4.1.4: + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} - dependencies: - psl: 1.9.0 - punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 - dev: true - /tr46@0.0.3: + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: true - /tr46@1.0.1: + tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} - dependencies: - punycode: 2.3.1 - dev: true - /tr46@5.0.0: + tr46@5.0.0: resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} engines: {node: '>=18'} - dependencies: - punycode: 2.3.1 - dev: true - /tree-kill@1.2.2: + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - dev: true - /trim-newlines@4.1.1: + trim-newlines@4.1.1: resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==} engines: {node: '>=12'} - dev: true - /ts-api-utils@1.3.0(typescript@5.5.4): + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} peerDependencies: typescript: '>=4.2.0' - dependencies: - typescript: 5.5.4 - dev: false - /ts-error@1.0.6: + ts-error@1.0.6: resolution: {integrity: sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==} - dev: false - /ts-interface-checker@0.1.13: + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - /ts-poet@6.9.0: + ts-poet@6.9.0: resolution: {integrity: sha512-roe6W6MeZmCjRmppyfOURklO5tQFQ6Sg7swURKkwYJvV7dbGCrK28um5+51iW3twdPRKtwarqFAVMU6G1mvnuQ==} - dependencies: - dprint-node: 1.0.8 - dev: true - /ts-proto-descriptors@1.16.0: + ts-proto-descriptors@1.16.0: resolution: {integrity: sha512-3yKuzMLpltdpcyQji1PJZRfoo4OJjNieKTYkQY8pF7xGKsYz/RHe3aEe4KiRxcinoBmnEhmuI+yJTxLb922ULA==} - dependencies: - long: 5.2.3 - protobufjs: 7.4.0 - dev: true - /ts-proto@1.181.2: + ts-proto@1.181.2: resolution: {integrity: sha512-knJ8dtjn2Pd0c5ZGZG8z9DMiD4PUY8iGI9T9tb8DvGdWRMkLpf0WcPO7G+7cmbZyxvNTAG6ci3fybEaFgMZIvg==} hasBin: true - dependencies: - case-anything: 2.1.13 - protobufjs: 7.4.0 - ts-poet: 6.9.0 - ts-proto-descriptors: 1.16.0 - dev: true - /tsconfck@3.1.1(typescript@5.5.4): + tsconfck@3.1.1: resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==} engines: {node: ^18 || >=20} hasBin: true @@ -7046,23 +4089,14 @@ packages: peerDependenciesMeta: typescript: optional: true - dependencies: - typescript: 5.5.4 - dev: true - /tsconfig-paths@3.15.0: + tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 - dev: false - /tslib@2.7.0: + tslib@2.7.0: resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} - /tsup@8.2.4(typescript@5.5.4): + tsup@8.2.4: resolution: {integrity: sha512-akpCPePnBnC/CXgRrcy72ZSntgIEUa1jN0oJbbvpALWKNOz1B7aM+UVDWGRGIO/T/PZugAESWDJUAb5FD48o8Q==} engines: {node: '>=18'} hasBin: true @@ -7080,307 +4114,158 @@ packages: optional: true typescript: optional: true - dependencies: - bundle-require: 5.0.0(esbuild@0.23.1) - cac: 6.7.14 - chokidar: 3.6.0 - consola: 3.2.3 - debug: 4.3.6(supports-color@5.5.0) - esbuild: 0.23.1 - execa: 5.1.1 - globby: 11.1.0 - joycon: 3.1.1 - picocolors: 1.0.1 - postcss-load-config: 6.0.1 - resolve-from: 5.0.0 - rollup: 4.21.1 - source-map: 0.8.0-beta.0 - sucrase: 3.35.0 - tree-kill: 1.2.2 - typescript: 5.5.4 - transitivePeerDependencies: - - jiti - - supports-color - - tsx - - yaml - dev: true - /tunnel-agent@0.6.0: + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - dependencies: - safe-buffer: 5.2.1 - dev: true - /turbo-darwin-64@2.0.12: + turbo-darwin-64@2.0.12: resolution: {integrity: sha512-NAgfgbXxX/JScWQmmQnGbPuFZq7LIswHfcMk5JwyBXQM/xmklNOxxac7MnGGIOf19Z2f6S3qHy17VIj0SeGfnA==} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /turbo-darwin-arm64@2.0.12: + turbo-darwin-arm64@2.0.12: resolution: {integrity: sha512-cP02uer5KSJ+fXL+OfRRk5hnVjV0c60hxDgNcJxrZpfhun7HHoKDDR7w2xhQntiA45aC6ZZEXRqMKpj6GAmKbg==} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /turbo-linux-64@2.0.12: + turbo-linux-64@2.0.12: resolution: {integrity: sha512-+mQgGfg1eq5qF+wenK/FKJaNMNAo5DQLC4htQy+8osW+fx6U+8+6UlPQPaycAWDEqwOI7NwuqkeHfkEQLQUTyQ==} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /turbo-linux-arm64@2.0.12: + turbo-linux-arm64@2.0.12: resolution: {integrity: sha512-KFyEZDXfPU1DK4zimxdCcqAcK7IIttX4mfsgB7NsSEOmH0dhHOih/YFYiyEDC1lTRx0C2RlzQ0Kjjdz48AN5Eg==} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /turbo-windows-64@2.0.12: + turbo-windows-64@2.0.12: resolution: {integrity: sha512-kJj4KCkZTkDTDCqsSw1m1dbO4WeoQq1mYUm/thXOH0OkeqYbSMt0EyoTcJOgKUDsrMnzZD2gPfYrlYHtV69lVA==} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /turbo-windows-arm64@2.0.12: + turbo-windows-arm64@2.0.12: resolution: {integrity: sha512-TY3ROxguDilN2olCwcZMaePdW01Xhma0pZU7bNhsQEqca9RGAmsZBuzfGnTMcWPmv4tpnb/PlX1hrt1Hod/44Q==} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /turbo@2.0.12: + turbo@2.0.12: resolution: {integrity: sha512-8s2KwqjwQj7z8Z53SUZSKVkQOZ2/Sl4D2F440oaBY/k2lGju60dW6srEpnn8/RIDeICZmQn3pQHF79Jfnc5Skw==} hasBin: true - optionalDependencies: - turbo-darwin-64: 2.0.12 - turbo-darwin-arm64: 2.0.12 - turbo-linux-64: 2.0.12 - turbo-linux-arm64: 2.0.12 - turbo-windows-64: 2.0.12 - turbo-windows-arm64: 2.0.12 - dev: true - /tweetnacl@0.14.5: + tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - dev: true - /type-check@0.4.0: + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - /type-fest@0.20.2: + type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} - /type-fest@0.21.3: + type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - dev: true - /type-fest@1.4.0: + type-fest@1.4.0: resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} engines: {node: '>=10'} - dev: true - /typed-array-buffer@1.0.2: + typed-array-buffer@1.0.2: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-typed-array: 1.1.13 - dev: false - /typed-array-byte-length@1.0.1: + typed-array-byte-length@1.0.1: resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - dev: false - /typed-array-byte-offset@1.0.2: + typed-array-byte-offset@1.0.2: resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - dev: false - /typed-array-length@1.0.6: + typed-array-length@1.0.6: resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - possible-typed-array-names: 1.0.0 - dev: false - /typescript@5.5.4: + typescript@5.5.4: resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} hasBin: true - /unbox-primitive@1.0.2: + unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - dependencies: - call-bind: 1.0.7 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 - dev: false - /undefsafe@2.0.5: + undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} - dev: true - /undici-types@6.13.0: + undici-types@6.13.0: resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==} - /undici@5.28.4: + undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} engines: {node: '>=14.0'} - dependencies: - '@fastify/busboy': 2.1.1 - dev: false - /universalify@0.1.2: + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} - dev: true - /universalify@0.2.0: + universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} - dev: true - /universalify@2.0.1: + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} - dev: true - /untildify@4.0.0: + untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} - dev: true - /update-browserslist-db@1.1.0(browserslist@4.23.3): + update-browserslist-db@1.1.0: resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.23.3 - escalade: 3.2.0 - picocolors: 1.0.1 - /uri-js@4.4.1: + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.3.1 - /url-parse@1.5.10: + url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - dev: true - /use-sync-external-store@1.2.2(react@18.3.1): + use-sync-external-store@1.2.2: resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.3.1 - dev: false - /util-deprecate@1.0.2: + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - /uuid@8.3.2: + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true - dev: true - /validate-npm-package-license@3.0.4: + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 - dev: true - /verror@1.10.0: + verror@1.10.0: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} - dependencies: - assert-plus: 1.0.0 - core-util-is: 1.0.2 - extsprintf: 1.3.0 - dev: true - /vite-node@2.0.5: + vite-node@2.0.5: resolution: {integrity: sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - dependencies: - cac: 6.7.14 - debug: 4.3.6(supports-color@5.5.0) - pathe: 1.1.2 - tinyrainbow: 1.2.0 - vite: 5.4.2 - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - dev: true - /vite-tsconfig-paths@5.0.1(typescript@5.5.4)(vite@5.4.2): + vite-tsconfig-paths@5.0.1: resolution: {integrity: sha512-yqwv+LstU7NwPeNqajZzLEBVpUFU6Dugtb2P84FXuvaoYA+/70l9MHE+GYfYAycVyPSDYZ7mjOFuYBRqlEpTig==} peerDependencies: vite: '*' peerDependenciesMeta: vite: optional: true - dependencies: - debug: 4.3.6(supports-color@5.5.0) - globrex: 0.1.2 - tsconfck: 3.1.1(typescript@5.5.4) - vite: 5.4.2 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - /vite@5.4.2: + vite@5.4.2: resolution: {integrity: sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -7410,15 +4295,8 @@ packages: optional: true terser: optional: true - dependencies: - esbuild: 0.21.5 - postcss: 8.4.41 - rollup: 4.21.1 - optionalDependencies: - fsevents: 2.3.3 - dev: true - /vitest@2.0.5: + vitest@2.0.5: resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -7442,6 +4320,4403 @@ packages: optional: true jsdom: optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + wait-on@7.2.0: + resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} + engines: {node: '>=12.0.0'} + hasBin: true + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.0.0: + resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + engines: {node: '>=18'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + + which-builtin-type@1.1.4: + resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-pm@2.2.0: + resolution: {integrity: sha512-MOiaDbA5ZZgUjkeMWM5EkJp4loW5ZRoa5bc3/aeMox/PJelMhE6t7S/mLuiY43DBupyxH+S0U1bTui9kWUlmsw==} + engines: {node: '>=8.15'} + + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrap-ansi@9.0.0: + resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yaml@2.5.0: + resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==} + engines: {node: '>= 14'} + hasBin: true + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@adobe/css-tools@4.4.0': {} + + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.24.7': + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.1 + + '@babel/compat-data@7.25.4': {} + + '@babel/core@7.25.2': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.25.6 + '@babel/helper-compilation-targets': 7.25.2 + '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) + '@babel/helpers': 7.25.6 + '@babel/parser': 7.25.6 + '@babel/template': 7.25.0 + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 + convert-source-map: 2.0.0 + debug: 4.3.6(supports-color@5.5.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.25.6': + dependencies: + '@babel/types': 7.25.6 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + + '@babel/helper-compilation-targets@7.25.2': + dependencies: + '@babel/compat-data': 7.25.4 + '@babel/helper-validator-option': 7.24.8 + browserslist: 4.23.3 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.24.7': + dependencies: + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-simple-access': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + '@babel/traverse': 7.25.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.24.8': {} + + '@babel/helper-simple-access@7.24.7': + dependencies: + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.24.8': {} + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/helper-validator-option@7.24.8': {} + + '@babel/helpers@7.25.6': + dependencies: + '@babel/template': 7.25.0 + '@babel/types': 7.25.6 + + '@babel/highlight@7.24.7': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + + '@babel/parser@7.25.6': + dependencies: + '@babel/types': 7.25.6 + + '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/runtime@7.25.6': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.25.0': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 + + '@babel/traverse@7.25.6': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.25.6 + '@babel/parser': 7.25.6 + '@babel/template': 7.25.0 + '@babel/types': 7.25.6 + debug: 4.3.6(supports-color@5.5.0) + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.25.6': + dependencies: + '@babel/helper-string-parser': 7.24.8 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + + '@bufbuild/buf-darwin-arm64@1.39.0': + optional: true + + '@bufbuild/buf-darwin-x64@1.39.0': + optional: true + + '@bufbuild/buf-linux-aarch64@1.39.0': + optional: true + + '@bufbuild/buf-linux-x64@1.39.0': + optional: true + + '@bufbuild/buf-win32-arm64@1.39.0': + optional: true + + '@bufbuild/buf-win32-x64@1.39.0': + optional: true + + '@bufbuild/buf@1.39.0': + optionalDependencies: + '@bufbuild/buf-darwin-arm64': 1.39.0 + '@bufbuild/buf-darwin-x64': 1.39.0 + '@bufbuild/buf-linux-aarch64': 1.39.0 + '@bufbuild/buf-linux-x64': 1.39.0 + '@bufbuild/buf-win32-arm64': 1.39.0 + '@bufbuild/buf-win32-x64': 1.39.0 + + '@bufbuild/protobuf@2.0.0': {} + + '@changesets/apply-release-plan@7.0.4': + dependencies: + '@babel/runtime': 7.25.6 + '@changesets/config': 3.0.2 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.0 + '@changesets/should-skip-package': 0.1.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.6.3 + + '@changesets/assemble-release-plan@6.0.3': + dependencies: + '@babel/runtime': 7.25.6 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.1 + '@changesets/should-skip-package': 0.1.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.6.3 + + '@changesets/changelog-git@0.2.0': + dependencies: + '@changesets/types': 6.0.0 + + '@changesets/cli@2.27.7': + dependencies: + '@babel/runtime': 7.25.6 + '@changesets/apply-release-plan': 7.0.4 + '@changesets/assemble-release-plan': 6.0.3 + '@changesets/changelog-git': 0.2.0 + '@changesets/config': 3.0.2 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.1 + '@changesets/get-release-plan': 4.0.3 + '@changesets/git': 3.0.0 + '@changesets/logger': 0.1.0 + '@changesets/pre': 2.0.0 + '@changesets/read': 0.6.0 + '@changesets/should-skip-package': 0.1.0 + '@changesets/types': 6.0.0 + '@changesets/write': 0.3.1 + '@manypkg/get-packages': 1.1.3 + '@types/semver': 7.5.8 + ansi-colors: 4.1.3 + chalk: 2.4.2 + ci-info: 3.9.0 + enquirer: 2.4.1 + external-editor: 3.1.0 + fs-extra: 7.0.1 + human-id: 1.0.2 + mri: 1.2.0 + outdent: 0.5.0 + p-limit: 2.3.0 + preferred-pm: 3.1.4 + resolve-from: 5.0.0 + semver: 7.6.3 + spawndamnit: 2.0.0 + term-size: 2.2.1 + + '@changesets/config@3.0.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.1 + '@changesets/logger': 0.1.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.1': + dependencies: + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + chalk: 2.4.2 + fs-extra: 7.0.1 + semver: 7.6.3 + + '@changesets/get-release-plan@4.0.3': + dependencies: + '@babel/runtime': 7.25.6 + '@changesets/assemble-release-plan': 6.0.3 + '@changesets/config': 3.0.2 + '@changesets/pre': 2.0.0 + '@changesets/read': 0.6.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.0': + dependencies: + '@babel/runtime': 7.25.6 + '@changesets/errors': 0.2.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 2.0.0 + + '@changesets/logger@0.1.0': + dependencies: + chalk: 2.4.2 + + '@changesets/parse@0.4.0': + dependencies: + '@changesets/types': 6.0.0 + js-yaml: 3.14.1 + + '@changesets/pre@2.0.0': + dependencies: + '@babel/runtime': 7.25.6 + '@changesets/errors': 0.2.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.0': + dependencies: + '@babel/runtime': 7.25.6 + '@changesets/git': 3.0.0 + '@changesets/logger': 0.1.0 + '@changesets/parse': 0.4.0 + '@changesets/types': 6.0.0 + chalk: 2.4.2 + fs-extra: 7.0.1 + p-filter: 2.1.0 + + '@changesets/should-skip-package@0.1.0': + dependencies: + '@babel/runtime': 7.25.6 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.0.0': {} + + '@changesets/write@0.3.1': + dependencies: + '@babel/runtime': 7.25.6 + '@changesets/types': 6.0.0 + fs-extra: 7.0.1 + human-id: 1.0.2 + prettier: 2.8.8 + + '@colors/colors@1.5.0': + optional: true + + '@connectrpc/connect-node@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1)': + dependencies: + '@bufbuild/protobuf': 2.0.0 + '@connectrpc/connect': 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0) + undici: 5.28.4 + + '@connectrpc/connect-web@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1)': + dependencies: + '@bufbuild/protobuf': 2.0.0 + '@connectrpc/connect': 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0) + + '@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)': + dependencies: + '@bufbuild/protobuf': 2.0.0 + + '@cypress/request@3.0.1': + dependencies: + aws-sign2: 0.7.0 + aws4: 1.13.2 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + http-signature: 1.3.6 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + performance-now: 2.1.0 + qs: 6.10.4 + safe-buffer: 5.2.1 + tough-cookie: 4.1.4 + tunnel-agent: 0.6.0 + uuid: 8.3.2 + + '@cypress/xvfb@1.2.4(supports-color@8.1.1)': + dependencies: + debug: 3.2.7(supports-color@8.1.1) + lodash.once: 4.1.1 + transitivePeerDependencies: + - supports-color + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.23.1': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.23.1': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.23.1': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.23.1': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.23.1': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.23.1': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + dependencies: + eslint: 8.57.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.11.0': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.3.6(supports-color@5.5.0) + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.0': {} + + '@fastify/busboy@2.1.1': {} + + '@grpc/grpc-js@1.11.1': + dependencies: + '@grpc/proto-loader': 0.7.13 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.7.13': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.2.3 + protobufjs: 7.4.0 + yargs: 17.7.2 + + '@hapi/hoek@9.3.0': {} + + '@hapi/topo@5.1.0': + dependencies: + '@hapi/hoek': 9.3.0 + + '@headlessui/react@1.7.19(react-dom@18.3.1)(react@18.3.1)': + dependencies: + '@tanstack/react-virtual': 3.10.6(react-dom@18.3.1)(react@18.3.1) + client-only: 0.0.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@heroicons/react@2.1.3(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@humanwhocodes/config-array@0.11.14': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.3.6(supports-color@5.5.0) + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@js-sdsl/ordered-map@4.4.2': {} + + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.25.6 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.25.6 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + + '@mapbox/node-pre-gyp@1.0.11': + dependencies: + detect-libc: 2.0.3 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.7.0 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.6.3 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + + '@next/env@14.2.5': {} + + '@next/eslint-plugin-next@14.2.7': + dependencies: + glob: 10.3.10 + + '@next/swc-darwin-arm64@14.2.5': + optional: true + + '@next/swc-darwin-x64@14.2.5': + optional: true + + '@next/swc-linux-arm64-gnu@14.2.5': + optional: true + + '@next/swc-linux-arm64-musl@14.2.5': + optional: true + + '@next/swc-linux-x64-gnu@14.2.5': + optional: true + + '@next/swc-linux-x64-musl@14.2.5': + optional: true + + '@next/swc-win32-arm64-msvc@14.2.5': + optional: true + + '@next/swc-win32-ia32-msvc@14.2.5': + optional: true + + '@next/swc-win32-x64-msvc@14.2.5': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@nolyfill/is-core-module@1.0.39': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + + '@rollup/rollup-android-arm-eabi@4.21.1': + optional: true + + '@rollup/rollup-android-arm64@4.21.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.21.1': + optional: true + + '@rollup/rollup-darwin-x64@4.21.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.21.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.21.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.21.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.21.1': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.21.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.21.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.21.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.21.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.21.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.21.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.21.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.21.1': + optional: true + + '@rushstack/eslint-patch@1.10.4': {} + + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + + '@sideway/formula@3.0.1': {} + + '@sideway/pinpoint@2.0.0': {} + + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.5': + dependencies: + '@swc/counter': 0.1.3 + tslib: 2.7.0 + + '@tailwindcss/forms@0.5.3(tailwindcss@3.4.9)': + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 3.4.9 + + '@tailwindcss/forms@0.5.7(tailwindcss@3.4.9)': + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 3.4.9 + + '@tanstack/react-virtual@3.10.6(react-dom@18.3.1)(react@18.3.1)': + dependencies: + '@tanstack/virtual-core': 3.10.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/virtual-core@3.10.6': {} + + '@testing-library/dom@10.4.0': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/runtime': 7.25.6 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.5.0': + dependencies: + '@adobe/css-tools': 4.4.0 + aria-query: 5.3.0 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + lodash: 4.17.21 + redent: 3.0.0 + + '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.25.6 + '@testing-library/dom': 10.4.0 + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.25.6 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.25.6 + + '@types/estree@1.0.5': {} + + '@types/json5@0.0.29': {} + + '@types/minimist@1.2.5': {} + + '@types/ms@0.7.34': {} + + '@types/node@12.20.55': {} + + '@types/node@22.1.0': + dependencies: + undici-types: 6.13.0 + + '@types/normalize-package-data@2.4.4': {} + + '@types/prop-types@15.7.12': {} + + '@types/react-dom@18.3.0': + dependencies: + '@types/react': 18.3.3 + + '@types/react@18.3.3': + dependencies: + '@types/prop-types': 15.7.12 + csstype: 3.1.3 + + '@types/semver@7.5.8': {} + + '@types/sinonjs__fake-timers@8.1.1': {} + + '@types/sizzle@2.3.8': {} + + '@types/tinycolor2@1.4.3': {} + + '@types/uuid@10.0.0': {} + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 22.1.0 + optional: true + + '@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4)': + dependencies: + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.3.6(supports-color@5.5.0) + eslint: 8.57.0 + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + + '@typescript-eslint/types@7.18.0': {} + + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.5.4)': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.3.6(supports-color@5.5.0) + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.5.4) + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.2.0': {} + + '@vercel/analytics@1.3.1(next@14.2.5)(react@18.3.1)': + dependencies: + next: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8) + react: 18.3.1 + server-only: 0.0.1 + + '@vercel/git-hooks@1.0.0': {} + + '@vitejs/plugin-react@4.3.1(vite@5.4.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.4.2 + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.0.5': + dependencies: + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.1.1 + tinyrainbow: 1.2.0 + + '@vitest/pretty-format@2.0.5': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.0.5': + dependencies: + '@vitest/utils': 2.0.5 + pathe: 1.1.2 + + '@vitest/snapshot@2.0.5': + dependencies: + '@vitest/pretty-format': 2.0.5 + magic-string: 0.30.11 + pathe: 1.1.2 + + '@vitest/spy@2.0.5': + dependencies: + tinyspy: 3.0.0 + + '@vitest/utils@2.0.5': + dependencies: + '@vitest/pretty-format': 2.0.5 + estree-walker: 3.0.3 + loupe: 3.1.1 + tinyrainbow: 1.2.0 + + abbrev@1.1.1: {} + + abort-controller-x@0.4.3: {} + + acorn-jsx@5.3.2(acorn@8.12.1): + dependencies: + acorn: 8.12.1 + + acorn@8.12.1: {} + + agent-base@6.0.2: + dependencies: + debug: 4.3.6(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + agent-base@7.1.1: + dependencies: + debug: 4.3.6(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + aggregate-error@4.0.1: + dependencies: + clean-stack: 4.2.0 + indent-string: 5.0.0 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-colors@4.1.3: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-escapes@7.0.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + aproba@2.0.0: {} + + arch@2.2.0: {} + + are-we-there-yet@2.0.0: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + + arg@5.0.2: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + aria-query@5.1.3: + dependencies: + deep-equal: 2.2.3 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + array-buffer-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.4 + + array-includes@3.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + is-string: 1.0.7 + + array-union@2.1.0: {} + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + + array.prototype.findlastindex@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + + array.prototype.flat@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.flatmap@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-shim-unscopables: 1.0.2 + + arraybuffer.prototype.slice@1.0.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 + + arrify@1.0.1: {} + + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + assert-plus@1.0.0: {} + + assertion-error@2.0.1: {} + + ast-types-flow@0.0.8: {} + + astral-regex@2.0.0: {} + + async@3.2.6: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + autoprefixer@10.4.20(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + caniuse-lite: 1.0.30001654 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.0.1 + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.0.0 + + aws-sign2@0.7.0: {} + + aws4@1.13.2: {} + + axe-core@4.10.0: {} + + axios@1.7.5(debug@4.3.6): + dependencies: + follow-redirects: 1.15.6(debug@4.3.6) + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + axobject-query@3.1.1: + dependencies: + deep-equal: 2.2.3 + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + + binary-extensions@2.3.0: {} + + blob-util@2.0.2: {} + + bluebird@3.7.2: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.23.3: + dependencies: + caniuse-lite: 1.0.30001654 + electron-to-chromium: 1.5.13 + node-releases: 2.0.18 + update-browserslist-db: 1.1.0(browserslist@4.23.3) + + buffer-crc32@0.2.13: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bundle-require@5.0.0(esbuild@0.23.1): + dependencies: + esbuild: 0.23.1 + load-tsconfig: 0.2.5 + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + cac@6.7.14: {} + + cachedir@2.4.0: {} + + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + camelcase-keys@7.0.2: + dependencies: + camelcase: 6.3.0 + map-obj: 4.3.0 + quick-lru: 5.1.1 + type-fest: 1.4.0 + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001654: {} + + case-anything@2.1.13: {} + + caseless@0.12.0: {} + + chai@5.1.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.1 + pathval: 2.0.0 + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.3.0: {} + + chardet@0.7.0: {} + + check-error@2.1.1: {} + + check-more-types@2.24.0: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chownr@2.0.0: {} + + ci-info@3.9.0: {} + + clean-stack@2.2.0: {} + + clean-stack@4.2.0: + dependencies: + escape-string-regexp: 5.0.0 + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + + cli-truncate@2.1.0: + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + client-only@0.0.1: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@1.2.1: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-support@1.1.3: {} + + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@12.1.0: {} + + commander@4.1.1: {} + + commander@6.2.1: {} + + common-tags@1.8.2: {} + + concat-map@0.0.1: {} + + concurrently@8.2.2: + dependencies: + chalk: 4.1.2 + date-fns: 2.30.0 + lodash: 4.17.21 + rxjs: 7.8.1 + shell-quote: 1.8.1 + spawn-command: 0.0.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + + consola@3.2.3: {} + + console-control-strings@1.1.0: {} + + convert-source-map@2.0.0: {} + + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + + core-util-is@1.0.2: {} + + cross-spawn@5.1.0: + dependencies: + lru-cache: 4.1.5 + shebang-command: 1.2.0 + which: 1.3.1 + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css.escape@1.5.1: {} + + cssesc@3.0.0: {} + + cssstyle@4.0.1: + dependencies: + rrweb-cssom: 0.6.0 + + csstype@3.1.3: {} + + cypress@13.14.1: + dependencies: + '@cypress/request': 3.0.1 + '@cypress/xvfb': 1.2.4(supports-color@8.1.1) + '@types/sinonjs__fake-timers': 8.1.1 + '@types/sizzle': 2.3.8 + arch: 2.2.0 + blob-util: 2.0.2 + bluebird: 3.7.2 + buffer: 5.7.1 + cachedir: 2.4.0 + chalk: 4.1.2 + check-more-types: 2.24.0 + cli-cursor: 3.1.0 + cli-table3: 0.6.5 + commander: 6.2.1 + common-tags: 1.8.2 + dayjs: 1.11.13 + debug: 4.3.6(supports-color@8.1.1) + enquirer: 2.4.1 + eventemitter2: 6.4.7 + execa: 4.1.0 + executable: 4.1.1 + extract-zip: 2.0.1(supports-color@8.1.1) + figures: 3.2.0 + fs-extra: 9.1.0 + getos: 3.2.1 + is-ci: 3.0.1 + is-installed-globally: 0.4.0 + lazy-ass: 1.6.0 + listr2: 3.14.0(enquirer@2.4.1) + lodash: 4.17.21 + log-symbols: 4.1.0 + minimist: 1.2.8 + ospath: 1.2.2 + pretty-bytes: 5.6.0 + process: 0.11.10 + proxy-from-env: 1.0.0 + request-progress: 3.0.0 + semver: 7.6.3 + supports-color: 8.1.1 + tmp: 0.2.3 + untildify: 4.0.0 + yauzl: 2.10.0 + + damerau-levenshtein@1.0.8: {} + + dashdash@1.14.1: + dependencies: + assert-plus: 1.0.0 + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.25.6 + + dayjs@1.11.13: {} + + debug@3.2.7(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + supports-color: 8.1.1 + + debug@4.3.6(supports-color@5.5.0): + dependencies: + ms: 2.1.2 + supports-color: 5.5.0 + + debug@4.3.6(supports-color@8.1.1): + dependencies: + ms: 2.1.2 + supports-color: 8.1.1 + + decamelize-keys@1.1.1: + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + + decamelize@1.2.0: {} + + decamelize@5.0.1: {} + + decimal.js@10.4.3: {} + + deep-eql@5.0.2: {} + + deep-equal@2.2.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + es-get-iterator: 1.1.3 + get-intrinsic: 1.2.4 + is-arguments: 1.1.1 + is-array-buffer: 3.0.4 + is-date-object: 1.0.5 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + isarray: 2.0.5 + object-is: 1.1.6 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + side-channel: 1.0.6 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.2 + which-typed-array: 1.1.15 + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + del-cli@5.1.0: + dependencies: + del: 7.1.0 + meow: 10.1.5 + + del@7.1.0: + dependencies: + globby: 13.2.2 + graceful-fs: 4.2.11 + is-glob: 4.0.3 + is-path-cwd: 3.0.0 + is-path-inside: 4.0.0 + p-map: 5.5.0 + rimraf: 3.0.2 + slash: 4.0.0 + + delayed-stream@1.0.0: {} + + delegates@1.0.0: {} + + dequal@2.0.3: {} + + detect-indent@6.1.0: {} + + detect-libc@1.0.3: {} + + detect-libc@2.0.3: {} + + didyoumean@1.2.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dlv@1.1.3: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dotenv@16.0.3: {} + + dprint-node@1.0.8: + dependencies: + detect-libc: 1.0.3 + + duplexer@0.1.2: {} + + eastasianwidth@0.2.0: {} + + ecc-jsbn@0.1.2: + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + + electron-to-chromium@1.5.13: {} + + emoji-regex@10.4.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + + enhanced-resolve@5.17.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + entities@4.5.0: {} + + env-cmd@10.1.0: + dependencies: + commander: 4.1.1 + cross-spawn: 7.0.3 + + environment@1.1.0: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.23.3: + dependencies: + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-set-tostringtag: 2.0.3 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.2 + globalthis: 1.0.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 + is-callable: 1.2.7 + is-data-view: 1.0.1 + is-negative-zero: 2.0.3 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + is-string: 1.0.7 + is-typed-array: 1.1.13 + is-weakref: 1.0.2 + object-inspect: 1.13.2 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + safe-array-concat: 1.1.2 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.6 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.15 + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + + es-errors@1.3.0: {} + + es-get-iterator@1.1.3: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + is-arguments: 1.1.1 + is-map: 2.0.3 + is-set: 2.0.3 + is-string: 1.0.7 + isarray: 2.0.5 + stop-iteration-iterator: 1.0.0 + + es-iterator-helpers@1.0.19: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-set-tostringtag: 2.0.3 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + globalthis: 1.0.4 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + iterator.prototype: 1.1.2 + safe-array-concat: 1.1.2 + + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.0.3: + dependencies: + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.0.2: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.2.1: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-config-next@14.2.7(eslint@8.57.0)(typescript@5.5.4): + dependencies: + '@next/eslint-plugin-next': 14.2.7 + '@rushstack/eslint-patch': 1.10.4 + '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) + eslint-plugin-react: 7.35.0(eslint@8.57.0) + eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) + typescript: 5.5.4 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-config-prettier@9.1.0(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + + eslint-config-turbo@2.1.0(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + eslint-plugin-turbo: 2.1.0(eslint@8.57.0) + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7(supports-color@8.1.1) + is-core-module: 2.15.1 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.3.6(supports-color@5.5.0) + enhanced-resolve: 5.17.1 + eslint: 8.57.0 + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + fast-glob: 3.3.2 + get-tsconfig: 4.8.0 + is-bun-module: 1.1.0 + is-glob: 4.0.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + + eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): + dependencies: + '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) + debug: 3.2.7(supports-color@8.1.1) + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): + dependencies: + '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7(supports-color@8.1.1) + doctrine: 2.1.0 + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + hasown: 2.0.2 + is-core-module: 2.15.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.0): + dependencies: + aria-query: 5.1.3 + array-includes: 3.1.8 + array.prototype.flatmap: 1.3.2 + ast-types-flow: 0.0.8 + axe-core: 4.10.0 + axobject-query: 3.1.1 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + es-iterator-helpers: 1.0.19 + eslint: 8.57.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.0.3 + string.prototype.includes: 2.0.0 + + eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + + eslint-plugin-react@7.35.0(eslint@8.57.0): + dependencies: + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.2 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.0.19 + eslint: 8.57.0 + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.8 + object.fromentries: 2.0.8 + object.values: 1.2.0 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.11 + string.prototype.repeat: 1.0.0 + + eslint-plugin-turbo@2.1.0(eslint@8.57.0): + dependencies: + dotenv: 16.0.3 + eslint: 8.57.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.0: + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/regexpp': 4.11.0 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.0 + '@humanwhocodes/config-array': 0.11.14 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.6(supports-color@5.5.0) + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) + eslint-visitor-keys: 3.4.3 + + esprima@4.0.1: {} + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.5 + + esutils@2.0.3: {} + + event-stream@3.3.4: + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + + eventemitter2@6.4.7: {} + + eventemitter3@5.0.1: {} + + execa@4.1.0: + dependencies: + cross-spawn: 7.0.3 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + executable@4.1.1: + dependencies: + pify: 2.3.0 + + extend@3.0.2: {} + + extendable-error@0.1.7: {} + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + extract-zip@2.0.1(supports-color@8.1.1): + dependencies: + debug: 4.3.6(supports-color@8.1.1) + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + extsprintf@1.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + find-yarn-workspace-root2@1.2.16: + dependencies: + micromatch: 4.0.8 + pkg-dir: 4.2.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.1: {} + + follow-redirects@1.15.6(debug@4.3.6): + dependencies: + debug: 4.3.6(supports-color@5.5.0) + + for-each@0.3.3: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + forever-agent@0.6.1: {} + + form-data@2.3.3: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + form-data@4.0.0: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + fraction.js@4.3.7: {} + + from@0.1.7: {} + + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + functions-have-names: 1.2.3 + + functions-have-names@1.2.3: {} + + gauge@3.0.2: + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.2.0: {} + + get-func-name@2.0.2: {} + + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + + get-stream@5.2.0: + dependencies: + pump: 3.0.0 + + get-stream@6.0.1: {} + + get-stream@8.0.1: {} + + get-symbol-description@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + + get-tsconfig@4.8.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + getos@3.2.1: + dependencies: + async: 3.2.6 + + getpass@0.1.7: + dependencies: + assert-plus: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.3.10: + dependencies: + foreground-child: 3.3.0 + jackspeak: 2.3.6 + minimatch: 9.0.5 + minipass: 7.1.2 + path-scurry: 1.11.1 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + global-dirs@3.0.1: + dependencies: + ini: 2.0.0 + + globals@11.12.0: {} + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.0.1 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + globby@13.2.2: + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 4.0.0 + + globrex@0.1.2: {} + + gopd@1.0.1: + dependencies: + get-intrinsic: 1.2.4 + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + grpc-tools@1.12.4: + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + transitivePeerDependencies: + - encoding + - supports-color + + hard-rejection@2.1.0: {} + + has-bigints@1.0.2: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.0.3: {} + + has-symbols@1.0.3: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.0.3 + + has-unicode@2.0.1: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.1 + debug: 4.3.6(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + http-signature@1.3.6: + dependencies: + assert-plus: 1.0.0 + jsprim: 2.0.2 + sshpk: 1.18.0 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.3.6(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.5: + dependencies: + agent-base: 7.1.1 + debug: 4.3.6(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + human-id@1.0.2: {} + + human-signals@1.1.1: {} + + human-signals@2.1.0: {} + + human-signals@5.0.0: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore-by-default@1.0.1: {} + + ignore@5.3.2: {} + + immutable@4.3.7: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + indent-string@5.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@2.0.0: {} + + internal-slot@1.0.7: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.0.6 + + is-arguments@1.1.1: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-array-buffer@3.0.4: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-arrayish@0.2.1: {} + + is-async-function@2.0.0: + dependencies: + has-tostringtag: 1.0.2 + + is-bigint@1.0.4: + dependencies: + has-bigints: 1.0.2 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.1.2: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-bun-module@1.1.0: + dependencies: + semver: 7.6.3 + + is-callable@1.2.7: {} + + is-ci@3.0.1: + dependencies: + ci-info: 3.9.0 + + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.1: + dependencies: + is-typed-array: 1.1.13 + + is-date-object@1.0.5: + dependencies: + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.0.0: + dependencies: + get-east-asian-width: 1.2.0 + + is-generator-function@1.0.10: + dependencies: + has-tostringtag: 1.0.2 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-installed-globally@0.4.0: + dependencies: + global-dirs: 3.0.1 + is-path-inside: 3.0.3 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-path-cwd@3.0.0: {} + + is-path-inside@3.0.3: {} + + is-path-inside@4.0.0: {} + + is-plain-obj@1.1.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-regex@1.1.4: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.3: + dependencies: + call-bind: 1.0.7 + + is-stream@2.0.1: {} + + is-stream@3.0.0: {} + + is-string@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + + is-symbol@1.0.4: + dependencies: + has-symbols: 1.0.3 + + is-typed-array@1.1.13: + dependencies: + which-typed-array: 1.1.15 + + is-typedarray@1.0.0: {} + + is-unicode-supported@0.1.0: {} + + is-weakmap@2.0.2: {} + + is-weakref@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-weakset@2.0.3: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-windows@1.0.2: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isstream@0.1.2: {} + + iterator.prototype@1.1.2: + dependencies: + define-properties: 1.2.1 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + reflect.getprototypeof: 1.0.6 + set-function-name: 2.0.2 + + jackspeak@2.3.6: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.6: {} + + joi@17.13.3: + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + + jose@5.8.0: {} + + joycon@3.1.1: {} + + js-tokens@4.0.0: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsbn@0.1.1: {} + + jsdom@25.0.0: + dependencies: + cssstyle: 4.0.1 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.5 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.12 + parse5: 7.1.2 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@2.5.2: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema@0.4.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json-stringify-safe@5.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsprim@2.0.2: + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.8 + array.prototype.flat: 1.3.2 + object.assign: 4.1.5 + object.values: 1.2.0 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kind-of@6.0.3: {} + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + lazy-ass@1.6.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@2.1.0: {} + + lilconfig@3.1.2: {} + + lines-and-columns@1.2.4: {} + + lint-staged@15.2.8: + dependencies: + chalk: 5.3.0 + commander: 12.1.0 + debug: 4.3.6(supports-color@5.5.0) + execa: 8.0.1 + lilconfig: 3.1.2 + listr2: 8.2.4 + micromatch: 4.0.8 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.5.0 + transitivePeerDependencies: + - supports-color + + listr2@3.14.0(enquirer@2.4.1): + dependencies: + cli-truncate: 2.1.0 + colorette: 2.0.20 + enquirer: 2.4.1 + log-update: 4.0.0 + p-map: 4.0.0 + rfdc: 1.4.1 + rxjs: 7.8.1 + through: 2.3.8 + wrap-ansi: 7.0.0 + + listr2@8.2.4: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.0 + + load-tsconfig@0.2.5: {} + + load-yaml-file@0.2.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.camelcase@4.3.0: {} + + lodash.merge@4.6.2: {} + + lodash.once@4.1.1: {} + + lodash.sortby@4.7.0: {} + + lodash.startcase@4.4.0: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + log-update@4.0.0: + dependencies: + ansi-escapes: 4.3.2 + cli-cursor: 3.1.0 + slice-ansi: 4.0.0 + wrap-ansi: 6.2.0 + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.0.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + + long@5.2.3: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + loupe@3.1.1: + dependencies: + get-func-name: 2.0.2 + + lru-cache@10.4.3: {} + + lru-cache@4.1.5: + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lz-string@1.5.0: {} + + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + make-dir-cli@4.0.0: + dependencies: + make-dir: 5.0.0 + meow: 13.2.0 + + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + + make-dir@5.0.0: {} + + map-obj@1.0.1: {} + + map-obj@4.3.0: {} + + map-stream@0.1.0: {} + + meow@10.1.5: + dependencies: + '@types/minimist': 1.2.5 + camelcase-keys: 7.0.2 + decamelize: 5.0.1 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 8.0.0 + redent: 4.0.0 + trim-newlines: 4.1.1 + type-fest: 1.4.0 + yargs-parser: 20.2.9 + + meow@13.2.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + mimic-fn@4.0.0: {} + + mimic-function@5.0.1: {} + + min-indent@1.0.1: {} + + mini-svg-data-uri@1.4.4: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minimist-options@4.1.0: + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + + minimist@1.2.8: {} + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.2: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp@1.0.4: {} + + moment@2.30.1: {} + + mri@1.2.0: {} + + ms@2.1.2: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.7: {} + + natural-compare@1.4.0: {} + + next-themes@0.2.1(next@14.2.5)(react-dom@18.3.1)(react@18.3.1): + dependencies: + next: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8): + dependencies: + '@next/env': 14.2.5 + '@swc/helpers': 0.5.5 + busboy: 1.6.0 + caniuse-lite: 1.0.30001654 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + sass: 1.77.8 + styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 14.2.5 + '@next/swc-darwin-x64': 14.2.5 + '@next/swc-linux-arm64-gnu': 14.2.5 + '@next/swc-linux-arm64-musl': 14.2.5 + '@next/swc-linux-x64-gnu': 14.2.5 + '@next/swc-linux-x64-musl': 14.2.5 + '@next/swc-win32-arm64-msvc': 14.2.5 + '@next/swc-win32-ia32-msvc': 14.2.5 + '@next/swc-win32-x64-msvc': 14.2.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + nice-grpc-common@2.0.2: + dependencies: + ts-error: 1.0.6 + + nice-grpc@2.0.1: + dependencies: + '@grpc/grpc-js': 1.11.1 + abort-controller-x: 0.4.3 + nice-grpc-common: 2.0.2 + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-releases@2.0.18: {} + + nodemon@3.1.4: + dependencies: + chokidar: 3.6.0 + debug: 4.3.6(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 3.1.2 + pstree.remy: 1.1.8 + semver: 7.6.3 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.1 + undefsafe: 2.0.5 + + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + + normalize-package-data@3.0.3: + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.15.1 + semver: 7.6.3 + validate-npm-package-license: 3.0.4 + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + npmlog@5.0.1: + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + + nwsapi@2.2.12: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.2: {} + + object-is@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + + object-keys@1.1.1: {} + + object.assign@4.1.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + + object.entries@1.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + + object.values@1.2.0: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + os-tmpdir@1.0.2: {} + + ospath@1.2.2: {} + + outdent@0.5.0: {} + + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@2.1.0: {} + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + + p-map@5.5.0: + dependencies: + aggregate-error: 4.0.1 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.24.7 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse5@7.1.2: + dependencies: + entities: 4.5.0 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-type@4.0.0: {} + + pathe@1.1.2: {} + + pathval@2.0.0: {} + + pause-stream@0.0.11: + dependencies: + through: 2.3.8 + + pend@1.2.0: {} + + performance-now@2.1.0: {} + + picocolors@1.0.1: {} + + picomatch@2.3.1: {} + + pidtree@0.6.0: {} + + pify@2.3.0: {} + + pify@4.0.1: {} + + pirates@4.0.6: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + possible-typed-array-names@1.0.0: {} + + postcss-import@15.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.8 + + postcss-js@4.0.1(postcss@8.4.41): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.41 + + postcss-load-config@4.0.2(postcss@8.4.41): + dependencies: + lilconfig: 3.1.2 + postcss: 8.4.41 + yaml: 2.5.0 + + postcss-load-config@6.0.1: + dependencies: + lilconfig: 3.1.2 + + postcss-nested@6.2.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + + postcss@8.4.41: + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + + preferred-pm@3.1.4: + dependencies: + find-up: 5.0.0 + find-yarn-workspace-root2: 1.2.16 + path-exists: 4.0.0 + which-pm: 2.2.0 + + prelude-ls@1.2.1: {} + + prettier-plugin-organize-imports@4.0.0(prettier@3.3.3)(typescript@5.5.4): + dependencies: + prettier: 3.3.3 + typescript: 5.5.4 + + prettier-plugin-tailwindcss@0.6.5(prettier-plugin-organize-imports@4.0.0)(prettier@3.3.3): + dependencies: + prettier: 3.3.3 + prettier-plugin-organize-imports: 4.0.0(prettier@3.3.3)(typescript@5.5.4) + + prettier@2.8.8: {} + + prettier@3.3.3: {} + + pretty-bytes@5.6.0: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + process@0.11.10: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + protobufjs@7.4.0: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 22.1.0 + long: 5.2.3 + + proxy-from-env@1.0.0: {} + + proxy-from-env@1.1.0: {} + + ps-tree@1.2.0: + dependencies: + event-stream: 3.3.4 + + pseudomap@1.0.2: {} + + psl@1.9.0: {} + + pstree.remy@1.1.8: {} + + pump@3.0.0: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + punycode@2.3.1: {} + + qrcode.react@3.1.0(react@18.3.1): + dependencies: + react: 18.3.1 + + qs@6.10.4: + dependencies: + side-channel: 1.0.6 + + querystringify@2.2.0: {} + + queue-microtask@1.2.3: {} + + quick-lru@5.1.1: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-hook-form@7.39.5(react@18.3.1): + dependencies: + react: 18.3.1 + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-refresh@0.14.2: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + read-pkg-up@8.0.0: + dependencies: + find-up: 5.0.0 + read-pkg: 6.0.0 + type-fest: 1.4.0 + + read-pkg@6.0.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 3.0.3 + parse-json: 5.2.0 + type-fest: 1.4.0 + + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + redent@4.0.0: + dependencies: + indent-string: 5.0.0 + strip-indent: 4.0.0 + + reflect.getprototypeof@1.0.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + globalthis: 1.0.4 + which-builtin-type: 1.1.4 + + regenerator-runtime@0.14.1: {} + + regexp.prototype.flags@1.5.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 + + request-progress@3.0.0: + dependencies: + throttleit: 1.0.1 + + require-directory@2.1.1: {} + + requires-port@1.0.0: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.8: + dependencies: + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + reusify@1.0.4: {} + + rfdc@1.4.1: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.21.1: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.21.1 + '@rollup/rollup-android-arm64': 4.21.1 + '@rollup/rollup-darwin-arm64': 4.21.1 + '@rollup/rollup-darwin-x64': 4.21.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.21.1 + '@rollup/rollup-linux-arm-musleabihf': 4.21.1 + '@rollup/rollup-linux-arm64-gnu': 4.21.1 + '@rollup/rollup-linux-arm64-musl': 4.21.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.21.1 + '@rollup/rollup-linux-riscv64-gnu': 4.21.1 + '@rollup/rollup-linux-s390x-gnu': 4.21.1 + '@rollup/rollup-linux-x64-gnu': 4.21.1 + '@rollup/rollup-linux-x64-musl': 4.21.1 + '@rollup/rollup-win32-arm64-msvc': 4.21.1 + '@rollup/rollup-win32-ia32-msvc': 4.21.1 + '@rollup/rollup-win32-x64-msvc': 4.21.1 + fsevents: 2.3.3 + + rrweb-cssom@0.6.0: {} + + rrweb-cssom@0.7.1: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.8.1: + dependencies: + tslib: 2.7.0 + + safe-array-concat@1.1.2: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + isarray: 2.0.5 + + safe-buffer@5.2.1: {} + + safe-regex-test@1.0.3: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-regex: 1.1.4 + + safer-buffer@2.1.2: {} + + sass@1.77.8: + dependencies: + chokidar: 3.6.0 + immutable: 4.3.7 + source-map-js: 1.2.0 + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + semver@7.6.3: {} + + server-only@0.0.1: {} + + set-blocking@2.0.0: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@1.0.0: {} + + shebang-regex@3.0.0: {} + + shell-quote@1.8.1: {} + + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.2 + + siginfo@2.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-update-notifier@2.0.0: + dependencies: + semver: 7.6.3 + + slash@3.0.0: {} + + slash@4.0.0: {} + + slice-ansi@3.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.0.0 + + source-map-js@1.2.0: {} + + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + + spawn-command@0.0.2: {} + + spawndamnit@2.0.0: + dependencies: + cross-spawn: 5.1.0 + signal-exit: 3.0.7 + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.20 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.20 + + spdx-license-ids@3.0.20: {} + + split@0.3.3: + dependencies: + through: 2.3.8 + + sprintf-js@1.0.3: {} + + sshpk@1.18.0: + dependencies: + asn1: 0.2.6 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + + stackback@0.0.2: {} + + start-server-and-test@2.0.5: + dependencies: + arg: 5.0.2 + bluebird: 3.7.2 + check-more-types: 2.24.0 + debug: 4.3.6(supports-color@5.5.0) + execa: 5.1.1 + lazy-ass: 1.6.0 + ps-tree: 1.2.0 + wait-on: 7.2.0(debug@4.3.6) + transitivePeerDependencies: + - supports-color + + std-env@3.7.0: {} + + stop-iteration-iterator@1.0.0: + dependencies: + internal-slot: 1.0.7 + + stream-combiner@0.0.4: + dependencies: + duplexer: 0.1.2 + + streamsearch@1.1.0: {} + + string-argv@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.2.0 + strip-ansi: 7.1.0 + + string.prototype.includes@2.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.23.3 + + string.prototype.matchall@4.0.11: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + regexp.prototype.flags: 1.5.2 + set-function-name: 2.0.2 + side-channel: 1.0.6 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.23.3 + + string.prototype.trim@1.2.9: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + string.prototype.trimend@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + strip-bom@3.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-final-newline@3.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-indent@4.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + styled-jsx@5.1.1(@babel/core@7.25.2)(react@18.3.1): + dependencies: + '@babel/core': 7.25.2 + client-only: 0.0.1 + react: 18.3.1 + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + swr@2.2.5(react@18.3.1): + dependencies: + client-only: 0.0.1 + react: 18.3.1 + use-sync-external-store: 1.2.2(react@18.3.1) + + symbol-tree@3.2.4: {} + + tailwindcss@3.4.9: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.2 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.6 + lilconfig: 2.1.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.1 + postcss: 8.4.41 + postcss-import: 15.1.0(postcss@8.4.41) + postcss-js: 4.0.1(postcss@8.4.41) + postcss-load-config: 4.0.2(postcss@8.4.41) + postcss-nested: 6.2.0(postcss@8.4.41) + postcss-selector-parser: 6.1.2 + resolve: 1.22.8 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + + tapable@2.2.1: {} + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + term-size@2.2.1: {} + + text-table@0.2.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + throttleit@1.0.1: {} + + through@2.3.8: {} + + tinybench@2.9.0: {} + + tinycolor2@1.4.2: {} + + tinypool@1.0.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.0: {} + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + tmp@0.2.3: {} + + to-fast-properties@2.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toggle-selection@1.0.6: {} + + touch@3.1.1: {} + + tough-cookie@4.1.4: + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tr46@0.0.3: {} + + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + + tr46@5.0.0: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + trim-newlines@4.1.1: {} + + ts-api-utils@1.3.0(typescript@5.5.4): + dependencies: + typescript: 5.5.4 + + ts-error@1.0.6: {} + + ts-interface-checker@0.1.13: {} + + ts-poet@6.9.0: + dependencies: + dprint-node: 1.0.8 + + ts-proto-descriptors@1.16.0: + dependencies: + long: 5.2.3 + protobufjs: 7.4.0 + + ts-proto@1.181.2: + dependencies: + case-anything: 2.1.13 + protobufjs: 7.4.0 + ts-poet: 6.9.0 + ts-proto-descriptors: 1.16.0 + + tsconfck@3.1.1(typescript@5.5.4): + dependencies: + typescript: 5.5.4 + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.7.0: {} + + tsup@8.2.4(typescript@5.5.4): + dependencies: + bundle-require: 5.0.0(esbuild@0.23.1) + cac: 6.7.14 + chokidar: 3.6.0 + consola: 3.2.3 + debug: 4.3.6(supports-color@5.5.0) + esbuild: 0.23.1 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + picocolors: 1.0.1 + postcss-load-config: 6.0.1 + resolve-from: 5.0.0 + rollup: 4.21.1 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tree-kill: 1.2.2 + typescript: 5.5.4 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + turbo-darwin-64@2.0.12: + optional: true + + turbo-darwin-arm64@2.0.12: + optional: true + + turbo-linux-64@2.0.12: + optional: true + + turbo-linux-arm64@2.0.12: + optional: true + + turbo-windows-64@2.0.12: + optional: true + + turbo-windows-arm64@2.0.12: + optional: true + + turbo@2.0.12: + optionalDependencies: + turbo-darwin-64: 2.0.12 + turbo-darwin-arm64: 2.0.12 + turbo-linux-64: 2.0.12 + turbo-linux-arm64: 2.0.12 + turbo-windows-64: 2.0.12 + turbo-windows-arm64: 2.0.12 + + tweetnacl@0.14.5: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@1.4.0: {} + + typed-array-buffer@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 + + typed-array-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-byte-offset@1.0.2: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-length@1.0.6: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 + + typescript@5.5.4: {} + + unbox-primitive@1.0.2: + dependencies: + call-bind: 1.0.7 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + + undefsafe@2.0.5: {} + + undici-types@6.13.0: {} + + undici@5.28.4: + dependencies: + '@fastify/busboy': 2.1.1 + + universalify@0.1.2: {} + + universalify@0.2.0: {} + + universalify@2.0.1: {} + + untildify@4.0.0: {} + + update-browserslist-db@1.1.0(browserslist@4.23.3): + dependencies: + browserslist: 4.23.3 + escalade: 3.2.0 + picocolors: 1.0.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + use-sync-external-store@1.2.2(react@18.3.1): + dependencies: + react: 18.3.1 + + util-deprecate@1.0.2: {} + + uuid@8.3.2: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + verror@1.10.0: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + + vite-node@2.0.5: + dependencies: + cac: 6.7.14 + debug: 4.3.6(supports-color@5.5.0) + pathe: 1.1.2 + tinyrainbow: 1.2.0 + vite: 5.4.2 + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite-tsconfig-paths@5.0.1(typescript@5.5.4)(vite@5.4.2): + dependencies: + debug: 4.3.6(supports-color@5.5.0) + globrex: 0.1.2 + tsconfck: 3.1.1(typescript@5.5.4) + vite: 5.4.2 + transitivePeerDependencies: + - supports-color + - typescript + + vite@5.4.2: + dependencies: + esbuild: 0.21.5 + postcss: 8.4.41 + rollup: 4.21.1 + optionalDependencies: + fsevents: 2.3.3 + + vitest@2.0.5: dependencies: '@ampproject/remapping': 2.3.0 '@vitest/expect': 2.0.5 @@ -7471,19 +8746,12 @@ packages: - sugarss - supports-color - terser - dev: true - /w3c-xmlserializer@5.0.0: - resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} - engines: {node: '>=18'} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 - dev: true - /wait-on@7.2.0(debug@4.3.6): - resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} - engines: {node: '>=12.0.0'} - hasBin: true + wait-on@7.2.0(debug@4.3.6): dependencies: axios: 1.7.5(debug@4.3.6) joi: 17.13.3 @@ -7492,69 +8760,44 @@ packages: rxjs: 7.8.1 transitivePeerDependencies: - debug - dev: true - /webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: true + webidl-conversions@3.0.1: {} - /webidl-conversions@4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - dev: true + webidl-conversions@4.0.2: {} - /webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - dev: true + webidl-conversions@7.0.0: {} - /whatwg-encoding@3.1.1: - resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} - engines: {node: '>=18'} + whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 - dev: true - /whatwg-mimetype@4.0.0: - resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} - engines: {node: '>=18'} - dev: true + whatwg-mimetype@4.0.0: {} - /whatwg-url@14.0.0: - resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} - engines: {node: '>=18'} + whatwg-url@14.0.0: dependencies: tr46: 5.0.0 webidl-conversions: 7.0.0 - dev: true - /whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - dev: true - /whatwg-url@7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + whatwg-url@7.1.0: dependencies: lodash.sortby: 4.7.0 tr46: 1.0.1 webidl-conversions: 4.0.2 - dev: true - /which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 is-boolean-object: 1.1.2 is-number-object: 1.0.7 is-string: 1.0.7 is-symbol: 1.0.4 - dev: false - /which-builtin-type@1.1.4: - resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==} - engines: {node: '>= 0.4'} + which-builtin-type@1.1.4: dependencies: function.prototype.name: 1.1.6 has-tostringtag: 1.0.2 @@ -7568,161 +8811,93 @@ packages: which-boxed-primitive: 1.0.2 which-collection: 1.0.2 which-typed-array: 1.1.15 - dev: false - /which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} + which-collection@1.0.2: dependencies: is-map: 2.0.3 is-set: 2.0.3 is-weakmap: 2.0.2 is-weakset: 2.0.3 - dev: false - /which-pm@2.2.0: - resolution: {integrity: sha512-MOiaDbA5ZZgUjkeMWM5EkJp4loW5ZRoa5bc3/aeMox/PJelMhE6t7S/mLuiY43DBupyxH+S0U1bTui9kWUlmsw==} - engines: {node: '>=8.15'} + which-pm@2.2.0: dependencies: load-yaml-file: 0.2.0 path-exists: 4.0.0 - dev: true - /which-typed-array@1.1.15: - resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} - engines: {node: '>= 0.4'} + which-typed-array@1.1.15: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.7 for-each: 0.3.3 gopd: 1.0.1 has-tostringtag: 1.0.2 - dev: false - /which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + which@1.3.1: dependencies: isexe: 2.0.0 - /why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - dev: true - /wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + wide-align@1.1.5: dependencies: string-width: 4.2.3 - dev: true - /word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} + word-wrap@1.2.5: {} - /wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - - /wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - /wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - /wrap-ansi@9.0.0: - resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} - engines: {node: '>=18'} + wrap-ansi@9.0.0: dependencies: ansi-styles: 6.2.1 string-width: 7.2.0 strip-ansi: 7.1.0 - dev: true - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + wrappy@1.0.2: {} - /ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: true + ws@8.18.0: {} - /xml-name-validator@5.0.0: - resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} - engines: {node: '>=18'} - dev: true + xml-name-validator@5.0.0: {} - /xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - dev: true + xmlchars@2.2.0: {} - /y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} + y18n@5.0.8: {} - /yallist@2.1.2: - resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} - dev: true + yallist@2.1.2: {} - /yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@3.1.1: {} - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true + yallist@4.0.0: {} - /yaml@2.5.0: - resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==} - engines: {node: '>= 14'} - hasBin: true + yaml@2.5.0: {} - /yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - dev: true + yargs-parser@20.2.9: {} - /yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + yargs-parser@21.1.1: {} - /yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} + yargs@17.7.2: dependencies: cliui: 8.0.1 escalade: 3.2.0 @@ -7732,13 +8907,9 @@ packages: y18n: 5.0.8 yargs-parser: 21.1.1 - /yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yauzl@2.10.0: dependencies: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 - dev: true - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} + yocto-queue@0.1.0: {} From 76f20b771c23eae91e2261658a7cffc9b30fe0c7 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 11 Sep 2024 17:12:43 +0200 Subject: [PATCH 152/640] idp type from intent --- .../(login)/idp/[provider]/success/page.tsx | 12 +++- apps/login/src/lib/idp.ts | 70 ++++++++++++++++--- apps/login/src/lib/zitadel.ts | 6 +- 3 files changed, 74 insertions(+), 14 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index c144186bf6..e8c4f9a2cc 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -1,4 +1,4 @@ -import { PROVIDER_MAPPING } from "@/lib/idp"; +import { idpTypeToIdentityProviderType, PROVIDER_MAPPING } from "@/lib/idp"; import { addIDPLink, createUser, @@ -51,11 +51,17 @@ export default async function Page({ const idp = await getIDPByID(idpInformation.idpId); const options = idp?.config?.options; + if (!idp) { + throw new Error("IDP not found"); + } + + const providerType = idpTypeToIdentityProviderType(idp.type); + // search for potential user via username, then link if (options?.isLinkingAllowed) { let foundUser; const email = - PROVIDER_MAPPING[provider](idpInformation).email?.email; + PROVIDER_MAPPING[providerType](idpInformation).email?.email; if (options.autoLinking === AutoLinkingOption.EMAIL && email) { foundUser = await listUsers({ email }).then((response) => { @@ -118,7 +124,7 @@ export default async function Page({ } if (options?.isCreationAllowed && options.isAutoCreation) { - const newUser = await createUser(provider, idpInformation); + const newUser = await createUser(providerType, idpInformation); if (newUser) { return ( diff --git a/apps/login/src/lib/idp.ts b/apps/login/src/lib/idp.ts index 73965b296c..cf98ad2281 100644 --- a/apps/login/src/lib/idp.ts +++ b/apps/login/src/lib/idp.ts @@ -1,4 +1,5 @@ import { create } from "@zitadel/client"; +import { IDPType } from "@zitadel/proto/zitadel/idp/v2/idp_pb"; import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { @@ -11,12 +12,22 @@ export function idpTypeToSlug(idpType: IdentityProviderType) { switch (idpType) { case IdentityProviderType.GITHUB: return "github"; + case IdentityProviderType.GITHUB_ES: + return "github_es"; + case IdentityProviderType.GITLAB: + return "gitlab"; + case IdentityProviderType.GITLAB_SELF_HOSTED: + return "gitlab_es"; + case IdentityProviderType.APPLE: + return "apple"; case IdentityProviderType.GOOGLE: return "google"; case IdentityProviderType.AZURE_AD: return "azure"; case IdentityProviderType.SAML: return "saml"; + case IdentityProviderType.OAUTH: + return "oauth"; case IdentityProviderType.OIDC: return "oidc"; default: @@ -24,6 +35,45 @@ export function idpTypeToSlug(idpType: IdentityProviderType) { } } +// TODO: this is ugly but needed atm as the getIDPByID returns a IDPType and not a IdentityProviderType +export function idpTypeToIdentityProviderType( + idpType: IDPType, +): IdentityProviderType { + switch (idpType) { + case IDPType.IDP_TYPE_GITHUB: + return IdentityProviderType.GITHUB; + + case IDPType.IDP_TYPE_GITHUB_ES: + return IdentityProviderType.GITHUB_ES; + + case IDPType.IDP_TYPE_GITLAB: + return IdentityProviderType.GITLAB; + + case IDPType.IDP_TYPE_GITLAB_SELF_HOSTED: + return IdentityProviderType.GITLAB_SELF_HOSTED; + + case IDPType.IDP_TYPE_APPLE: + return IdentityProviderType.APPLE; + + case IDPType.IDP_TYPE_GOOGLE: + return IdentityProviderType.GOOGLE; + + case IDPType.IDP_TYPE_AZURE_AD: + return IdentityProviderType.AZURE_AD; + + case IDPType.IDP_TYPE_SAML: + return IdentityProviderType.SAML; + + case IDPType.IDP_TYPE_OAUTH: + return IdentityProviderType.OAUTH; + + case IDPType.IDP_TYPE_OIDC: + return IdentityProviderType.OIDC; + + default: + throw new Error("Unknown identity provider type"); + } +} // this maps the IDPInformation to the AddHumanUserRequest which is used when creating a user or linking a user (email) // TODO: extend this object from a other file which can be overwritten by customers like map = { ...PROVIDER_MAPPING, ...customerMap } export type OIDC_USER = { @@ -87,9 +137,9 @@ const GITHUB_MAPPING = (idp: IDPInformation) => { }; export const PROVIDER_MAPPING: { - [provider: string]: (rI: IDPInformation) => AddHumanUserRequest; + [provider: number]: (rI: IDPInformation) => AddHumanUserRequest; } = { - [idpTypeToSlug(IdentityProviderType.GOOGLE)]: (idp: IDPInformation) => { + [IdentityProviderType.GOOGLE]: (idp: IDPInformation) => { const rawInfo = idp.rawInformation as OIDC_USER; console.log(rawInfo); @@ -113,12 +163,12 @@ export const PROVIDER_MAPPING: { ], }); }, - [idpTypeToSlug(IdentityProviderType.GITLAB)]: OIDC_MAPPING, - [idpTypeToSlug(IdentityProviderType.GITLAB_SELF_HOSTED)]: OIDC_MAPPING, - [idpTypeToSlug(IdentityProviderType.OIDC)]: OIDC_MAPPING, + [IdentityProviderType.GITLAB]: OIDC_MAPPING, + [IdentityProviderType.GITLAB_SELF_HOSTED]: OIDC_MAPPING, + [IdentityProviderType.OIDC]: OIDC_MAPPING, // check - [idpTypeToSlug(IdentityProviderType.OAUTH)]: OIDC_MAPPING, - [idpTypeToSlug(IdentityProviderType.AZURE_AD)]: (idp: IDPInformation) => { + [IdentityProviderType.OAUTH]: OIDC_MAPPING, + [IdentityProviderType.AZURE_AD]: (idp: IDPInformation) => { const rawInfo = idp.rawInformation as { jobTitle: string; mail: string; @@ -152,9 +202,9 @@ export const PROVIDER_MAPPING: { ], }); }, - [idpTypeToSlug(IdentityProviderType.GITHUB)]: GITHUB_MAPPING, - [idpTypeToSlug(IdentityProviderType.GITHUB_ES)]: GITHUB_MAPPING, - [idpTypeToSlug(IdentityProviderType.APPLE)]: (idp: IDPInformation) => { + [IdentityProviderType.GITHUB]: GITHUB_MAPPING, + [IdentityProviderType.GITHUB_ES]: GITHUB_MAPPING, + [IdentityProviderType.APPLE]: (idp: IDPInformation) => { const rawInfo = idp.rawInformation as { name?: string; firstName?: string; diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 7239da23dc..3bf51753e5 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -20,6 +20,7 @@ import { import { create } from "@zitadel/client"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; +import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { SearchQuery, @@ -436,7 +437,10 @@ export function addIDPLink( ); } -export function createUser(provider: string, info: IDPInformation) { +export function createUser( + provider: IdentityProviderType, + info: IDPInformation, +) { const userData = PROVIDER_MAPPING[provider](info); console.log("ud", userData); return userService.addHumanUser(userData, {}); From 181a1cdfef6a1fc71dc8ecc6c0f044212bb1f0ea Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 11 Sep 2024 17:28:03 +0200 Subject: [PATCH 153/640] apple idp --- apps/login/src/ui/SignInWithIDP.tsx | 5 ++--- apps/login/src/ui/idps/SignInWithApple.tsx | 14 ++++++-------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/apps/login/src/ui/SignInWithIDP.tsx b/apps/login/src/ui/SignInWithIDP.tsx index f324147a40..b6ffff41ea 100644 --- a/apps/login/src/ui/SignInWithIDP.tsx +++ b/apps/login/src/ui/SignInWithIDP.tsx @@ -14,7 +14,6 @@ import { SignInWithAzureAD } from "./idps/SignInWithAzureAD"; import { SignInWithGeneric } from "./idps/SignInWithGeneric"; import { SignInWithGithub } from "./idps/SignInWithGithub"; import { SignInWithGitlab } from "./idps/SignInWithGitlab"; -import { SignInWithGoogle } from "./idps/SignInWithGoogle"; export interface SignInWithIDPProps { children?: ReactNode; @@ -140,14 +139,14 @@ export function SignInWithIDP({ ); case IdentityProviderType.GOOGLE: return ( - navigateToAuthUrl(idp.id, IdentityProviderType.GOOGLE) } - > + > ); case IdentityProviderType.GITLAB: return ( diff --git a/apps/login/src/ui/idps/SignInWithApple.tsx b/apps/login/src/ui/idps/SignInWithApple.tsx index 2f1bfb7599..37ec080f3f 100644 --- a/apps/login/src/ui/idps/SignInWithApple.tsx +++ b/apps/login/src/ui/idps/SignInWithApple.tsx @@ -15,14 +15,12 @@ export const SignInWithApple = forwardRef< {...props} >
- - - +
+ + Apple Logo + + +
{children ? ( children From 623fc086bd25582cbf783832c36ab0812fc865b0 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 12 Sep 2024 08:19:07 +0200 Subject: [PATCH 154/640] fallback text --- apps/login/src/ui/idps/SignInWithApple.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/ui/idps/SignInWithApple.tsx b/apps/login/src/ui/idps/SignInWithApple.tsx index 37ec080f3f..725a59b5e8 100644 --- a/apps/login/src/ui/idps/SignInWithApple.tsx +++ b/apps/login/src/ui/idps/SignInWithApple.tsx @@ -25,7 +25,7 @@ export const SignInWithApple = forwardRef< {children ? ( children ) : ( - {name ? name : "Sign in with Google"} + {name ? name : "Sign in with Apple"} )} ), From 98f221b9811d971c0ed74b25781800dc17c536e4 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 12 Sep 2024 08:26:12 +0200 Subject: [PATCH 155/640] idp button --- apps/login/src/ui/SignInWithIDP.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/login/src/ui/SignInWithIDP.tsx b/apps/login/src/ui/SignInWithIDP.tsx index b6ffff41ea..f324147a40 100644 --- a/apps/login/src/ui/SignInWithIDP.tsx +++ b/apps/login/src/ui/SignInWithIDP.tsx @@ -14,6 +14,7 @@ import { SignInWithAzureAD } from "./idps/SignInWithAzureAD"; import { SignInWithGeneric } from "./idps/SignInWithGeneric"; import { SignInWithGithub } from "./idps/SignInWithGithub"; import { SignInWithGitlab } from "./idps/SignInWithGitlab"; +import { SignInWithGoogle } from "./idps/SignInWithGoogle"; export interface SignInWithIDPProps { children?: ReactNode; @@ -139,14 +140,14 @@ export function SignInWithIDP({ ); case IdentityProviderType.GOOGLE: return ( - navigateToAuthUrl(idp.id, IdentityProviderType.GOOGLE) } - > + > ); case IdentityProviderType.GITLAB: return ( From c6d815fe79526399366fa6af619f2eadad67a1b0 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 12 Sep 2024 08:42:48 +0200 Subject: [PATCH 156/640] verify tests --- apps/login/cypress/integration/verify.cy.ts | 39 ++++++++++++++++++++- packages/zitadel-client/src/index.ts | 2 +- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index a26d8b51e2..1bbe265c02 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -1,11 +1,48 @@ import { stub } from "../support/mock"; describe("/verify", () => { - it("redirects after successful email verification", () => { + it("if no MFA required, redirects to loginname after successful email verification", () => { stub("zitadel.user.v2.UserService", "VerifyEmail"); cy.visit("/verify?userId=123&code=abc&submit=true"); cy.location("pathname", { timeout: 10_000 }).should("eq", "/loginname"); }); + it("if MFA is required, redirects to mfa/set after successful email verification", () => { + stub("zitadel.settings.v2.SettingsService", "GetLoginSettings", { + data: { + settings: { + forceMfa: true, + }, + }, + }); + stub("zitadel.user.v2.UserService", "VerifyEmail"); + // stub("zitadel.session.v2.SessionService", "GetSession", { + // data: { + // session: { + // id: "221394658884845598", + // creationDate: new Date("2024-04-04T09:40:55.577Z"), + // changeDate: new Date("2024-04-04T09:40:55.577Z"), + // sequence: 859, + // factors: { + // user: { + // id: "221394658884845598", + // loginName: "john@zitadel.com", + // }, + // otpEmail: { // set a factor + // verifiedAt: timestampFromDate( + // new Date("2024-04-04T09:40:55.577Z"), + // ), + // }, + // password: undefined, + // webAuthN: undefined, + // intent: undefined, + // }, + // metadata: {}, + // }, + // }, + // }); + cy.visit("/verify?userId=123&code=abc&submit=true"); + cy.location("pathname", { timeout: 10_000 }).should("eq", "/mfa/set"); + }); it("shows an error if validation failed", () => { stub("zitadel.user.v2.UserService", "VerifyEmail", { code: 3, diff --git a/packages/zitadel-client/src/index.ts b/packages/zitadel-client/src/index.ts index 5ff017ae2f..2e262dd013 100644 --- a/packages/zitadel-client/src/index.ts +++ b/packages/zitadel-client/src/index.ts @@ -3,5 +3,5 @@ export { NewAuthorizationBearerInterceptor } from "./interceptors"; // TODO: Move this to `./protobuf.ts` and export it from there export { create, fromJson, toJson } from "@bufbuild/protobuf"; -export { TimestampSchema, timestampDate } from "@bufbuild/protobuf/wkt"; +export { TimestampSchema, timestampDate, timestampFromDate } from "@bufbuild/protobuf/wkt"; export type { Timestamp } from "@bufbuild/protobuf/wkt"; From 38f1bad24cf801558a129beb16c24535d72fcb3b Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 12 Sep 2024 09:06:49 +0200 Subject: [PATCH 157/640] gitlab --- apps/login/src/lib/idp.ts | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/apps/login/src/lib/idp.ts b/apps/login/src/lib/idp.ts index cf98ad2281..fb8386c2bd 100644 --- a/apps/login/src/lib/idp.ts +++ b/apps/login/src/lib/idp.ts @@ -85,6 +85,34 @@ export type OIDC_USER = { }; }; +const GITLAB_MAPPING = (idp: IDPInformation) => { + const rawInfo = idp.rawInformation as { + name: string; + email: string; + email_verified: boolean; + }; + + return create(AddHumanUserRequestSchema, { + username: idp.userName, + email: { + email: rawInfo.email, + verification: { case: "isVerified", value: rawInfo.email_verified }, + }, + profile: { + displayName: rawInfo.name || idp.userName || "", + givenName: "", + familyName: "", + }, + idpLinks: [ + { + idpId: idp.idpId, + userId: idp.userId, + userName: idp.userName, + }, + ], + }); +}; + const OIDC_MAPPING = (idp: IDPInformation) => { const rawInfo = idp.rawInformation as OIDC_USER; @@ -163,8 +191,8 @@ export const PROVIDER_MAPPING: { ], }); }, - [IdentityProviderType.GITLAB]: OIDC_MAPPING, - [IdentityProviderType.GITLAB_SELF_HOSTED]: OIDC_MAPPING, + [IdentityProviderType.GITLAB]: GITLAB_MAPPING, + [IdentityProviderType.GITLAB_SELF_HOSTED]: GITLAB_MAPPING, [IdentityProviderType.OIDC]: OIDC_MAPPING, // check [IdentityProviderType.OAUTH]: OIDC_MAPPING, From 99aea433eab9e770195fb73d51179d54a6d368d0 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 12 Sep 2024 09:11:35 +0200 Subject: [PATCH 158/640] test --- apps/login/cypress/integration/verify.cy.ts | 75 ++++++++++++++------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index 1bbe265c02..7a482c7a6d 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -1,12 +1,39 @@ +import { timestampFromDate } from "@zitadel/client"; import { stub } from "../support/mock"; describe("/verify", () => { it("if no MFA required, redirects to loginname after successful email verification", () => { stub("zitadel.user.v2.UserService", "VerifyEmail"); + stub("zitadel.session.v2.SessionService", "GetSession", { + data: { + session: { + id: "221394658884845598", + creationDate: new Date("2024-04-04T09:40:55.577Z"), + changeDate: new Date("2024-04-04T09:40:55.577Z"), + sequence: 859, + factors: { + user: { + id: "221394658884845598", + loginName: "john@zitadel.com", + }, + otpEmail: { + // set a factor + verifiedAt: timestampFromDate( + new Date("2024-04-04T09:40:55.577Z"), + ), + }, + password: undefined, + webAuthN: undefined, + intent: undefined, + }, + metadata: {}, + }, + }, + }); cy.visit("/verify?userId=123&code=abc&submit=true"); cy.location("pathname", { timeout: 10_000 }).should("eq", "/loginname"); }); - it("if MFA is required, redirects to mfa/set after successful email verification", () => { + it("if MFA is required and no mfa factor is found, redirects to mfa/set after successful email verification", () => { stub("zitadel.settings.v2.SettingsService", "GetLoginSettings", { data: { settings: { @@ -15,31 +42,27 @@ describe("/verify", () => { }, }); stub("zitadel.user.v2.UserService", "VerifyEmail"); - // stub("zitadel.session.v2.SessionService", "GetSession", { - // data: { - // session: { - // id: "221394658884845598", - // creationDate: new Date("2024-04-04T09:40:55.577Z"), - // changeDate: new Date("2024-04-04T09:40:55.577Z"), - // sequence: 859, - // factors: { - // user: { - // id: "221394658884845598", - // loginName: "john@zitadel.com", - // }, - // otpEmail: { // set a factor - // verifiedAt: timestampFromDate( - // new Date("2024-04-04T09:40:55.577Z"), - // ), - // }, - // password: undefined, - // webAuthN: undefined, - // intent: undefined, - // }, - // metadata: {}, - // }, - // }, - // }); + stub("zitadel.session.v2.SessionService", "GetSession", { + data: { + session: { + id: "221394658884845598", + creationDate: new Date("2024-04-04T09:40:55.577Z"), + changeDate: new Date("2024-04-04T09:40:55.577Z"), + sequence: 859, + factors: { + user: { + id: "221394658884845598", + loginName: "john@zitadel.com", + }, + otpEmail: undefined, + password: undefined, + webAuthN: undefined, + intent: undefined, + }, + metadata: {}, + }, + }, + }); cy.visit("/verify?userId=123&code=abc&submit=true"); cy.location("pathname", { timeout: 10_000 }).should("eq", "/mfa/set"); }); From daf1f5ac7ab41d62134bf5314b293e856a5bbd9a Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 12 Sep 2024 16:08:58 +0200 Subject: [PATCH 159/640] mermaid flow diagram --- apps/login/readme.md | 43 +++++++++++++++++++ apps/login/src/app/(login)/loginname/page.tsx | 1 - .../login/src/app/(login)/mfa/create/page.tsx | 27 ------------ apps/login/src/ui/UsernameForm.tsx | 3 -- 4 files changed, 43 insertions(+), 31 deletions(-) delete mode 100644 apps/login/src/app/(login)/mfa/create/page.tsx diff --git a/apps/login/readme.md b/apps/login/readme.md index 086c0ac070..3d49f991df 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -1,3 +1,46 @@ # ZITADEL Login UI This is going to be our next UI for the hosted login. It's based on Next.js 13 and its introduced `app/` directory. + +## Flow Diagram + +```mermaid + flowchart TD + A[Start] --> register + A[Start] --> accounts + A[Start] --> loginname + A[Start] --> register + idp-success --> B[signedin] + idp --> idp-success + idp --> idp-failure + idp-failure --> loginname + loginname --> password + A[Start] -- signInWithIDP --> idp + loginname -- hasPasskey --> passkey + loginname -- allowRegister --> register + passkey-add --passwordAllowed --> password + passkey -- hasPassword --> password + passkey --> B[signedin] + password -- hasMFA --> mfa + password -- allowPasskeys --> passkey-add + mfa --> otp + otp --> B[signedin] + mfa--> u2f + u2f -->B[signedin] + register --> passkey-add + register --> password-set + password-set --> B[signedin] + passkey-add --> B[signedin] + password --> B[signedin] + password-- forceMFA -->mfaset + mfaset --> u2fset + mfaset --> otpset + u2fset --> B[signedin] + otpset --> B[signedin] + accounts--> loginname + + password -- not verified yet -->verify + register-- withpassword -->verify + passkey-- notVerified --> verify + verify --> B[signedin] +``` diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 7d04c3924b..7c87474cd1 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -45,7 +45,6 @@ export default async function Page({

Enter your login data.

-

Password

-

Enter your password.

- - - -
- -
-
- - -
-
- ); -} diff --git a/apps/login/src/ui/UsernameForm.tsx b/apps/login/src/ui/UsernameForm.tsx index aed25fb23f..8f87d9a458 100644 --- a/apps/login/src/ui/UsernameForm.tsx +++ b/apps/login/src/ui/UsernameForm.tsx @@ -1,7 +1,6 @@ "use client"; import { sendLoginname } from "@/lib/server/loginname"; -import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { useRouter } from "next/navigation"; import { ReactNode, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; @@ -16,7 +15,6 @@ type Inputs = { }; type Props = { - loginSettings: LoginSettings | undefined; loginName: string | undefined; authRequestId: string | undefined; organization?: string; @@ -26,7 +24,6 @@ type Props = { }; export default function UsernameForm({ - loginSettings, loginName, authRequestId, organization, From 3d99a686faa5cfb8bf8c5b7f06fd4ffcfe967abd Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 12 Sep 2024 16:21:41 +0200 Subject: [PATCH 160/640] flowchart --- apps/login/readme.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 3d49f991df..d7a9586651 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -10,12 +10,12 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a A[Start] --> accounts A[Start] --> loginname A[Start] --> register - idp-success --> B[signedin] + A[Start] -- signInWithIDP --> idp idp --> idp-success idp --> idp-failure + idp-success --> B[signedin] idp-failure --> loginname loginname --> password - A[Start] -- signInWithIDP --> idp loginname -- hasPasskey --> passkey loginname -- allowRegister --> register passkey-add --passwordAllowed --> password @@ -38,7 +38,6 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a u2fset --> B[signedin] otpset --> B[signedin] accounts--> loginname - password -- not verified yet -->verify register-- withpassword -->verify passkey-- notVerified --> verify From c9559142780460308bf7e4864d4700531f77e469 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 12 Sep 2024 16:28:15 +0200 Subject: [PATCH 161/640] cleanup verify --- apps/login/src/app/(login)/verify/page.tsx | 28 +++------------------- apps/login/src/ui/VerifyEmailForm.tsx | 20 ---------------- 2 files changed, 3 insertions(+), 45 deletions(-) diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index fb9a90b081..de246e0c5e 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -1,8 +1,6 @@ -import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; import Alert from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; -import UserAvatar from "@/ui/UserAvatar"; import VerifyEmailForm from "@/ui/VerifyEmailForm"; import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; @@ -17,11 +15,6 @@ export default async function Page({ searchParams }: { searchParams: any }) { authRequestId, } = searchParams; - const sessionFactors = await loadMostRecentSession({ - loginName, - organization, - }); - const branding = await getBrandingSettings(organization); const loginSettings = await getLoginSettings(organization); @@ -34,24 +27,15 @@ export default async function Page({ searchParams }: { searchParams: any }) { Enter the Code provided in the verification email.

- {(!sessionFactors || !loginName) && ( + {!userId && (
- Could not get the context of the user. Make sure to enter the - username first or provide a loginName as searchParam. + Could not get the context of the user. Make sure to provide a + userId as searchParam.
)} - {sessionFactors && ( - - )} - {userId ? ( ) : (
diff --git a/apps/login/src/ui/VerifyEmailForm.tsx b/apps/login/src/ui/VerifyEmailForm.tsx index d2d130fadc..ce2589661d 100644 --- a/apps/login/src/ui/VerifyEmailForm.tsx +++ b/apps/login/src/ui/VerifyEmailForm.tsx @@ -23,7 +23,6 @@ type Props = { authRequestId?: string; sessionId?: string; loginSettings?: LoginSettings; - hasMfaSetUp: boolean; }; export default function VerifyEmailForm({ @@ -35,7 +34,6 @@ export default function VerifyEmailForm({ authRequestId, sessionId, loginSettings, - hasMfaSetUp, }: Props) { const { register, handleSubmit, formState } = useForm({ mode: "onBlur", @@ -88,24 +86,6 @@ export default function VerifyEmailForm({ return; } - if (loginSettings && loginSettings.forceMfa && !hasMfaSetUp) { - const params = new URLSearchParams({ checkAfter: "true" }); - - if (loginName) { - params.set("organization", loginName); - } - if (organization) { - params.set("organization", organization); - } - - if (authRequestId && sessionId) { - params.set("authRequest", authRequestId); - params.set("sessionId", sessionId); - } - - return router.push(`/mfa/set?` + params); - } - const params = new URLSearchParams({}); if (organization) { From e2020459ed707e75e03b6a2b36cdba21a0a167f4 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 12 Sep 2024 16:30:48 +0200 Subject: [PATCH 162/640] revert tests --- apps/login/cypress/integration/verify.cy.ts | 62 +-------------------- 1 file changed, 1 insertion(+), 61 deletions(-) diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index 7a482c7a6d..a26d8b51e2 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -1,71 +1,11 @@ -import { timestampFromDate } from "@zitadel/client"; import { stub } from "../support/mock"; describe("/verify", () => { - it("if no MFA required, redirects to loginname after successful email verification", () => { + it("redirects after successful email verification", () => { stub("zitadel.user.v2.UserService", "VerifyEmail"); - stub("zitadel.session.v2.SessionService", "GetSession", { - data: { - session: { - id: "221394658884845598", - creationDate: new Date("2024-04-04T09:40:55.577Z"), - changeDate: new Date("2024-04-04T09:40:55.577Z"), - sequence: 859, - factors: { - user: { - id: "221394658884845598", - loginName: "john@zitadel.com", - }, - otpEmail: { - // set a factor - verifiedAt: timestampFromDate( - new Date("2024-04-04T09:40:55.577Z"), - ), - }, - password: undefined, - webAuthN: undefined, - intent: undefined, - }, - metadata: {}, - }, - }, - }); cy.visit("/verify?userId=123&code=abc&submit=true"); cy.location("pathname", { timeout: 10_000 }).should("eq", "/loginname"); }); - it("if MFA is required and no mfa factor is found, redirects to mfa/set after successful email verification", () => { - stub("zitadel.settings.v2.SettingsService", "GetLoginSettings", { - data: { - settings: { - forceMfa: true, - }, - }, - }); - stub("zitadel.user.v2.UserService", "VerifyEmail"); - stub("zitadel.session.v2.SessionService", "GetSession", { - data: { - session: { - id: "221394658884845598", - creationDate: new Date("2024-04-04T09:40:55.577Z"), - changeDate: new Date("2024-04-04T09:40:55.577Z"), - sequence: 859, - factors: { - user: { - id: "221394658884845598", - loginName: "john@zitadel.com", - }, - otpEmail: undefined, - password: undefined, - webAuthN: undefined, - intent: undefined, - }, - metadata: {}, - }, - }, - }); - cy.visit("/verify?userId=123&code=abc&submit=true"); - cy.location("pathname", { timeout: 10_000 }).should("eq", "/mfa/set"); - }); it("shows an error if validation failed", () => { stub("zitadel.user.v2.UserService", "VerifyEmail", { code: 3, From f07a64f4e52c4e9299332f8603bc49897215cc88 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 13 Sep 2024 14:01:03 +0200 Subject: [PATCH 163/640] loginname to ignore usernames, description --- apps/login/readme.md | 30 ++++++ apps/login/src/app/(login)/register/page.tsx | 4 + apps/login/src/lib/server/loginname.ts | 97 ++++++++++++-------- apps/login/src/ui/UsernameForm.tsx | 13 +-- 4 files changed, 96 insertions(+), 48 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index d7a9586651..fe10c5e8ce 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -43,3 +43,33 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a passkey-- notVerified --> verify verify --> B[signedin] ``` + +### /loginname + +This page shows a loginname field and Identity Providers to login or register. +If `loginSettings(org?).allowRegister` is `true`, if will also show a link to jump to /register + +- `getLoginSettings(org?)` +- `getLegalAndSupportSettings(org?)` +- `getIdentityProviders(org?)` +- `getBrandingSettings(org?)` +- `getActiveIdentityProviders(org?)` +- `startIdentityProviderFlow` +- `listUsers(org?)` +- `listAuthenticationMethodTypes` + +After a loginname is entered, a `listUsers` request is made using the loginName query to identify already registered users. + +If only one user is found, we query `listAuthenticationMethodTypes` to identify future steps. +If no authentication methods are found, we render an error stating: _User has no available authentication methods._ +Now if only one method is found, we continue with the corresponding step (/password, /passkey/login). +If multiple methods are set, we prefer passkeys over any other method, so we redirect to /passkey, second option is IDP, and third is password. +If password is the next step, we check `loginSettings.passkeysType` for PasskeysType.ALLOWED, and prompt the user to setup passkeys afterwards. + +If no user is found, we check whether registering is allowed using `loginSettings.allowRegister`. +If `loginSettings?.allowUsernamePassword` is not allowed we continue to check for available IDPs. If a single IDP is available, we directly redirect the user to signup. + +If no single IDP is set, we check for `loginSettings.allowUsernamePassword` and redirect the user to /register page. +If no previous condition is met, we check whether `loginSettings?.ignoreUnknownUsernames` is `false` and in such case, we return a user not found error. If not, we redirect to the /password page, regardless (to not leak information about a registered user). + +> NOTE: We ignore `loginSettings.allowExternalIdp` as the information whether IDPs are available comes as response from `getActiveIdentityProviders(org?)` diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index f010f229c2..700173e9dc 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -15,6 +15,10 @@ export default async function Page({ const { firstname, lastname, email, organization, authRequestId } = searchParams; + if (!organization) { + // TODO: get default organization + } + const setPassword = !!(firstname && lastname && email); const legal = await getLegalAndSupportSettings(organization); diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 5831d3eb21..3701fa7de2 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -28,6 +28,45 @@ export async function sendLoginname(command: SendLoginnameCommand) { const loginSettings = await getLoginSettings(command.organization); + const redirectUserToSingleIDPIfAvailable = async () => { + const identityProviders = await getActiveIdentityProviders( + command.organization, + ).then((resp) => { + return resp.identityProviders; + }); + + if (identityProviders.length === 1) { + const host = headers().get("host"); + const identityProviderType = identityProviders[0].type; + + const provider = idpTypeToSlug(identityProviderType); + + const params = new URLSearchParams(); + + if (command.authRequestId) { + params.set("authRequestId", command.authRequestId); + } + + if (command.organization) { + params.set("organization", command.organization); + } + + const resp = await startIdentityProviderFlow({ + idpId: identityProviders[0].id, + urls: { + successUrl: + `${host}/idp/${provider}/success?` + new URLSearchParams(params), + failureUrl: + `${host}/idp/${provider}/failure?` + new URLSearchParams(params), + }, + }); + + if (resp?.nextStep.case === "authUrl") { + return redirect(resp.nextStep.value); + } + } + }; + if (users.details?.totalResult == BigInt(1) && users.result[0].userId) { const userId = users.result[0].userId; const session = await createSessionForUserIdAndUpdateCookie( @@ -115,13 +154,14 @@ export async function sendLoginname(command: SendLoginnameCommand) { methods.authMethodTypes.includes(AuthenticationMethodType.IDP) ) { // TODO: redirect user to idp + await redirectUserToSingleIDPIfAvailable(); } else if ( methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD) ) { // user has no passkey setup and login settings allow passkeys const paramsPasswordDefault: any = { loginName: command.loginName }; - if (loginSettings?.passkeysType === 1) { + if (loginSettings?.passkeysType === PasskeysType.ALLOWED) { paramsPasswordDefault.promptPasswordless = `true`; // PasskeysType.PASSKEYS_TYPE_ALLOWED, } @@ -146,42 +186,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (loginSettings?.allowRegister && !loginSettings?.allowUsernamePassword) { // TODO redirect to loginname page with idp hint - const identityProviders = await getActiveIdentityProviders( - command.organization, - ).then((resp) => { - return resp.identityProviders; - }); - - if (identityProviders.length === 1) { - const host = headers().get("host"); - const identityProviderType = identityProviders[0].type; - - const provider = idpTypeToSlug(identityProviderType); - - const params = new URLSearchParams(); - - if (command.authRequestId) { - params.set("authRequestId", command.authRequestId); - } - - if (command.organization) { - params.set("organization", command.organization); - } - - const resp = await startIdentityProviderFlow({ - idpId: identityProviders[0].id, - urls: { - successUrl: - `${host}/idp/${provider}/success?` + new URLSearchParams(params), - failureUrl: - `${host}/idp/${provider}/failure?` + new URLSearchParams(params), - }, - }); - - if (resp?.nextStep.case === "authUrl") { - return redirect(resp.nextStep.value); - } - } + await redirectUserToSingleIDPIfAvailable(); throw Error("Could not find user"); } else if ( @@ -205,5 +210,23 @@ export async function sendLoginname(command: SendLoginnameCommand) { return redirect(registerUrl); } + if (loginSettings?.ignoreUnknownUsernames) { + const paramsPasswordDefault: any = { loginName: command.loginName }; + + if (loginSettings?.passkeysType === PasskeysType.ALLOWED) { + paramsPasswordDefault.promptPasswordless = `true`; + } + + if (command.authRequestId) { + paramsPasswordDefault.authRequestId = command.authRequestId; + } + + if (command.organization) { + paramsPasswordDefault.organization = command.organization; + } + + return redirect("/password?" + new URLSearchParams(paramsPasswordDefault)); + } + throw Error("Could not find user"); } diff --git a/apps/login/src/ui/UsernameForm.tsx b/apps/login/src/ui/UsernameForm.tsx index 8f87d9a458..68beacf7e9 100644 --- a/apps/login/src/ui/UsernameForm.tsx +++ b/apps/login/src/ui/UsernameForm.tsx @@ -59,17 +59,10 @@ export default function UsernameForm({ return res; } - async function setLoginNameAndGetAuthMethods( - values: Inputs, - organization?: string, - ) { - const response = await submitLoginName(values, organization); - } - useEffect(() => { if (submit && loginName) { // When we navigate to this page, we always want to be redirected if submit is true and the parameters are valid. - setLoginNameAndGetAuthMethods({ loginName }, organization); + submitLoginName({ loginName }, organization); } }, []); @@ -120,9 +113,7 @@ export default function UsernameForm({ className="self-end" variant={ButtonVariants.Primary} disabled={loading || !formState.isValid} - onClick={handleSubmit((e) => - setLoginNameAndGetAuthMethods(e, organization), - )} + onClick={handleSubmit((e) => submitLoginName(e, organization))} > {loading && } continue From 69fd0b42fad8ecd129d065c9b1ed7255dd8224cf Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 13 Sep 2024 14:06:00 +0200 Subject: [PATCH 164/640] exception --- apps/login/readme.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index fe10c5e8ce..9edef9383e 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -61,7 +61,7 @@ If `loginSettings(org?).allowRegister` is `true`, if will also show a link to ju After a loginname is entered, a `listUsers` request is made using the loginName query to identify already registered users. If only one user is found, we query `listAuthenticationMethodTypes` to identify future steps. -If no authentication methods are found, we render an error stating: _User has no available authentication methods._ +If no authentication methods are found, we render an error stating: _User has no available authentication methods._ (exception see below.) Now if only one method is found, we continue with the corresponding step (/password, /passkey/login). If multiple methods are set, we prefer passkeys over any other method, so we redirect to /passkey, second option is IDP, and third is password. If password is the next step, we check `loginSettings.passkeysType` for PasskeysType.ALLOWED, and prompt the user to setup passkeys afterwards. @@ -70,6 +70,8 @@ If no user is found, we check whether registering is allowed using `loginSetting If `loginSettings?.allowUsernamePassword` is not allowed we continue to check for available IDPs. If a single IDP is available, we directly redirect the user to signup. If no single IDP is set, we check for `loginSettings.allowUsernamePassword` and redirect the user to /register page. -If no previous condition is met, we check whether `loginSettings?.ignoreUnknownUsernames` is `false` and in such case, we return a user not found error. If not, we redirect to the /password page, regardless (to not leak information about a registered user). +If no previous condition is met we throw an error stating the user was not found. + +If the outcome after this order produces a no authentication methods found, or user not found, we check whether `loginSettings?.ignoreUnknownUsernames` is set to `true` as in this case we redirect to the /password page regardless (to not leak information about a registered user). > NOTE: We ignore `loginSettings.allowExternalIdp` as the information whether IDPs are available comes as response from `getActiveIdentityProviders(org?)` From 260c91e66147f46b9a1d4fb7228c8c9015bd9d41 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 13 Sep 2024 14:14:09 +0200 Subject: [PATCH 165/640] rm duplicate arrow --- apps/login/readme.md | 3 +-- apps/login/src/lib/server/loginname.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 9edef9383e..11ff6d3bd7 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -9,7 +9,6 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a A[Start] --> register A[Start] --> accounts A[Start] --> loginname - A[Start] --> register A[Start] -- signInWithIDP --> idp idp --> idp-success idp --> idp-failure @@ -74,4 +73,4 @@ If no previous condition is met we throw an error stating the user was not found If the outcome after this order produces a no authentication methods found, or user not found, we check whether `loginSettings?.ignoreUnknownUsernames` is set to `true` as in this case we redirect to the /password page regardless (to not leak information about a registered user). -> NOTE: We ignore `loginSettings.allowExternalIdp` as the information whether IDPs are available comes as response from `getActiveIdentityProviders(org?)` +> NOTE: We ignore `loginSettings.allowExternalIdp` as the information whether IDPs are available comes as response from `getActiveIdentityProviders(org?)`. diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 3701fa7de2..9d6e49779e 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -185,7 +185,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { // user not found, check if register is enabled on organization if (loginSettings?.allowRegister && !loginSettings?.allowUsernamePassword) { - // TODO redirect to loginname page with idp hint + // TODO: do we need to handle login hints for IDPs here? await redirectUserToSingleIDPIfAvailable(); throw Error("Could not find user"); From 4c406a829991cbfc3aecc152e13f4d813c0742d8 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 13 Sep 2024 14:15:45 +0200 Subject: [PATCH 166/640] fcns --- apps/login/readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/login/readme.md b/apps/login/readme.md index 11ff6d3bd7..ee94729667 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -48,6 +48,8 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a This page shows a loginname field and Identity Providers to login or register. If `loginSettings(org?).allowRegister` is `true`, if will also show a link to jump to /register +Requests to the APIs made: + - `getLoginSettings(org?)` - `getLegalAndSupportSettings(org?)` - `getIdentityProviders(org?)` From d687ad905a82ae5fe752f3d002b29cf950f4bf38 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 13 Sep 2024 14:18:13 +0200 Subject: [PATCH 167/640] flowchart idp --- apps/login/readme.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index ee94729667..880ba0d494 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -9,11 +9,10 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a A[Start] --> register A[Start] --> accounts A[Start] --> loginname - A[Start] -- signInWithIDP --> idp - idp --> idp-success - idp --> idp-failure + loginname -- signInWithIDP --> idp-success + loginname -- signInWithIDP --> idp-failure idp-success --> B[signedin] - idp-failure --> loginname + idp-failure -- retry --> loginname loginname --> password loginname -- hasPasskey --> passkey loginname -- allowRegister --> register From 11220421019a1701dfbde82fab0ba1bb457b797e Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 13 Sep 2024 14:24:45 +0200 Subject: [PATCH 168/640] flow --- apps/login/readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 880ba0d494..730382c5a2 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -4,6 +4,8 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a ## Flow Diagram +> Back navigation or retrys are not displayed. The flows for reauthentication after registering Passkeys, OTP or U2F methods are not displayed either as these will be omitted in future UX. + ```mermaid flowchart TD A[Start] --> register @@ -12,7 +14,6 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a loginname -- signInWithIDP --> idp-success loginname -- signInWithIDP --> idp-failure idp-success --> B[signedin] - idp-failure -- retry --> loginname loginname --> password loginname -- hasPasskey --> passkey loginname -- allowRegister --> register From 08de7ddb7838c04ef8578ca7782c60e81588a741 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 13 Sep 2024 14:47:33 +0200 Subject: [PATCH 169/640] describe discovery --- apps/login/readme.md | 4 +++- apps/login/src/lib/server/loginname.ts | 30 ++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 730382c5a2..a8383961bc 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -58,6 +58,7 @@ Requests to the APIs made: - `startIdentityProviderFlow` - `listUsers(org?)` - `listAuthenticationMethodTypes` +- `getOrgsByDomain` After a loginname is entered, a `listUsers` request is made using the loginName query to identify already registered users. @@ -70,7 +71,8 @@ If password is the next step, we check `loginSettings.passkeysType` for Passkeys If no user is found, we check whether registering is allowed using `loginSettings.allowRegister`. If `loginSettings?.allowUsernamePassword` is not allowed we continue to check for available IDPs. If a single IDP is available, we directly redirect the user to signup. -If no single IDP is set, we check for `loginSettings.allowUsernamePassword` and redirect the user to /register page. +If no single IDP is set, we check for `loginSettings.allowUsernamePassword` and if no organization is set as context, we check whether we can discover a organization from the loginname of the user (using: `getOrgsByDomain`). Then if an organization is found, we check whether domainDiscovery is allowed on it and redirect the user to /register page including the discovered domain or without. + If no previous condition is met we throw an error stating the user was not found. If the outcome after this order produces a no authentication methods found, or user not found, we check whether `loginSettings?.ignoreUnknownUsernames` is set to `true` as in this case we redirect to the /password page regardless (to not leak information about a registered user). diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 9d6e49779e..20475e4104 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -9,6 +9,7 @@ import { idpTypeToSlug } from "../idp"; import { getActiveIdentityProviders, getLoginSettings, + getOrgsByDomain, listAuthenticationMethodTypes, listUsers, startIdentityProviderFlow, @@ -20,6 +21,8 @@ export type SendLoginnameCommand = { organization?: string; }; +const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; + export async function sendLoginname(command: SendLoginnameCommand) { const users = await listUsers({ loginName: command.loginName, @@ -181,9 +184,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } } - // TODO: check if allowDomainDiscovery has to be allowed too, to redirect to the register page // user not found, check if register is enabled on organization - if (loginSettings?.allowRegister && !loginSettings?.allowUsernamePassword) { // TODO: do we need to handle login hints for IDPs here? await redirectUserToSingleIDPIfAvailable(); @@ -193,10 +194,31 @@ export async function sendLoginname(command: SendLoginnameCommand) { loginSettings?.allowRegister && loginSettings?.allowUsernamePassword ) { + let orgToRegisterOn: string | undefined = command.organization; + + if ( + !orgToRegisterOn && + command.loginName && + ORG_SUFFIX_REGEX.test(command.loginName) + ) { + const matched = ORG_SUFFIX_REGEX.exec(command.loginName); + const suffix = matched?.[1] ?? ""; + + // this just returns orgs where the suffix is set as primary domain + const orgs = await getOrgsByDomain(suffix); + const orgToCheckForDiscovery = + orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined; + + const orgLoginSettings = await getLoginSettings(orgToCheckForDiscovery); + if (orgLoginSettings?.allowDomainDiscovery) { + orgToRegisterOn = orgToCheckForDiscovery; + } + } + const params = new URLSearchParams(); - if (command.organization) { - params.set("organization", command.organization); + if (orgToRegisterOn) { + params.set("organization", orgToRegisterOn); } if (command.authRequestId) { params.set("authRequestId", command.authRequestId); From 46074b62f2938caacbcb088d6312c4127d09bb14 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 13 Sep 2024 15:04:14 +0200 Subject: [PATCH 170/640] bold --- apps/login/readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index a8383961bc..fac9db0db3 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -62,19 +62,19 @@ Requests to the APIs made: After a loginname is entered, a `listUsers` request is made using the loginName query to identify already registered users. -If only one user is found, we query `listAuthenticationMethodTypes` to identify future steps. +**USER FOUND:** If only one user is found, we query `listAuthenticationMethodTypes` to identify future steps. If no authentication methods are found, we render an error stating: _User has no available authentication methods._ (exception see below.) Now if only one method is found, we continue with the corresponding step (/password, /passkey/login). If multiple methods are set, we prefer passkeys over any other method, so we redirect to /passkey, second option is IDP, and third is password. If password is the next step, we check `loginSettings.passkeysType` for PasskeysType.ALLOWED, and prompt the user to setup passkeys afterwards. -If no user is found, we check whether registering is allowed using `loginSettings.allowRegister`. +**NO USER FOUND:** If no user is found, we check whether registering is allowed using `loginSettings.allowRegister`. If `loginSettings?.allowUsernamePassword` is not allowed we continue to check for available IDPs. If a single IDP is available, we directly redirect the user to signup. If no single IDP is set, we check for `loginSettings.allowUsernamePassword` and if no organization is set as context, we check whether we can discover a organization from the loginname of the user (using: `getOrgsByDomain`). Then if an organization is found, we check whether domainDiscovery is allowed on it and redirect the user to /register page including the discovered domain or without. If no previous condition is met we throw an error stating the user was not found. -If the outcome after this order produces a no authentication methods found, or user not found, we check whether `loginSettings?.ignoreUnknownUsernames` is set to `true` as in this case we redirect to the /password page regardless (to not leak information about a registered user). +**EXCEPTIONS**If the outcome after this order produces a no authentication methods found, or user not found, we check whether `loginSettings?.ignoreUnknownUsernames` is set to `true` as in this case we redirect to the /password page regardless (to not leak information about a registered user). > NOTE: We ignore `loginSettings.allowExternalIdp` as the information whether IDPs are available comes as response from `getActiveIdentityProviders(org?)`. From 28b50ca64afb4d6c6a088e98a1ebe96382f50fee Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 13 Sep 2024 15:04:54 +0200 Subject: [PATCH 171/640] md --- apps/login/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index fac9db0db3..4537958d5f 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -75,6 +75,6 @@ If no single IDP is set, we check for `loginSettings.allowUsernamePassword` and If no previous condition is met we throw an error stating the user was not found. -**EXCEPTIONS**If the outcome after this order produces a no authentication methods found, or user not found, we check whether `loginSettings?.ignoreUnknownUsernames` is set to `true` as in this case we redirect to the /password page regardless (to not leak information about a registered user). +**EXCEPTIONS:** If the outcome after this order produces a no authentication methods found, or user not found, we check whether `loginSettings?.ignoreUnknownUsernames` is set to `true` as in this case we redirect to the /password page regardless (to not leak information about a registered user). > NOTE: We ignore `loginSettings.allowExternalIdp` as the information whether IDPs are available comes as response from `getActiveIdentityProviders(org?)`. From 409c0d8f58722fa474386e1f539f3968bfa965ac Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 13 Sep 2024 15:33:20 +0200 Subject: [PATCH 172/640] screenshot --- apps/login/readme.md | 2 ++ apps/login/screenshots/loginname.png | Bin 0 -> 114853 bytes 2 files changed, 2 insertions(+) create mode 100644 apps/login/screenshots/loginname.png diff --git a/apps/login/readme.md b/apps/login/readme.md index 4537958d5f..38160b9346 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -45,6 +45,8 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a ### /loginname +[/loginname](./screenshots/loginname.png) + This page shows a loginname field and Identity Providers to login or register. If `loginSettings(org?).allowRegister` is `true`, if will also show a link to jump to /register diff --git a/apps/login/screenshots/loginname.png b/apps/login/screenshots/loginname.png new file mode 100644 index 0000000000000000000000000000000000000000..342e60799e0be3dba78b14c066e5c738885f0ea2 GIT binary patch literal 114853 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 KS-1oB+kXMRX`qq- literal 0 HcmV?d00001 From 19c47dd986877e66b8e7ab5ab571ae4e438a49ef Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 13 Sep 2024 15:33:52 +0200 Subject: [PATCH 173/640] img --- apps/login/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 38160b9346..9765c5da00 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -45,7 +45,7 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a ### /loginname -[/loginname](./screenshots/loginname.png) +![/loginname](./screenshots/loginname.png) This page shows a loginname field and Identity Providers to login or register. If `loginSettings(org?).allowRegister` is `true`, if will also show a link to jump to /register From cad55e70059759be3ad87897d5ed5f1e5df2e86c Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 13 Sep 2024 15:35:01 +0200 Subject: [PATCH 174/640] img --- apps/login/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 9765c5da00..9587fb77b8 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -45,7 +45,7 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a ### /loginname -![/loginname](./screenshots/loginname.png) +/loginame Date: Fri, 13 Sep 2024 15:35:22 +0200 Subject: [PATCH 175/640] img --- apps/login/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 9587fb77b8..b7b26ea251 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -45,7 +45,7 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a ### /loginname -/loginame This page shows a loginname field and Identity Providers to login or register. If `loginSettings(org?).allowRegister` is `true`, if will also show a link to jump to /register From 70a1ca25d9e746cdd54d5b404017988b24be5008 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 16 Sep 2024 11:36:44 +0200 Subject: [PATCH 176/640] password page changes, doc --- apps/login/readme.md | 22 ++++++++++ apps/login/screenshots/password.png | Bin 0 -> 84874 bytes apps/login/src/app/(login)/mfa/set/page.tsx | 10 ++++- .../src/app/(login)/passkey/add/page.tsx | 9 ++-- apps/login/src/app/(login)/password/page.tsx | 8 ++-- apps/login/src/lib/server/loginname.ts | 16 ------- apps/login/src/ui/PasswordForm.tsx | 41 +++++++++--------- 7 files changed, 60 insertions(+), 46 deletions(-) create mode 100644 apps/login/screenshots/password.png diff --git a/apps/login/readme.md b/apps/login/readme.md index b7b26ea251..cd355a5485 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -80,3 +80,25 @@ If no previous condition is met we throw an error stating the user was not found **EXCEPTIONS:** If the outcome after this order produces a no authentication methods found, or user not found, we check whether `loginSettings?.ignoreUnknownUsernames` is set to `true` as in this case we redirect to the /password page regardless (to not leak information about a registered user). > NOTE: We ignore `loginSettings.allowExternalIdp` as the information whether IDPs are available comes as response from `getActiveIdentityProviders(org?)`. + +### /password + +/password + +This page shows a password field to hydrate the current session with password as a factor. +Below the password field, a reset password link is shown which allows to send a reset email. + +Requests to the APIs made: + +- `getLoginSettings(org?)` +- `getBrandingSettings(org?)` +- `listAuthenticationMethodTypes` + +**MFA AVAILABLE:** After the password has been submitted, additional authentication Methods are loaded. +If the user has set up an additional **single** second factor, it is redirected to add the next factor. Depending on the available method he is redirected to `/otp/time-based`,`/otp/sms?`, `/otp/email?` or `/u2f?`. If the user has multiple second factors, he is redirected to `/mfa` to select his preferred method to continue. + +**NO MFA, FORCE MFA:** If no MFA method is available, and the settings force MFA, the user is sent to `/mfa/set` which prompts to setup a second factor. + +**PROMPT PASSKEY** If the settings do not enforce MFA, we check if passkeys are allowed with `loginSettings?.passkeysType === PasskeysType.ALLOWED` and redirect the user to `/passkey/add` if no passkeys are setup. This step can be skipped. + +If none of the previous conditions apply, we continue to sign in. diff --git a/apps/login/screenshots/password.png b/apps/login/screenshots/password.png new file mode 100644 index 0000000000000000000000000000000000000000..05cf8747bb449155446e0a9757139ca37590bda7 GIT binary patch literal 84874 zcmeFZ2RPO5|35AvBS~bhoJx|Fy%i1;DrIKxovdT;5eG>nWD~Nt%uq)5I`*#Yl^G8I z`{>>K9dCWEZ@>TdcU}MMe|=mR4(GM*`+knc^YMJ#=gA#8Nqk%iTr@N^d}%51duV7F zQ1Fe5jRpQAOYeq`hK8GAEGBkGT1<@Y&SOghV>5j;G^r;q!cW~%m?3fBcj^+mDSb1E z$T4gLT?!{2^Ul*V+&9nBJwJQXGXS5^3KQWOfURirB4JDwO$CoWz(*oSNByb94HZ&6 z?)xK{zBy%hvu~uIVi7Jzj3qJB#Ef}ML%kYb`Ll7?aH1_=WKnvCf%e2> zi@d-5;zfc+JZ(xs%_}#R;W?6yj##wglh{%8?EWE~p|KIm;eyg`O%Zf$jbAP|I+(DA zd2SLKJn1TF+HafO8hsXXIVwvVeuYux+*w~`+{Xq&Y2-H^lU!=Xi!iw0cHQ+^nEr{- zuI474dRdOxIiic2QQWMsNFpCsDhC=y2AxPumCEt1YmL}py|w4X?&*e2&csqUS1W4QffDglM($W2Wx;@h6a zIoxKa)oO3Q!aP+?xJ=KB)9P(K<_sfxh^15`IgYhO?|iZnZ?h(Q>@Kk=W8Cx=q34$Z zp6DdryknIr{v4x_;(`BFi3bU@cUn^Iy3OyinvkuN2T3S&oG!RDfKTlnEWX+vG2Jt* zW5ZX9JM3;DHVWO0muw$p*MpPPp-%>kYw55_ze;|Eew*T zUoM3Sl+y~IVXc`Oms#Q3THR9HBHd#5yk6};b_@H$GENgt6pkNui+7awi5jxIK1Mff z7@%hr{7s+ef8d|cpU|BUz{JFK!6fzhj(gMRq0e=nNnEA#+r+|QS1wwgiz;|}5n@hH zeWCCv&C`+4Mm^m;Qoi78mn`X(!dOFNpNTNuJD*9+7vTJnE8u(pen2s?CKKUv_;cl3 zfiE6Ezx6`y>hOIE#miSHnetz}`;`8I>3Q3gQ6-uO@o)RydZ#9(6Q_5lK2#u28oN?Q ztce@@OrB5bo%l>jLMqmM?iZ5kl0xDYx2fZ_jW2VaVfIlJ7l6(s1*P7*Pkle}et4Q< zYK+1{a^iHI%^9Jf$_Q6a*N{E^mmAj**Qzsu@@EZ(BiHZBDzwPR#wn&MX4@$(1oDdw zD+xVtNq0=#Cc-c9eOKokluY5o#bSEew3Bm~(;?C)l7W-fq+0c>s^bf>_&REe|*;X!`c_Vg}wwq*wEXFdWq4{1t@Kh zEIcuYhm41UATl-L(e1~#O=M}Jp2-Tya>z2tF1^xjd1a7h;M82&tZgm1s<(=}8by-h z6Xw&d6C$x`)@&BupV0pze@X4xHIl*00zoT6qbJ z-Pmie)6do^-imYHb-{JvcVVmyt^86ck6c4O+ciN#cd3y|Tg4b!=$`0BSVL1r*09$m ztx|E;V{hLV(cQZPyW`NFueGWr{b=_<*DO0 zc+ecA9Hgvmra;BQa&__1&@}oerU1$5Yc>y+bCyTw!z7wW)YUR_dCgbeUAiSxA|qF) zJCQhPI#KmltmMT|2ZmhDqBkK;RB_m5z|zOjR@!qVc(*WE&kYmFoI4dZ6jpQz|B}1Gbh0Z+xk|Ip0dkHa6VYOK=v@2A+;kb}Fm{O5#G#ZYqK?I_CA`fU0ybo=*1_?VtN8aYVEi##!ou$Z(Y$<${nPE3QU6ZI@ z?;On4qk^DLP3a}2K2@(@uenFHzgj_bgF#jPx;!HKI)C8~=emvd$?lwT?IiYhxy6h{ zX*;*aA`k|}U%RT5k&i!TjPwUszn$WaWr%4L5plgRRbv!bqQ9ln`Ed_%DKmuSZZrH)TY)N8d?p@Wgt0h5|;qr|EFCKz?+F+qiE^vp3 zTjJlPXB0A1UQ|UO6-Vc~owC|evL{ux@+FbwdrDP?$UR%SeZKDwGkC+KJzjQRhJL%g zr5Y?SzU5oHCT1mL1TRGFs<>TTX63U*1fV)ltP!aZconm2%HCpwliy}$Vtk^!NRk`b zj`Ef?QlZE2>(mBoho%Oc85yvny3sPPwz?U&6jI++U;ptTj{Rd|(F?c?m(iCuu&*HOQ`P?()TVdgX{Mh-&eL#mc7ldYiZ7+t!JsD&jK@lgt`uzAdDZpG}pJ$rh}QA zSy=PKgy;{B;0Lc!|7NA9J2=F~RES<#_70tx1`Ph(^osW-?mF+s~_3O;w2xe;s3ma`1vxPO}$3=c# zM_k`p_p$LK8)HigI@EQwbu4Xdgy`u}H~RbQ$8+k#jQ_clh4o*<0s~}4y~E1R!p8dd zwZW-^sQ>ccF^1`zsfZh!gE<5D5N2oN;1oPK;a_k4bIV^&RsQuI)rZj8}gAM!n9Buw@egrXz|bEq>!Z?8N-=siBJ!?>5E%9gHZ0Hs}ojV#j_OCzQx8t6LpI&HRttLEjF;RCd@1-M0gFjDag4&|}j1Vo=p$ML~{_hQbou~g!(GTnUuND2`ZU1$&A5rPQq2j-x;=c)p&i()AexQf9f5xmY zhuv%_wTfHc&3vGs5&1#I<)L|a|8Sm$X(U(nI*-LrNu1qEhmdG>f?PHtdWJPd+4)*u zM3-68e(2=xj(hGMCnZ5SXB+<6oNnjc(Av7TVdq^K%}Q>qmw;v0qk4_}`qC<)?5)xn z>Y`Iqh1O$dWMm~nrYJ)wG#NrxPFidfp0b!_-{AbDI}iEvv3FR6J6iS|-^BO9`mOJ? zA&D{2E<%&c$*C*byt5|;2FTkkSElW?NeZTO3%l7oap@6JtQZqA=J$eqW}%t1N-#|C z&DGdt-!#L6Nu+F%MbFPEXU9e(dDp2I1RIeUg{>1F{4Qow&J=@n`RO9iHkjhd`jwEC zwM;{#t)Sj6EK{T2Q7L34?1S9IoCS@p=Elhn+YP#cw=zETuGbHQH76tJ=9nvKzU02J z%&6Dps>oWJo(kJ$2#ocw-xmF_gclOEFGlH?JIiLVTf$~lDDT|t0- z7kcNW2%Xx5oVx@j1j6b!+7f#NCORhe=tXSiG`-A9bO;s%>k@56MpjaD>$ghxl%3AG zjTJbW?F#0mY$-3LDf&+hF6z#w@6^M%b5rcBY(E7z%OJ4W3|G%?eg}={J`u%7`$@5ej$W zdZ=45g{BNoid^(P87>At>9EWC`(wehB9;2I^S>l~6l%vV995*ODYc!edHcY$=FY9{2Q_=?XO8WJqZbEq;vLr;4@Y`@PmWH(4%*!fjvjg!f31Hnv`?>jN5pU{8Sv9-Dge2V0n+oujN`-C_f!DA_>g?w6R)y00OT4|??vz6N0+f*|# zeVXxUHfle|(N2s~5v(ll3u;ptkuhmTj0o)!UICd3=!MdPy(EXmdxqhy?^OP4^@z>=s^ zT*-aIbTy#9E5&+2)%aws$DwM^Lwsz+X{l-Pe&Q4Qe1n$M;fuQI+ZyQ4_$88K&cg4T z@a~35ixCzo7h8zgk+*EEOxDN=ER0n>xq4`I+(jAD-M%*!_R|{4NAtfk?mt-*de2v{ z+v^-0{z+j^jxt~xj>uAvMT&8|YCBeiuWHWH0c?bZxL837i0FI8ZCuv99~xJ(+{<3eeuU< z)3ej5?G^g$1|1SdCn;g6H7RCr7@eN_d9y>UwJ>U4jT4@->fpn0vcZrg@V@ z@aO!eea^_S-SanQ3kvY2HzDdJyFfvWD(E z1NcxIlV&pVF*K_yhi{1QVP^dAX)9Sr+Ld`DhGnA^ z^F~_RX4)97qz@P1HzY?GKiI9@t@dY0?sq9x|2Fc;_0ZIyd7MMtwDdVt?KJ;{?g-;~ z^YC7RomEV1=(nJ41U(kD!zurT2?z)D+lRi8!*^vGYa#0(KaVdMP{g#UU_ma)K*nfH zrP~l{zg3{_OtW+=e`m3otZ`LWIh7c#)?t^pUR~)7fP!;kWr6<% zNsTGvvMs@~=WmL>VpmJW(Ci!z8;>zm!A<*#4{ka|WWgtSiHuSEn(q5hd!s>?N`pG7 z7^*lk=c!Fk$issxFL$A2>>-^78avh#iW{;9Oe@{a5$@&m``5dX~yGcQVd>?NDtl;y? zu{ZU>bSO>ygD6h2yG{o1Bojf&Ly!Cxg0deKLil|{v-ya#aOF_pDvR@uNq*)%m$4w} zirILDAE7lgPVyxt5}qo{HBE>h)GECocSdw>ObplT<71K@!);OTN)ABk-fg)p9VWk&E8G6(2_au!O>YSD71sP z-|w(LvbN8WL$<}eAxLs1&(OUR?sW>Wpf}Xg)uv&H32OuRMI%9pL+gI^O1nuAVf0(l zM=-9P+<=1IrE%Lzv}&o!yYi|Z?&ub@mUA1+wypO9n3OL`qDd_q=5lEDMIBJqI`l3- zkM1HqSLf8q=?c{$Qrf_RC*}^;wt`FSF>oG>IU~ZCVbvaC?GI{5;NYUG02g8UpR4UF76?rsO37zFUU z>*GxHV8!*Vx^iSLb;ttFS$M%z&)qrBA)_9T-a165mYzapBBs;)z*~XYL#u*@ehD|p z6h6(_sU@CW@w7HQ&Ajxyaahrm!;iJ?Wj1?1ZaZC=TL{7d?NC;4Sd#Cd>3z<65d5MK z%tHFWEY_!i^(OAD>1Bg)%T;g}y~KhvldTY^T{x7$dd{MP0Xe&F62;_1G-kDbO6O!G zIAih08M9tsaV{QN9On}T@aG~}5^Vrcr`{)BKt0?S@Ni`C4AsIc#K$)|TGbXBN{1)- z1ja2Atssy>EfmSqyn#ImZREMxxnjS8%kt z4eP1ylt@Mk!BbTAqR z^eqR8gW*lKXAIq5Nba%a(zSs;)zeVLOw}mcYKr5Ajv!-nI~@>nqFU;18p{Cc2A}>9 zZt#lz7_)s(@tXmIb;CHiZiHHOQnq#UOSeJV_jb$~#iG&(*Pv7jtj6Ym3f?%9qiivh zb(8pYL+ye8INTKKvpjhwTP8awv&W0jqIZt)hyz`PyoZN=IxVsIOXA8_}D| z7#+|Ccm=DaD3znR3s3mO!BJ>P>n6OCM2QJ?MF2AnjUDgTnK5YLUpRjZ4r1X91 zH^s4`kDruUYJoM71Zx0d(c>eB&f>7?COPVM&cd;ws*P(o7T{3R-yI6Bx?Y#mLrw@U zcs8Rw4A!a_tQCZ8<#Xy0S0@4{bIhSKUZ6~pBY9k=5pr}sN>TG6jNu~>Je}>q6u$ZU z@%7LDDGUL_`Bon{O@0?m#@A3Xcb(uBjIk?u|&Y7oa*0XvyWvQ|72cy0^HyhjXU=ldA3e0#S~f8TuuHpSHOWU9&z9=cwmip*{?Wa zVL&93ON|V{8Z&`4{-~w*<(B%bTHpkbBXJiO9XNr{-79uyP>80~L?4{Oad3(^z@`88 zd0?H#xD0SGIHCO0BbHBn0zsD6s{9$`N~xGT1RI0EmOK9s0-Ivi!!X<2N8^is6#paH z@0F=pDwntqt~qCR#3I7gQda4~YA7de_zzD8GJxT=Zy3;Zw9CLig4GNuwON;=#{TXLns|r{*3rLeBUZG}nQs$wdK6om+AM%fc$LGwz zmhvRywpMVU0cy5GoxnjQ|2R;8L?P>AqGIzOE!y!@6RM?Wo*5`x$OW}K)_XXPHEVE| zECS#K2I<35_lhR)gHNl&?5@(0HS)}s`hZ0~0~VS492^)B z$C1UoF9ATx@keK*4QKFpO8~Hzc;uw#EFjS*6WkaF^(Eu&9tSa0!u%Qcis#n%mc&6C2bIazM&OZ!2mIn!*6Z^W}X(zd~&t_wpP$_`IO^WTcChHQlgL# zJraZPQ8B0v)D(E0q4c-Zi*kV(L>~ZWaThT8|I;W9{P8?fulF4_J-vY%K2fW`M z{kv*F`Pj_C^_$uuj|*xN2*0LUP}Y0?Z~{m3c!tVl$N0}5uLD4%|9A5JI)ERMXTgYd zk3_BeH$kX-LFRjt0aWn*X=PAg(T*sK266rWWSL8tg8rZ&`-3GIkYc;1rkhmMfF0fm zdv?TazM&RikBnI8q-SWtJ@tZo5Mo{&)DeUTvX#?EFxmq`fKZP~9ltGvzzmTG3=x5f z_0nvm2XILmkUA6zpkT0)N2^abs#Z0)@>m`qmGIOZEYy60>JvOZssOrh*mUBq0`Qym zN$PX#DHPfB4)I*Nb|zb&Ltq~uHN{oP1z!r*zxW?5 zHUO)ylTB$*`Di7P2JpFnGR}1IFW??X0)};fNIhI}t~mEuRv~LN_qTg}#dc@p-Ecuy z##GF_ofVO(Mp`srlI~l(Shc@5{;i4CGSxhdbz|6m^wD~cQZaG`^xa}HZw*>jx%8Q4 zSJYhaqdNE5fO&B770*Fm=yV0(BI;U|K*JPG7vTD(rvRaV!fe1hH0PDphaV7PO;+p6 zrx)7WYcsOQQP^aVcJZ*C>05$4w)yIY7^g+@tcs0+m{3~V?z`i%mW~Rrv6HlY`NA{U zD56uV1ek>0Je;Sx0j=&6iqtwv@WiFIP(ogKbBmLnjQl+tm?(jygZzxLK?#%%in;-+etT|Eb_t0KwUOg6Y&}h?0n_GP zYZoiylNbp^u~uw)s0p{jdVP4TgzMFyE;*<`+eJWkVXxnAFrO6u`cVzect?nOpTIZvl2TFxz>D08_?S3^+7-D!$DDCJE$gQ)$%gt!6`EA~_M{+RG)r{oU2t)tJrlmplC) z2Xvb#F^C6O?jj{ULr=g%fqRRgc#dDoRndJQaNRZ>G;gcGhEDJqVKrLSQK`?iN~N~FrlReAfp;+7juGVBxghMex65YO`kDj1+% z2I!>&oB1U#$L*toB_-B+Z9n2LJ`u=j@y^%BwOn{TO}>TXjL_DY7oJN_xz^_;w|%Gf zYku-BvkLvu%AInyg2NRO9CR^jNeszlLsO`Qs=X^o{bC`C*Gh+X!XH9Gat+y&?Rs7q zIV10v-zHo(gOquB4QFSeSQFM2HA1{-U$w)vuvE~VttCFS`YgQs;*Q|PkPY6bq3~)% z(R^j)w@*y9Q^K3?9(72N`hcEvwRvJcqrAf>g}h1gigkoVRC&VQ;EnpNS*0q9%C%%x zF0Ni>kqN>O?489jOV}4C%?O`IvO!l4!qA#HNb*A3R8kCJ7PN}~o&~*C3P_X*2b2W% z7w<70CR$wWk4SE(mMwr8b}Z<#I;p_+cSdOqnqRTcszLFf1^(04u{%RKX8n10ciLNf z%fE7s4==gebJxZ_n2g>1%rGA3b;hKRnX<*vVM<_>Tux}c+t(#iWBLqRJ(czC^kMU! z9dbC(m6&V$QQH~u$A*K4x7Z5f?#>7za9#Eq{~RVS*OVx%1Y7yY>dG#-ksCr?7(JX8 zae<$~$3lXedJ0$+GqLaanGo&+6Al7Vr*r!57bP$>GHOtB`TgK}|Hk9ntnJ2Z3H$p9 z`WS{7n2sbv+DBpAs6I)d*}Kf6OPCM>r@q>nct0A75fBs#oMVf{L>gE@9AdK=;bZ_I)C&rY50rirRHZ5_66)^w&uWb=3_*02mH{zw4Qs%PC zSnyoSxo%PLo2=Zt@ZxshL)W#6B^OpIm$eSrd=FKd;X8`F$^GI)mv0-Tt{x~5;sk&n z+D!2@*aP1d9sDbf1JDm@kRN@JAETfxF^q9AXd>!%JYTj@q_#ClyU#nD5loAmmXzh- z(n--^JGa>{iFrA)F>bPOfOAeX+!hu^3WqHiw7w?a7%ms!Nu~2c){;my{ZF&q%=TClHyry1Pgn zF1#3+eHHvb13V^`K9`3ck__*3x@H>>uMUfW%zGxzq+~tR*a$_Eu!s@y$;FIiS<}&gi~y+37kw>B?^}vb}2wM275MRA`ji7$bVA z?T0#YG{?gYJOtO`3CvUHs+bE}o0~NEzWJ6`hV*JUnUlnE&W~Xpgz|cT`(|EQj9CIv z2->@!DF+Khbo{;E7Qkw0-I{f2h*-+z^L)d)Vkg{?bDC|7W}P_@hn46hX9mIVjq-9= zqhej>b!qXkVIP?yXA?0Ys&s4lu}HHf+gq-iWuutLr$tfdPfrpEw!a*fG1h@($|tOI z>d>8=TUkk@MH&@N8#+_dDrIY^6PW2_M%RNGp^#aUyP;vU;zhelNA^1EJ;K3&ViqHI@?~LlC7Az{XSN^H!*QDcywLX-6xZ!p^TjhepBLBB9 ztZsZ|lQP>@Yp%mR;>9r8V#R8IgD2%6{o1;9r!4hmgrdyOn6Zo~(15ouvj_p z8l`+&PAAAPXRxKy;9%#ylCP1bc``3L6Dny-^`P3fM&6WlUdfOy22qw3Nb8uGR}S{V z#EGFC7+a}k5F3i+4v75&r1wkLMsyz&sA6b#$4>ENPR7&LZ-+kA>q=&En$MN&iacA| z)F{mm-QDg(3O6zcu!vUETQx|XepwoMLWhTy%(w?psg!LS(XunLS?Q}jid?j>C5l=0 z_Ei;N#Ug}XVau`K=wN;MD1)r{@x*UD zL9+C(8qZ#2+y$#>raCCWA-&OW7@Krn{y^Qqe;yzqxi47z!oWpe{o-w zoOtFDr6i>nYb`X`I_1VzpU68W%si$OSgoPqSX!s^B($vauMmXJ`CTcw#lu|bR7Y-@ zlzcaKE{~qM&a@Z*-9!LWO)c=zZKv23xS@9-OKIw!syrv9ak#pjaca;;jqBwWiq6dF z{(}mjloVjvO<%8B4`6Ve{}|jg&C08DIx7>HKMRTNi|ul33FK$xohOJWTa%*6saAqU zzMLB{+^6m^XZr@MIo%lQwdkL)etjNc&Nk8V7S>xfa(-m@E6EStq*EE65%pJ zQ+#b{u~U^hiEA{lc37+J@%tugw017#jg~#(X^w#tyimpY=)s5#0GJM=Xw=%NZng zPw9mPv(l(0I5|h;NP-W4VT|}tDS)n{014^Z_v==960&l>b!1EEr;JqBE|y`f;1pc6 z3P}%NI1P4wnNej5bhf@JJ6Ew9y<`THaeQ|#eMzU5k{=fD9=-zWPfkdkEeVb-0H*j z{aN^Ye}UEvCh5bo!Ghl!O7HT>+st$-#BO)LVF4;O5yQY_6TiKRh2eM5e5!Tf z7L-r;(DFhtPTeV>l+5(Ikm<>>v$RUDn>EO1x&bYvw)#Lg9;msiqu7~HiUN400#22B zjnCewcGRFSsc}j)iA|X%H>x!7SOhgpXT^A&Baq6u+l21Zp*Zx@pcvLe^QyamnrcbW z;>MoRGj^+sya^E(7-F3ti*>Et^Y^9ZV=ZsuFe4LV>#Wie=MW}#?ic8!vTkO-5q|rl z?0!rPoAym-6}N(Lp&onqx6c43;j_A~1X8L_uk%g44(ll|G~`VDLOHqxt#yJO6h3!i zP%SjW(Zx_4=)n28xC)SmSOD1wm<`2@td?H=Ai}l-GPhmcqlu;L8Wn8J1K+Pb;{OGfTRryyvQ?h(_h#hv2%&h)%JBigv@4cB~EDXr>0WOyDu zbEZKiIy18-2rB%&AHxI=u>3WeCR8g);T`t~=$bBN{%Gcyn8Le@J_c?s z|J4C*Kjony$M+E#n$eHpNA=j>=>sZokDt$mNz@7h^ta$rVJk*KfzUAGb~nK6YZ-Z~ zw1We61c_C#4dA}Bz!NR=Ie0p`OF4qjbE7OP`I!AeoIgWUV(~wc{GfI|o>$_Wcj-yj zI4=yaC{Ood$z<^Sb*8c_^QdBF`Sp}wN<8xqo(YQ;bfK!F>>b5DLEapI#R3;92JC_P zMKYBawz;qs^lYXdB%Tm)yFR|^^1W*RVVg!z7ImY-nTnnHeEF_OB2Wk^^DdRILMxJ> zB5)LHK84(pSmV0r(7$%bsq#le7e^Kyp1Pv$^s& zcrTE*hj&Els+v>OhRN}#&D^7E;l2Bc8{p?R#k0=AB70lDr30F*++^i;PvO>^VY?BB zskp&{0Zfq|patY3b67Cdq&y?MrPZYDmf25@8)E^gn>fsSwm6@R20{3Isjk7H8db7n z72@=8Yn;H1OcOy{h-d)h5ZM~F3=i|??F7<_E4v^W$Tjt^9#q?BBy;bkFU#%}-kDPy z-A23-)o+R98b`qs?vt007N*=`B*8rk@&5@c{GtvKtw%@Mh_xH>V5@DE;&AethBqG- z3jIye7u-*tX+Q**AU6i148oI^-c2V2)^x>e6cpTdlVj_of_GavE{mJ`!SY2_Z8PsI zoFIUSrVp#I6#)QgX#cb$nQ!CqtF1b~Z3qBBQH4XqAO}x=Q?EF#W|sP17t~ZuETg51 zrE@hXlBpzp=B~X$=j}^Ba|yuIPPI4IMcJ;S8UI!K0-`yA3(DPtARE6&(e{H9%6gxn zlCQeQw!Yn&%r+I{WYK33WnMlk%hiJ3Vdk}2xmh8*6F_WZ?9H+QtK*32$m6S)a`Pk% zDr8A6-A68sc2@QTgE}UB9KQ8~agi5hNjww*)%p4D7-OMG-C({qGo9&o3AF57 z7p-HPT6>>69EjT6;{g@M@69xB4(fB!p9IXWEnR-Xc74p5XyI?$?VfMzsqG(<@mM_6 z=z4>UB!BcdRFa#yxxbr@aoX=4OLCv<&O(^Us)A?e2o~gVuPRHVaYxIWCfn||2*Vha z5=u;MfFDxwmTryn^iY^YKUJ#Q?it@*ZL^#*AI}YOF>8+w-WYY;FB*~5JxInKsIX%b zdv)Ow;606g@}5vnRN4KbgUnB;42nCuDbmc$_Swf+$U8j=5te|Xss$LlO74t;TS%}K zLd6hs_?c~i3_SB5!_UtGpjfNjMPZUsy_AL`?OI>6;ea)cRD#S)clB1$6BNVo)E&OO zxDgIZ8YDM-9hzwzicl5m9k|B*=+4Rm=nyUZX zgA1TLTz|Cx<_$3IeH}8~XpCym(paQGNjX~%5ROU~kcH)fhVs9qE##0dAvZcWfN5DI~s?%y4KkbaNXe;#}E@%{hgYHVy}Y2`aU7)x|fc0BynD z*}NctRTh89_-4I8r{@oZc|~Tc3y$H}RK|`IX96kh9|%DIH>6HvC=n0Q(a7+7rSZYe zB|yio{fVmZ&!onG(Afs%-``~QL9Ko8<1I?9d#o%CC;;Ej7Nh}-aQzQzlm89dM7eI>3l-8oo&LX=5IvlqOvpd`JUsq` zmhfj)^N%|I&hIsmpql&>G2S0s8|W{-zx_k~#ox8!0nWK-Eir zoK_5=hP>-@I*reKezJpyoaNYk4xrQnp~3KIng~GcLb!hJ`_v7#XO`Wzcp#}z|0oQP zKN=(z{fpn)ytFQkrp}Q7hJ*hv)yT0Z257VX;OPGy%!87u{u?0ydwdbvAVPrJH<+b{ zN`~zq8A1R-4|azhyQc-jwPVEOzg1!aLDnBkBlm1z4|Xl$T^~!V${*qdS0KWV(&o znI9wMRPY9DTJ%6i-v^XuEl11>JmU+B*?%;fmO=s}l?B!ZE(Rtnm%|s;-(>oy$or`# zBHKZa#(;p9%v5cSPes%X`^HP*-d%D6%=2YSmE$PByif8A5lSv!4MP3p3^UO+b!D<1uoQjT-P{X{yG#3f zOOL<|or4$PNjhPuVuKw~?(uLmbhYvWxyxCUKq2F&ngCKQWgZkdKo4@O3!u-^Iv zr4MB@^57(M0Hq?O9PhaIjo6#qE&EQRJ2ce|@4xVky}T;QmX{oSEf((t#y8M80-A4r zz~W%Bp&a9Y%0=y%l2LZ0 z@L*Vm!waF<(9-kQmQlNoEK%Cfe-x{5TMr8_!r`8;c}*-$EZH5ytOdyRY&U!*4F~;= z-p`F;`97)y!dqit@A?o{)(a*j-$9Eq=5o+^$9HtC+8Jgo-&@blE(S&A9eaGss-5@hi4ql;p^!|kQr{|2hD##TS5sRuVQQ9LDj{S|(pqA1sL9qutN}ADy z4((nv#KAyGz@}Z}z3ns+hT?Lyhf8g5#7+Vkz^zyOwkAV8Yk>0DW8Ve5VB4&`+jbY_ z;!r;5%$vowe)pxczy|yIKRqn)UpPs-_5Mxe{gUMxJdSXO)vrDbLNn49Q7BQ!`1V|O z+lCO(J7ltIFv(ZA4V75+JL$WjdcT3JmAt#iB{zbu6nO#^i`ZULFi@V`^KKt-FxX(s zOV_hc367l@4-IkF>jONEsx1+UD_c$xK{7tHAGYnc7sH!3p|Y7bFY2#ziQPk{FZFQs zJ`9U4E-%Oms=KvRs1~}RI<^VA>UJB7F1<7>{*rwbdA<9aZu{ZU=`7?sZTq|O#j;eU z>Z)(bs**;_@F}_VgjZH)tC(#Tw}XY}wF&UTF3XzCa;VPjtOJb?N9yPSQ30mK3dPBw z#2}Pw(Rs1zx^HuzE`9sbl#k^19mwA@A>zOUc7YCDfn31rHfX!#{!|qGLrwtSYX>bL z;6!ZYUzjZ$>3|-i%_rOW3)8s3=yy3H_|0G-;}|~ZRu?VxR_)oMgctqYEX>pO=?I!9 zQF|iaJ@HtuX?ObSb<)P~inPk}eR;k-c1olqK8rRB_l}Ky@MMUHu;BjpHiFV<D8tZ0T+%xQ{g61_bGV88?)IELC<0C4=y#?ZMSrJ9#h4^{Md66ZzAtQ*CVq#JebK zb5=W$)SxKJ?Kv{|hdY;BXZ`Wlbk_lo6at>h_DKlKddHi#RS+C1_8`3GTfjp^$`{Nn zn{Jt8tFR4ls(k@!56g{*ZiBiZLN(yseKW)-6n$p~^`G4y!lmZqptq<1+sYvtA|vMq zlGH{bug`?oMr%@~plCpWHW6SB>yg`q_DuXMABgf*2v%N2<|dNUqnP*h(>Uf7k`Z-- zoTf!S^PjV1$NTno-1b=k!FPEVB$2jCp0`ZiYI%CnM#U}^HZE(4wD^E#4k=ByjEs-J zbJ-%Yn%Gd|Lu6%{B(00h#=Rm-=Sj-^DU>{+cH-mpu6cZ)%X#;BNsHxOzlGtS&@joY ztXP^nCA{nr_g-ueG?Iu=il+KS&pPpZX=225>^qwb9O&$A!5e5E7tVt2aV|&0ubB!& z*i-^gu%LE7yjLcX@*+f*1c;QEwU*&l*A-kZv}lD={+!`3Xl@uVcC6Y< zQWLpc$T$T178d(jsCTqeB%anB%WH3K$2QwZeM1=8*M0lRmaeLVI>~AcX(iiWHtiTn z8UtoMuv+TcwDoKrhWf2|nTN#PaG>s(3>ILS?!+<2fWxL;+@}CXF05P7Gy$cq!m;ym) zzc)xuG|jJo5B|i9-TJ_gH-%D0=8>~*-`<9=IDG@{2nMwV-qxF)dw}^Cz_VF{`dFbt z``m@7LPbiugVpiP%P*>;zM`F3?`1cnt8 z1$^2?l~PRYG>s1J%3Tpo5s+K-S#9~`A9h{DhZ18M8HklPU#he(sJ9{U*c4Nl^PT=i zWsd7R^1apGplv--C7i_AX(dpTO5@wJaJ{y!s+m=$D*}h=f_Oyb1iC!qCLRa-Y7GyiO*Q9Hk8kJjX7uQZj+xL4=m|C@? znyURfoO7Q+@NlaLS@SbVCx=9;#M5pJa3)HG6T_kqw*KKc)ADPR$sERPI;`5u+qqoo zS@BJbHW8lOm_jqnXXHpyG zO4a@q4=>G?zaI^NXO?!HzR>caorKk@QRDM2v#YSWMpB_}fpNzOh-BPOSrO-OttWU5 zBvy;<2Im%LE~#wq)!V9Yu3loCi^zffm zCHv0kUt;H--nd7hZV=xD_DprIns61(D&%_*lNfhhx?U=9C*2&#&--g~aLF`~vr2{> z`kfJ!sxspFmJ{=I>xpCH;b_L(a;0|gjlw~VCFx$Tj?-sapq+0bgmEb*5~@m{ON6`6 zoJ%N?gD;v8F?Y4Sl~J0|Ntwxr&6xabX`GWFQ!{b1Mj|uNH5(gx&L)Q5{rw#r@F6FC zJaCol(z2(5wM69ZN+lVTg7^aOAMt+jJ}s< zROxZMi8GAj9}KrZqbkE!o@H!NE2v+POdsSo-3S%-D&k}V(IROyooq}mI--gYPIguU z4744a4M^Hga~+Yi18E#1;8s0yS=dNC35Po%S#9x{ij&DC5(|?d2nz%Fv~p?r4UGX# z-Omwi&ITwEZeBnBteWXvv%((5eplps#OYJ!Gs*NS)w|3URgiGCrUM9dkj=3J*3Oj3l_I9Aa`F&0zArnfJY&6zv9t@U3h=d)^&OBm9e2O zS=1xdZ(W$OiEm9<1V{pftuQc22i9~o@bqGUvcBIGlSfsHYq$VQ;`N1iwU#1PjtL-c z&SDjg)W}Pq9o_zasC(<6EVnQ2oAw5zK}zWo1#bjFq`L)aDQOU-rMtURkQ9(kNokQ% z6iF!o>28pccVBqIxle!Whl|5!DI)1Mnu+e&X6@LmHTcRXw1 zPBq69sm>fzkSRCN<<#htfPY<90#o|it>-To(wjvHvTEB1YVR~pfQLiT+Q@*gCYX_H zH&Y*HJVe|MtZxf;;u&*Ntlzh6AhK#GHlIl3r|>-89yMYpSRO8`ZxGQueNeJk?<(zb z6l6BmgDu|wPVp(fMzcr_LK8T2RqTr^53o3<1D<>N@f%eRl3*+8k>u!bR%cQR^5QcP zmuWWBhHYSE>k3mxT+s@z6diPIm@i9P?aw1o&~{nQ*&n}R{>X(pdRX#Pv1{Wxd-{=V zwP5K%jw7=fBZ(C8qamMCOk&PbPlq>HT!uBY!!}@FCHxG+TJ;|S4vwF#9e}@oW zs1zLFF<)p@*lC~M(6KW1_dw|IXWnOA2gs_1L(1UO%X{ae^}NDoYgHpU``V3{#XSHc zYLXZ@$WzMqc>m2VIL=}wk7mfR-*r}9^X7CY+P$LNeFmL2k^oij zl6=uDspHi}j$sS$=Ntpj{1?1c_DL@gsHz$Y^bCkS ztP1D`NdUD?hDPKNBqD!WGFt8a7YIxrbb_pwOaOwq1}tjynt@@7j1h;OL}GTkT+Pid zKvPngICl8xeE-p?-K5#W#A}Z;6}7MAxU_kD}yWP8=aLd;kc^a;AAu0(1laK*ZCRXkebcw7QA0B34Ci^>(p{f zer=f+_zeL$DdonVOirordE#~x}tGQJTr0f1OLfdhK%ml z+_$xE!HU8G6R-+)ET|b8oQ`P)E0DX+=b?Hk9 zIn9u{mADPIGz_&$%%_hfC-w{;3Mk{;Tgp)Hp|~KJ5$B5rUD^x`2MIAvK+l~7hEOW_l% ztI%pQXMfBay@Nru=TRkN@+4c}ctDJQ()Sfpu0i2GFUPoud0_h}4CCG_3hBb#5fc?% zd=uv-xJ#rl;`OZ~p!7(b{Lb0P6FRnJOOJ}jDce8kzV{60?li)kLaX)a(&q95(Z!P2 zQ%endjS-b${hZ4_`L-iYVb$6;Z;6CvGFcx63q>7|%xh!N^dnq9h}Br^B?;dAWTakC z8-*ldaWBB~f%~_az(DtdQASN=RcalV;Mk^6j2XjRxy;BW-zN|(mOyiXcGCTVcK%fY z+*1`q>~03xxF-Q!fP%H-QJb~}SY}|p(o17%k>}JgCmpP0OH-Kj*%Fnigin@i6C5H6 zA5zsD1YKbYXV(jP9uZOaF6b$b?q;WJH2;{zz)ENR71kjfkI8;ctPG9)QYtZ_-o534 zN`;q8$ybiwYb1&!N-wKBxD&WGIE;@U9v)YHPZ4J4c&bBINu!$j+}GH!TTSmPXq{>I zy3cWIA;k*YZt(*a5YfH2kVgg}itbI&7zV7YD?hKS-1DSQ9e;N8fV&}SjEi_?m9Bp_ zi0mEa9#tgLK&91OS)8}KXa*DJei1hqK+3qk@>oBqoOHjTe9a*~p8LY$QbPyY+ORz4 zIJyaqrS(Ou6~_MaHv^%4FFPQJREw+{CNoBET|fxlEK1WoH%i|d2QLlqK`E1$&ux1W zj)cz7@?qlDOEdFtpa;>+zBu^kK|IZ02cMr59&jzBdaQyC$t*Ee`xGS>vU++e4N>J# zJF|g$W{vjvA1x=mnV}vkJue2FEKz8~DcqeQ;Uv}nP09SXEt3FB`up}Ne=_brmD}z? z<+k7c?<`WFoJw7*GtB&dC_1864FOhpg>=+T9t0XUf6+%9LF2!M$K>JW z6a7{7TFom^tc(6ptozd-{~wf7AJ#t=8-APS%DVG^UX=%B)zO~+SXOV7Y{oayRE>MmCZ7Kz3;5X&cfBWB{F#nqtRKNj%QtW?dDDWSi z8}K@lC0v7+Bd_~@hYzdG!{zdADVeGHfL$!FZX zkB1%MDL!Y59z|{pAAYEodAx0AJN&n2_eZ`#^*^{x*7sx*AUz-i-3SyNQ+F*v8Cnb~ zL+foWWuVYrE3Z1t1-4(jcxLi?qH=cWLNlvE ztYCWEc}I2hky5T~w{UDhI{PAMVM+}}A#RvJ!3I|q^FRRz9H_747qcmXmOG`5kVNH8 zCpGSpI%rp*BIJ1;SE@!Tmh&7vgJS=s_i=3NHbF<=cGdaTkmqT|MO1P*-Rt{ns~6wL z%Ggq*@*euH4lm#8y{$EckQHS(_9}n$5f%?5M2W`-Wdmuf&OAmD_-$r+D+h>x9J+uA zATJFDPFYCse1i2+6AfrRQ*l69OnS*C?%KZRH5xldI$juTp zOCB~d6zM3Pm=7f%8zmB$WrFq!18rfX1L(t{`u*76h#25yy*_JGgy?3=w9r@ql=6_` zN3$u=43RxMKyVZZLo|U!Gp(2?%kI;inPZ$`5I}OAURuqqGJn%Sbfc{U^zF|bqvr%C zlJB%N2aG-`X6E68UmA`K2HRq7s-Dr!nvEnxj+@z%sHNQF^B8*t_i$Mk@TeIpSc~l0 zzF=^r@%ua}OEjwPVNO>W$8#3T-L_i@yGeC@`PJQ5(B(uH;% zR40KhAG9=4NN8CZOaIEEU0=jMn0XCP$^=Htnl1( z0z(eO@p^5;nt|Kw#twbRRzgsm^BEsyc+fZsh~g@$BZiXKW|#1LA66Qpow){qVC>)7 z#V&UlbH^M|dy51DA!Op(&8D1D&$yMAWa=z|^3Zdk0#YjSje7Q?=GBstH&LpTveez} z7S-qk)z8v<;g5$R?p??}5^iJwtoO?7WqkkLN|TRcwgT|<%ie4k@x5V&*-NQ^hO>tG zKG9>0gq-j`|kp+KMWPJ#OQVY2Di99mig_ z-wo~qztr&|>X-b?pI<^GX%dosZd13h;OVztGct^yzr|_udc7?>)iHmI)b>>Vc~!-RcD5Xn!FDc7zXtF~JuX6Pi<>V1)M zXXU-Z$rnQpms1wY9S#NTj29Xz&6nLyh#ygo7-Q1DXZ!j>k{o>x8*z^CVP&ocj%6qf z3nsDTN%EaW_Qzc>7UJW(GkQ63oRzU9kmv-F=%TPtp4em;6~%E&ROk)Vc6bPRaf?@& zkY5io)GgKWe#4bh8fm_YFP>!3-WkP-zl4R9$6skXPnK#rgq{BQnZTp5!a^5=9vyP_ z(x-lx$Mi#W$kUNy5zwO$&_xry&VES}7@^O%kKe#UYRBlL2 zj_PV788X|+0#udMFC}r&7p|hyw9i7nvdA6Z1ctZF3@`!xaLLi{C0IRYr$Fi{9e**a3{f=O3f6 zN3xBV9gfmQU#9<-`LTtC61{U^PhX};_KWfd$pXjl38`bJS8c_NGL|+rl%+lcKStRw zS>bXJk-`EiT8p**Jb z-hw5qojF`~*fmL)mawpJiWSw(p2!r*u`Ue8Q%6q(;{w^nV5g70i9M_BR83U^q1_D z{m=jV(AKcQ;o<9tU+-PB!_v#HY}8Jk@_fWs?awaMF5cb!F0sv`CjNOtLr`(pJ8DFH zDn$Gx3#6_bRByx2W%xv#8vWw~Hg5PN))%i){rq2Fqd{zJoX2o54^L5DovBzRK~i(R z!Wu1diRs9IE6N9pD{N_{_9pb}sl30?^i53MEL2sI($Oh7AX_eWzRQY!S^2`sQ(yjK z3n(e1HpQ{@bHHdHB z_nf{-lBs*@)P0LG1@*=}z7+V)UMOvJx=n;kH?L+l(7$WDnrsl=;>9Q!8NKF=sX)@* zqEm+yXeAB#6hAzduy3$_ainEDQ*-mg`FCn7b9UrU%2?602LdT2O_Y>ekWIQrxy8g3 zIh<2&K4eLhx-cHxD~P9!bx%eyS`D0!OuIjte8?~RYi~WCt`t=o0-yL- zXVD`f|LDtW5x@)N?p=6+%+8=lDhs+O3dGoFQ+}UbvFgxSyi0t^W$IH&zPg%I<($eW z{V58cG&`mNt*UxhHD9Mr(X4P-Qfe7Snkl`uKum~|I{aexXntN1?(u>mbwD#U8rK?# zymy)kU{5D(I=em3Mm+sFhk&Sk4zNoDvyI=6S3$r!*VlnD|~~X?-mPy z?2Jg>92Bx8bT?&4h?nt6YDTd0X-e9qdVxZ5D0n>$%{u)uz3(`cP$)s+%N;_lyzMToIcfryBS)7uQ(B@Y~ zzO7Cq9{VyT`aqSW*UkRL)AJ#=3y=^{xlMlsGt43u$ zg@;4JED!n%2|^@!hA$hAQY=RhjQk0lub!&9|q( zcf~In$jTTm#^hBY-E2Jba|T96iH`6ajJRNhM!|qvp^G9V2>|Pz>h=Fvbv}XVE5779a%8I|p^lDUD61dja6zR6zFE^KC%Oro=?wGqh;q%3jWaTjMJB`C9Z2#`Oy4Xfy2zi5|ztX+%6)EoQe=3hTtpK>y|rZ_P1nWQpO zdFq46)$1@&ws>`PC5B61^vPP2%T9jJW(9z-xG4Q&0<}m$dur@TD4@EOK|=*e_j|*( zpmWq)EdQw3v%{%J<4j@gnKPL9l9CAXD|m`Z8uw(~RWC8Fbs~d(shc39Q*x*1xSkPO z>0W=Y^oNUV{nv?z2;4+IiGz(@1nJHlbCnXG0QLSlReOUG)w-g<7vElsv^dQTYt9YS zoX@UDNs>6vK5OnCu-gbvtvsBO=t>KuU_fQ2lnO0~+udT}>I^@MmAMrw$7peFHGL)a zw47YBHGn+V#U*dCs;UaTjk+VxjaiQqgEoJlsH7x;;Qn2qG^elVd|ex_H(3FUn{!1} zT1I9TEnoyKViGhL8*2h|ZMVSpZ5&e0C+b?w%8v$V)gpTuXFr5)Pj}k_ka{KadRVq} z(qfLxH0apX~6uDf5vD${KJ`Dpn`U4Cx-39q6B<difd-OJf=n8S@`g*h^N!?<|yE&QoM@SZLSgNCxN@^8tck88@O0+7}E%^?K z%WZJ@XGBNmr$zKjmE2=q;=jgL+}&Rk*Q2YNe7l(eAi|T|jvHEhEkJ!regt&56ieN{ za3;Es+GV;zOJ4+nOHe52KF8;0~plKxdDxDbSANRW7?ht5RW& zUvzG+aqFfCgRY!;q0?>jc04foS0f~wGr;b;Zu55x`RDF>zolJ;wi&h)#^HGy{gi_v zn}2%5q^;6m7-5y3UtNmt$!?Bt)I|C?UeopxPC^?lue?5{%r-hNVw4vCInMoL zj_V25{nxN{(DQEYg2o%&V9Ig{UReT7w9i$-;DWl^wk}mo3wj8hxBn0V`nO4Ns?JFB ztd;p*1-%lPKk)NUVYG=vkyNXrYi&uA6gj!)_}y|6*1b$O+I|XfcG}G{tCC={DTK72tc@04JeMFhJ3A z>vo9tBSykj|2}nomhM5RCjc5bn%c*zXLoL&sh2RT=~1#>`Y;Y;-6%)O*aD~4qfZCq zkYh^^o>KP9HH92)rM+n~Xmr~2y<}P68BV_VT2yMDozwN;NxmX=Flfvc_9SbEk~$dP zma@f-NZIhQrVQmXa7S{u6#jJzZ!ej*)zQ-64lnxmBfr4o=*N?2BcXjp4~)WtGTBYD z`}rGw;6)}0KqRtpB)ZH5d69s>y~v-Z274qZnr&sW))p`IK`Nf zM=tO8CPR6%N@=9-o587Qn`GaF%z`JYyEE4GQA5`EU`2q)Fg2R&8zI2!ocP_ zz^@{3eo)g~Y&EQ~-_AjIb#iM};RTp+rR2D8_MjJ1${U-=nlCPex~w%b9cN{4um`xl zZm_*aWkdTyWZG1~QJX1tH#F64kmAx;SUXxe0)i#D4X{zmx)t@{dii1(0R;7+OZSdp zaTPH*VqMXz?BBE;e)j&g)&?_JgTKqeoBq-lvw<05QjpWX_IhiD-Jf1jp2c6I2D=KcY z;J28!T9&|(J|U)E2LJ3v5Z?X@mozNHXZD-J7oxk65h+ zYhL!sK-5ES>%K(kmceH%`v``Qt3icwM26x6GzIc?OOC@&|kpN!CC0{<@5ezaX-Bfyd z`myo$uyW9l0Yen`H6OsVk>rU6rkxSxuQ}aoWp4`$p5@o08*<;$u6Zcp7zts0U%zyI zYamfQI2re7&eB=2{63$PVuL|#j*2VXzNpZhVBIC`GgnUSvj3Jhxz>O+t$!3QEk5t*)|+w+ zhNW2yTFa8l6IW^6Iyp{^KM{#j1yP~ser*q~=V>xZ2TGTlgbRRxD4*~=KQm_X9(lu3 zGm-UZw&NJb^P7lPZ3`egSWTYWh!-{Uad}+IP{e6d@3U5NUau-3i{9B0dNTo?3rfEG zjNlql4JM`Zyi9YEcD*}I(oSei%56)hvl7shTf{mH+`^gMxJ_lG2d-V2j@vt{vBh*x zR)B6FJ|!V|l1J$4eB;KAd7$`6-7t1Fjk~dp&DE$IU>&h1Wq7My z)(B-j3_omn&TY46u0|qdPYGA=vtLC|rF8J8C_=ru&3_w<`qoyH&yxS)4UPJAHcb-5 z8*@2qnfG8R-f#mhm^^MqUz9bekC$Hhd)K!VH+nw0(mdVEwn`dV`_6ya?n#6g5^^*+ z(N&m?{Cd>2`*9x4C$K)p4Xc1FzG$*fcmH(Dz{oB7{r=jjRa7Q*$5#n$l4rcmwrjbB zT<)NG$)-z+RiG&qFRHVkY>*dA7}eYykp5_Ho{C^|(HRH;J2q+c8%{KrOly zXiY(9DK|)UPdEJ&oRe23%C+qkl{Xcs9*V&X8I|F;ouHOVw6trc4(f!$=DD)Zveq~D zAHO*Eo|-b~j2NPcx)BEaC<)I}eedZ3t*FRsFp3q{nvjCbAv?$hHXU-ud=sV;=2?7h z_mrR7RgUunD872$5prO6V|t67N%!b+ZK_?PLU)y zgnre}ffB-n#L0xjihu+gz&@!4$V2S70S08l$fZd)N@|5&Ye-(71!>WL=9!J2!9>Tv zhpjcr&#~!$Ty~8(L~E1lTT5}|ZQkx6uWEel?ga}tOl1lCyg8M&fy{8Z-m;Nt^->HX zts5#91vPF*m`Ugm%SL`qYrqJr zaZJv3H~b>mi%9E>f^%0i2A&T6eh;#v$&X7T!*iCfJV z4c8CEr+VL}Y#=30((2;IwJ@UXm|Akzch;flaznKyjb8irgvnk+{dB8RJZkp5WPMBu zhpCOUS>wTpIDI)uAN23Jq}y*M13Nl8)Fm+%I>=<%N20ovE}h-yKQaWOQ`f!gd497p zgyeL%-@c{XLE)n@>4~A2c}jx6^CW}R8~vk&Ro2EqD`Qx8B~B7B(odb14)>D*p07&b zwk32@cIZr!UQVSq2UwjjbOZjDfP4jSGSM*ljfKgxq~#*fTm{G)@sGvtRtyBviWYizv5vl=aX= z5zQPjmwGT63f8QTmF3Q-no>W!Sr+d*RD-B(KBe^d;_rJIKIALSCljdX3gIipI-=+? zF`P{u6&*%MgW8Va<{O!vpG)n1y+L0q`puR2=`-(`4%018A$0=Vu<3i-Kg`)>gO4VdwA~xN@&I?IEKAE+tsV(m ztWLy7VSvdAv4nk0(u?ad@N4U;*DmwNRi~OJV`Dc>cbRoEzwvJW08K4|y9~;ix*nwe zK9wn%+OjgFQm|%LDGlcqo+o39OE}uiX{=}7M|{m)`LZoYo{(FA>{-QGnsztA>t`jM zfjiO?T~sP&m8FnnU=u8}UPWD%csN>E+NALa!%|X?MCK7XEojJnOTUiL+BY=2qA5X= zq}=L&+JO$A%h~|v6B3pRAOG2&Qu%Hg3cB=0GISPBeSgIvj*<1|O+p?~WIb*eCR?wv zpwLo>+y2AbSlv2!x4rFOoLZIlL>km&G|)IqQF*r*4{0-n^F@$Su6_X>=J#bL^0G$T zU~T5I`U(y5azQa0re|B|w99MA0o5a*S=GaV4nF@*Z2b3GsUFA6<$kp`sn3b^+&Y-T z?kc=?e%y}tbXn#B%I@qTl?UZl=to5~0!YIGPuFjfl#j<0X6W_m@A&@{1+7_BNN|z# z?2j-)P_9>FAIr@*b&@r>S%oDicRB}_Kk+!`>l7zfaUqLS1hIHp`xR7!)U7!5I&+Q z8w-8appz4;oHDP5>*IH2;j~|U?SuCu_sB2$=>g&_ns&{zao#a>&%-HCPi>2it1^40 zj~olq*)V8>myXxF-tL0D#A+FrXXJbJ)s>Z;++5W&imOE)lqCde?Ck6&)ZX7^E@}@o zzt$coX!(6WB4Pz)WtBX;-lf>~=$5J!d!Wo~>8;U0?y0s29|)U`SQHHM#zn%xN0A|DTjCQ-wOLk;*XbNdca#+P!JsuAinRp2q!A2VMSqeRs9$r zBglac@08>?d|SheLCeofJq}gn8bDPJWc~jNg+LM$oL1EeQ>xtTmAItusT_UCJzUJ; z&#+qnxvh8&`s$23h3#>>#pUnf-+h;y&++E8^~Ex5ei0e%V6SAqnA03%gh_1XdRu@~ zg)3K}F1zqp;`ZXVV?vA9sdITLV;9C;^bT8!d$p)?E;q$8W@2KZmi~qEP3exTR(50x zvfO=O&|Ac_;a*i4D8<*M%!dPRgX*_yi8Co<*ypM5FOZqTWRu|3!^HB|v!Tp94Dtc) zZz4lD8KSTIkMiU)^P?et4lxO6mk^yrr`y$v@e z>Rc?g6w1g8>U6Pk3&Ob*p6K8WP(}*1b%A?^si9m3zfr3XYQ?B=%IM40#az1L8(K*b zpoMbE^qD^hiI~_!=_*WNubn}5dRCf~i;HU&zq?D*XCWf}1OHRU$*ysv$ZJ9PkX}L7 zEt%iF{ve=LL!q8D*wdmM#bHiW$SJPdHFm=!tPPfD{D}Ri8f5D8`Dal_9TBzI&_$y_ z$VyXt9X&x*j}9>j-V2IYxu)znt6 z;w~&uZhVI=w)Ea=_vN-*SW59IBI3%yz0~vWoq^2e4LuahGAqS$PUV99yu7H=I1^k; zm)_o24~y2>kW$>0!gr~^uL0GC28l2#zTXN7t0gPmM43pfmx32sG_pG`hK~VL9i}`? ziGkSMN6;t0&bl(xIC0G40S%3Gp)j4$Mz|d3sOVuU&V=b~06Di*l<5$iUbwx2ytTUn z6NYrnn5)sDf0SOEcRc~j8XE-xCSaOp=EEn^Hr~@C{fy7Gx_UfQ74|}i67~dkA{3+e zTC|8IY}%2no$(F2tjYT%>KEeS{WrmdCLXw3WZIbs+qL%8T!-#j29DpBOS#34< z+stSSb$-+mdP(ON?obP|jsTNaaXE{)#LNqg{(fI^xiP2 z3?{kcsuI4`t47dRfiHnyKu=vXz# z#5Y=aFRNwomP>)8O|l6e2cx6(HQqsQw3Mb}8*F$R2)EINsj2+v%wWrc5z+34lUyY- zk1%MNazYJ5w+^=Mz+hc=dIjsXZ7|D%nFl`PQyXM$TGQLpA$VIGUNw?gN4-h71PbA3 zSpko?>Yv@SFYpL%pskh&ULF{bfMM56dAKUR9F@``Ku0mAg2_r;vyXEoJ^Ti;5vw6w zpc}GJNxcE3U#%_*3SFBhD{(`$Ki=LsIG1J;d`NCOAaRQ5%K%+Ym!-K%;6AhY8zNb& z0dW5if!w;_;0M=Dp}r61>}PA8FCzIh7DGt4?U}UiL3;q_=$camE}36HeA~=njacm& zAIcKQKD&zjl6^KaTiL_?D;Xb!gc$p@#K}53E}SVoqF&>%>JTrQ;*IBbAK1#&H`#Bz zGsnRfFXMEgqw+)(CSN0W_v4~UM76c}#vDLL8}@mKH|iZSH(wvs6Dmg(4M%5TAdA1{ zz8mIYsX|ypo<^G&XJA?M0E0MQd%6`d64lRVT0IMnJGUnI_L`G(5}P$Lb~%y(t&nNf za9&%TwVY&&Sp$Xaqawr^+G&%R!eoPQsVG*+PC6N_uTgGJx>4sgpTIVwV`A`u7hx@> z-x|?|aue5`$!#IhXRu zGmva)`1UoFjlG>PE>@fglUL$C@3tt5I@DNWxRY1Ph!X7e-%b!RWsF&1v}3*37sC%X zfQs$qZsX^X*T8SC7}n;>Ry?GnP&y0S7bRiSVLoVhm%tJWP`k$ip#3`GVxz&~?Cut< zD8C?~BOq5De`ia4L6Qz$25lDyYXVSOGyc!8hE31VqEYf?fwWS2jujJBTvnv&8y;qT zmsmQ;zfaLWl0hq2U;z)A)1fBRPxv)cWs?`Qb--51POYZ zchuUTU)|?Lwfu>DxVpFnNVm{~uqDls^8;4jBuC4j>Iw1l>s|^_zkz?0Oul0{5@eWD z%dq7rgUiSxE`Qj{#X5}m04tq*c7JB|5_!_A>S{9vOOG_sMZ;N>o1d!sDC7~&BX4R} z3va3G@N3K?_JnS>sDT{sb*NI*vvk{o1`GyO=dI+71&}JzZ<7qi7mvFw92rejz>C>|Ny-CNCsjJ9^_2?z|g`zaHKu4WPPGRE~+D*>T5e zQ{0wK2D&5B+JJRugviTtJl=hk3iR0Gmc+BYKJg-iLW){(EBok#pK9-w_@JOsAQFfx zso#~H@3~Iygy8g+t}hf&2-(Cl0lXak(NW&V0d}Ynh9|X)e-j#e=t%etn*dMKG3AfGo2VG}YL)z$auuNH+GEd-88Rx(SBjebek=!@nR zV7&DRLuL6>o>M3S2*09WdmZFfaV#~4{5g_M&b3US3S7jf%Upp)u9_vypJ;ftzl9`8 zWd__9e$_Rp1_}_@j`}&YgD*usIj*gCx`1S^0!Zd5`n27p(&&hY`|+~2epe*N2DxU6 zLz|=>mn733m3``nDQ&x!H3(7>FJdLgyy>LPXEiRUjb>a^GqWo&S^&6=Y5JWn9wesD z+lvN+yrt-4uKGuq{-EyQXFCYK(Dw&I1OI_2;8Gwe)sM`PwRZKUyE9)aa5M8Xt7Ddh zpq#Vi7$+8p$q_`7!iU$11>jH?Fe@2J+$p8I6+S6RbsKt{XT>lrn18gO=(3|VD2xUD znMJt3PW)ta--f6ZJU2=yC}UHuBgTo2=n#MWed&!gB=>wFnFb!y5u6A>lWZ&zB|AA*TJ$qgmb3vc& zT}LDkR#LpJ4u)#@rTNSpUEkCX&j|n3GZycVEE}qjQAz+->aGaWXQpS(I~*z`13pq=XCH4VN{ILl8KS zb3z6MAyU;za7B?wJW7!<$qX*otLnCSr|{|BAjJTt$3erlJ)2&ssqJ^fHF6s30N~`+ z+bfNn{FfLm@TDMD#JDU8DHr8*QoefGdE)Cq8~Sy(@s}7c2qw;$pO5`utw3zoWni`d zO8Wen&U{#Tb!L6-Y;=xWSU&oTi=(v`#4H8zBPv;7-C((KBmV2xtfgDYH|V@Q1TQ>5 z@IO2Nm{b6yu>l~>C05Yg-B($ybV%v*S4qyft9tjNzqpxLKe_mQoQRr)*IBMHhy+}Y zIPj^vhrcB8v}PV@-+F#O0&E*EBa|tx-WXTrRIp{|`gZzJREp0hGlP{D61b8vJo7 zLkP8gf|}yfm}*f}b&gC`oWAj{xr{ za9vnI!SLlzA9=>dbBZezgX!Lh-Mjayc*JvVftf8^Yo{{;BHVP+BM2al4ltIHKj^>L zAMEga_;|(C-5Fd?+KI5PULXOkaz6)Unu&1XKkV=pY+AFfz8;P5&UcvDMl`4lpOGO+ zoq!cvZ5IEO^Ui+Hj%`Q?o#!A^H~F1pAIVbOg$&61;JPEp$@SI~mg23-NTZosbu5)W z2L|+A?!thY|HXigLlC}Gz2$mZT)f(5LKQY0+27V?kAyxv^g!*#`p{6n&I@7denKg# zfH^EmR_C0Y7|q+SN?B8O|5(Uu-U|!HEFWeMaW#a5nhtb1klZP6MGnLQU=5nc-763Z z@E1;6EJFejU(^i(5c~LaC8xy~#Rv~?Jp8WJz&%Zk>=#Ws_sGazN%oKS z-%MrZxJ1EF-bYSE<{Opt+y|+fRGA4^1gi5-P>T$%3K=u;|9FUq-)`h(ZJ9J)$R*_pgZS8S>H1kV7YR-yi(!A7(4+jlf@nE4g*a z-1VgXlm=HUBm^@@(m9Hd*(!#Y-lYsUxeGwD0%c64sla%is6U%mG?^?usl;O>V}5?# z43WYoh^2**hxG*f*F9E{5na}9(u)A>hio!>$L;0@{Hw#pfZH3U2ViD4PXezv(gW(g zzCNaJBNG$&HFUN2Xh|t3c6WD&D65@g3cuI(svu#UmxYVGC59##t_z#qY{EWO%5vd} zajL0W#+9Qx;mk}%6W5!e66$t3HJ}APgcZ>xOw`e8BK`r1P4HfuIpnTqE^IpbUh5%B zEHlHmfUIz^ru9+A#>R4_=83!oeLOczc=X@R+x}RSG~x(Z-0irhH-!7w-Bc+o*9JBWQ3znixOk(#e^N0+`}}|W z1b7Q(9=rx@4BFPP{*4&`2q6MM2zZ0Gs}~;mhspi*Q&1TirQd%d%Qoz3h-@-PD;MGa zyuTolyDUhl9y=ks!1?^6DO#kf3ZY~5HaJ!T-wIMykhA-Hye@|C0X1Mw^VUSS?lFa- zuuH!V1fM{IxJ~eh!v5cX0@%od(co!&1rl`QI0qcap)UQyq5g;O__wF}|J8ShljN7= z6D$)x<9O`z<8b%4#r)U+lR)gbw~e-^>w-bc=M`_J1_mvB77SV#ToGAMv?>Gxq!s}r z_LmX-;f5YT2dGDzf;+oPBvNTAJ$%~0qeTIaR_PyoA8^6&|DDljXm^eN9FToQNRRHi z?6N(?`J4V<88uJ_`~hzKy(%vrOf(RRrA==6s0!=R1u4Kfp~uSpM@sY;8~6m-+0vR= zpER&DJdBxIU}pi4omF3~f{_1xbq*Mmbv>YdrlLS16`+{x4mtDZKb-j=Vh9KU|LIQ< z2ILlv7=1>-n_Bh}H-^)R}R%=2kb z+OX?qVE?DDfxgmv2?{lz9?+Q56N>x5{H-TIuqpE6Fj2-F4)M;vdAE*W7I6x- z>JO2?&%*`&9)t~z*ngWxU|qj|C@8i8YL8z%<~_(`{{FrHyK()X?Ek}L{Ofx!kOY6w zK`HiYlf=!$jes^6$$!xHzs%pi+OPOUwDVu*JDBksxN)mH`HuP5@D-JZ51)lm3g~cx zn}@Hnvg&N!#abp%ueHv+3TX7(PFuQl0M0U+4d=S=sSxo`vpVy-2+|4u5u{(J5x$~B z{RnrqJ1INKi`8l$OQeGfI2A)eLW*eJl3Bfvb{F4)e=7|HJVFH2UU-t)ng1g6`O~QO zFfT&bamk-USfIcGvnk&Pqs0{jccXZ^m(|rhymCwDCM75*7wwk3-e89Kwz;{v_9t#J z6o?qK%iFV~Kab^E;>8nN{qBj$^yWaH+kjJWSxP8Rt=p095#X{}0F2sPbq4xTB|uoK zjr%tmdn7_}MUzhUAMwTyi#z7JC{CZ{{2Yq{2ew7Btn4dWkTgwh)-qa8Adc{&pgo;ZUP zxFNXhb_cX1rRM}^fnw~MBhdTPXtctp;*&UecTLki%QJZEt$PrNzXO4({W&s5drP07 z3Q6~Uo^?7@RMc{|?dT1qW&>v{Oyf$_y z6ryxhi#6zhF8*a)Vq)jDj+1F{Cu4kWF2g7&>;v7*gYsEP;m|E=Eu00h+sboipKaf< z7`VTWg_u`xpW2O4kmfX6`&?e3{}uyWE2D;ju2M5*cH;=TDv8+Pm2NB2GcKz%3i7Kq zd~X7<3?lH5<05~aLONYM-3UBP@%D5mOho%hsR9-iV^SF07Qm=IklF%VM4SmxTP_Ge zI1r4@!NJjvlcyt>!YStgZlvm5)Ad{jHvNxO*lOx?p)s>Fpj{ zq@=RT@$$=e;L1+BYhNlMdK+|U>D4={wVjt{k)D1#6G+Jlz$=f5ZNO_Pd4Q&xX5=NP zBYp>iO7h+BpRw=T9d?4Nf{~wrTGD0G+jI`FGr0}>RZoQs& z*ACzrZzEC|q5Gn5MqCDyH`jE#^fScc!`kSwygD8}_URUuh;07I~GY%#MDdM1hHRan2A5w4Rz=CJ}rlYL%A+)&f9@t<2Eckzfire*C#FwE$$Y zuYr23?tsLoDfK*{SHsb%H%K;{&zqh*j6EK616L)ZxBH1&tPJLly+9TYM>i0Ph=T6( zJ0|269)vnr>1VQUt--+^>fhc_zA_11@7-AxIDfb(RleAM0z<<1Aoe{DXq&_+L6u31 zph0&cK;c^4UGd#;04}kzHT}qE{jvLd+g~JzzM%gjU2u{4{)$efNB3oHALGl_C3d48 z!fQzV_J(IBTPw9k3n}&#_L?exM91f!*hwJIoB0k?q`md3NveH98V-`w{reFRnORTAwv&7cE&^_Y>kFH& zonAlo+Mhibw@L)RM?TU>*gOpG!%MxDwtdGBCWI@+!Re1jdFPk#0w`sd`2@iR_4+5- z#jtbOQ|3+oZ)KOJYLQ!kehx+@LE)U;Wa#Ib(T`!dM6GlA+P%Thui&ogcyQm8Vab3L9`Kl z+PD!FpygfAwycmxBxq|V1EFp#6-jQ43mR-6fZs0qC`O~^)baTX)j@-k0$e_*yqZ)VR3osR z7%BmDvYq6XP?SveE*2yn{G6=!C694Ma50c=I*WA;j!E!P;%zBhmZ&W_t3|(1E&18g zhQXf8!XiisA_>Kxkh$Iehf~vrjs~G(gXus9)`I=hvRBOsX8f$AhjKOg1R0A054v%4 z!b|c##x|8CNvjmYrAr;`^R}~SC&&J&%0%;4KqO%9j-tZ|VcC0j?=Q&{@jSl4D?csf z^PC>g#Q6DHG~tgy9li@Q*}a4tTMw-bi;VX^YyA?<880b-!CLD;S87AhSaWDGnbP5_ znraT4GE`3bDINqOB)Lrxa*=*!ff7Q09Tm1wT$#hBpU`pnf7T7XJRMH=vP1LD6>%zr zo7(lt5qt;g*I8JGFukU|v?y`8rq{g}3)M;!KwF2$r8u-r8I>$lNpMoWpkr?~Vpc6v zos}(eC(k5Ky9L=s*4<+e@x^qitxfm{h!k_)68SxA zd4uAE9+TZxiG(HG=7&!3D#ORG$aHJZi^PEIabC!v;89@%`nQfLcyA2Wi5wtBe; z{DDmnk_^(j%eAS&=m36igRVPsUEelty1~#7Z_gF%h_62TE}D{zD^Gg2`0+BE5{h%6 zegEeXOAf+$7sDYR^|Le#q=eF@!S%OBUmBbzRgHoLL{Pp^;G#`8>LqvYdOs(Zd}_wX zUR+c(7_1##I~as?7pJY|)#Cv}umQS|tbLlOvqJ^Ch+DXGsNCzX1|SKbj{IQR?o%p` zOGo$Dh9@S9WDZqTRJwuIfdyR0P=nK%Cei}Y9`_T>FzH~P&^^0Vk_4+oX;gccR2;w_ z#HhV9!oR>DNaD-OV5}7+&Lk7YELh756Cf1VGINvr$EZPC_tW4iPlqFIhRash*WY{mXsHD+leGcYarQ%82T}l#>iwUi4IOCzIM~gd zBO)N2pC1@(Y5aM~;@Twob1jwl&TVoe5=p8kKhV)c8EN?niM_G|u5uhxUTt~MjWAy( z=DU|eBQoy#RMdG{Ukv_|ojpXibqi-Hoat7{C-&?G+{#5zFNa=n`^=MQY zS1j${ZPB2=aWHO;G=Ev0E1vd4H$O&K)p(qPNDxkT$E@XWCtPK~wC6ccW*800 zLpBATv?0X6J1_5ukL>6g>Rkyl?CyH?SgANa@1mCu)nC7Q@TT?QPgS^KH+vkyBzL+Z@ML13i&p;n9F;;d0+0C0Bi{-WLAau7fxpeY z>9X94jwKD4`2WY=m&ZfhuYcc3MI}qyAnPbeg*N*V8Cs-LRMw(J%9?$ds3^uV2_;KK ztDPkK76zdxOLj)Ku^S9#=DEJqeOI^6x$pCO&hwn}dY%7%=l8lB-_PfB?brKyU+a>M z-wGlG(%=2}Lg@GP64fEDch6@j3M(84d{@2I9=11?<6q@IT)e8EsTQSQejTFclRuM} znK0H%+W{VM$Fk@+;hX&Nm%C5Y)qbgp90FotD@ho}Omi6N zAO#mU$oo2ecg@%G0+?`IyUToeWNS4-ca>gw^#Rk+tg~rlsg-AcbW_mzsn`wq>dR_h zzT>_Mf<#`1u9{AX*mEUiRTyMb(p#p_7Kw_7ELZT#*%&_|2{HTGBmM?INowls7G z504Y3%ab68>b;h+N-9SmY;{6e(n{qAM?#a}Uu zeW|cXwwVE{)Ujoua*oN3K=JG~@vzXu&Iz|N@MhWJK%?lzW>4Uu1HRoD@kUX&AS6;O z?&{kM*MIkWR}Z(*W`?Q?+3oyxH8n_7r}rm(LbKyuZ8_ON1{pIr3-2k1R8@rUnoNh9 z#g7K16qB#5>e27y=HVE_nL5H7tIj4M{H>*|R`0nrj@o}wLhzZgILD$( zB{guOFRwsru;K-N>=FnhiMq}4nP#Lp{s-}M=VIKRN!lTtHi}Fsn@Kszc$t)_%b2mt zuJ*v+JHj1N2ZwnA+CF`q-41uFU3I-aQG^*2E9(8d){>dj#RZXAG$vxTI`KJtiHX30 zu%-Fx%+cCjJs|y6^VcDZc^hE#Hsj9s#^OiJmN8zhbm*`Dm`VML#J6}lm2@hUT}uhRV{=klEh#FAw^#fVq$K3Ku({kx$N$1yTt zxO`Oo@$EJrU?;gz92fwe>VNrAP_mTxe{raB6uXKCd;fvQ-`Py1Yz9U&3)Ttu(V0hE zS|(q8ZQQ1kK6sYZPr39Ll%mcS~x7f<;=p*7@+F3%eh0 zMD^yK2D2*@JukYT?%hu{&?xru-kN8+(LbyleYkXy2Y#uHEauXisZcqgeDy%f69!Mg zq@;pL>1Tz)qo610Z%W|%e?bEOmB#}6@^=;L&H$=(Q|}|n%V*J)mp>U5UbqQQS$Rxf zIxSRsOTPNfOKT0I;3m5M=}lZ%P#4#E;FqjO$*eZf#W*>=(N8!D4m{)W58CgKXn!~_ znyiPd*brnC&6(<*00xw74QdH1(%O}saJK|x|E4lG1z~<=0D6CA07$>q6}ts>MF6SD zs{HEu;Y17AGbWIxYPMgf{=#<%uHYX?(=U(n52Wc=$2pbQ#;K^PS#{j(W&8_v+|`O> zW7h@0nNOybXb_I#vmcC2ru1F`nd3=Oh?u+(7XyLW_REauny2izTU{_dy6xEawwnLQ zb$H^%;XLTwdM+BgcZ{O;_)ZqN%(^`Cm}>*L^lOJ6;Kyn@ZVJV{o>z@qfD7Na3^&*zF$|}w-%iCRE=?T zOMhb72WHI6i@T+@!3pmIiC=;EZ!Ql;bZyYSdCuoKW8?0$1e^hlgnBV46Kj3d1Ha|8 zh943X`+}`eMnW4;{`_0E8-H-u28d=Uf@Wo}irElu6z#1YXl$EzM#Fe67zV_~f9i1x z{QmxN690wckp6|&k^TFx^B;ViTO?F(`KI3cdp?D}G`qxz)=&93`|yvTr%)X7tT`=vGg2bMAASC;V~=-&9hU>SdPoLzRf@pEbY z%}xJECm{d&NhgArp;ts&#aErnC;UTimh@+D#*5oZ@W7Myid*CD??La59&Y$YuFj_P z&<$~eTwt+jW=SMYHr(ByOJ~9$_jm!h2gvT=ff_cGl7-FxuMUZre}|Ex^|BIY;a~ok z=kYFL#$bH8x+b7+?{t_SA60EEgul7jii;7wG2k@MW5{3>z>jtzzDlk=NXMWz!gb^)OyEPx|<0Yc>!3Yw>so4me*F7|uQ7sCK*Te5+y{r}+e_fu_r>CUpO4xBB~b z`|rv#o`;nf*c(IybIXlk<&Zht8BfOIRZ;gFBzCy0^ZCB~z>UIjf*cK-x{uC4m~>)Ag^VhSJMfKUK6Lz%U?qsWIE z=ThOmW{!dRs{}qgwHRPpNzU8Hk;vGW= z3jNs2f%y2lZ{QE`02suvlQ*rqp}JQ2;>l6Sv^S*`_k7MnJVG$~``Cz0iGRr=J6v?) z@~%pEqv$ZLgHiDF8t3QhUa||-y&&^npzh^};E#Qf^{0QN@caQdsPo-9<=u0e4Y%+1 zX+EgdnPKMcg5dFtx(4X2@BCEi`zQu?H(mGeXRU(>S+4V0Pw)Lp8miyLX^1d9JP!x> znDyJ*Rhr_*7R59?LdqggSoq~}oqly(c%KbF)Pa6u5MfF*7P@(2v;8VYbXVAEE+|8p z?SK|%T zd+U3e_YwRwbSC3jRzd=C_RkeWs7@|WK?H#W9Fp}<*BH&8=Z9aT{isPAjF-(N)4O22 zO6LnF@L%uv`M*X=;m5TF-FO+%R#CxfNHypEx8l}TU_JltNv|nwK`Ni;Hg>=|c@D`- zjyK?^ndi${iGS;-LAd@af7;sa>ZUjsr!9|oqA_d7Z7NqgFt@tN~NSo?|aR|6VaMB~Jak_Ud( zR(B8pq~sf?h=SnpL*iv|4g+BA?0y53NQLc!}D7-&07AmUc)Oujoi$XZ_MyKi2R(d+JN0u__aulhY z$PaE<`_1WH#DFkVJpVMt1vmeu|M%h-KDI=?)+9PDgvN08)Fu?(D!Km*48ezpHACgM zgEs7?IF;blD2B`t1HT~E5c^qWEsj7$$SY=wC3D*S#}w&fii19evybVahJCA{qf-@u z{=JEGVo8t0u@kX5BTy4aglh4eLjw~?LWyt%vtSTX!8CxJQ9{bXb%6XOzhf7r4k_yc zZ(m#{@RT(J5o#9@9Caw#jRmJ=6}6^r7NM%Pj9Kfl|`;9t@L|u zOx!5uxc^0QauF~3`Tz=FilvQ3WJ+*9xyvwEqgvLwYJ)e+&u})$?8fYAF;Z$Eq3FTSGQY>>~K5mhHdUQ z=q94~6?ttitUaLMxsk8@j@d;}dpke{4Lr2>LRzQPDhljjZpelWmUh-xT)Smkt&nq2 zKaPH`a{02;96)uItf?lY9DJXffm!kJ<*x3#bvdvg-8vcV{Ut_vt>N$1djAS=UcFtM zme#NbTgvkbHv~m$^CfqvU_3t4V1$tKj0@b%nt--?W+pTcd}^i=#GqNgw{)dl-8x4b z>IZ+ft9fykHzrOI)D$}0CQ>4|Nk*=*YqCROvgd#p8wNIT3%GzW|xbk@8Fh>b+z2+}F$04|u&$^#DEW?~bL?5hP zucr9+BA}_rW2H7weOxgj|1H=F2nNGYecFF*I7>dk`|9U$Bv~;1<`ltTZ9=hU%1_jz zTTKP{RPmC@MJ2t@A|rA+gEL5519(_8<;izv@>$&iT016$6)Cr|g}er$7G+P;W8^kz zMv&~c5zJW|_F4l4+03+LE=41^rVtxQ6>QH(d<;FViEHvY-|WLiqw6fQ5A>P$6{y%L zgC9w-6w8@<+!`)!KiITrult6Cseq9ISkVf)UNdAKKmlhZmWlWueOK})nz4HOJ&rbY zF%D)glWvRyf)5^CVK}8cRf{YYRlqG@a$naCFs9jc`}qa@&rS(y7@aH8V0u0Q|3A6o z0pVLYwiBvou)%yrf0w}3J8XTL*l8`PL$u7M+C95k*IG1)Xf-_%Slmh-wC`3dgg@J7 zFkKxoT~{8@DSh675RYo;60F4na#N+jO9sIpjtzDBu;B=frNGD>Ka%TZ7`-Twq=YYl zr|-32c{B2qn?ghCjxz;}*loRu(BR!XD*O9;JcZDzT5o-SEE8sH(4IOZq_vng)x2P; zPl^ljxg)D_$0XB6J+%}Hj?MOX-ryU>b2<{ZFHi{kEG#VC?g2_gnj-ZUI5K-9LnJG! zUma)g8BU;TJv(fh)@1F2RmeKhl67DJS&U7?=-EBV8n|LHrV?FOn&3u`nXME*cGv&Z z>`X2_6Kmk~7!aAcBZgjvL}T7Zw@F9HVEQ?3N{(6UQF{DlXFK@$)J|VX{nP|7%PaH| zzu}|2ls5-I9BOG>W=y#l&@_PI#R8w}Qe2>a2w{+?p=hURwUFW$K%tWPB=8pF13~A! zijcOQ$_?Nnto9G-EF0+(Xg~U<#?M8JuMy85H5G78kW`G%aBa;SjwBKF9ebQ15+P^xzO?NJ3@A%9KR zKCQzHyBrA_k$@9k%+*<@W+`-JIjaYrKY!?3sHhYQA;Nwe{n!*iRMPR!dQc1Aojup5 z!Cy!gnNF5;exR#ms~?BLsY{IjYjg`YVc^0!HRXB{epN>s(foZ(ybQrELMjRh0<6?8 zdvJKk8idM_u5G8LUbT!_AHlO52J&n|AWsFud$D4J2Ac;DZO&-}w_uO3$cmM2^8&<<(LS!X;VHFwPSrQ!NPItvLKqEwNB{KC=A6Wz)0D=*{nH7N5M@{)`BP?e>!@Z zmt3@FF<=YZsa@U7HWXoB!3X;QVzyb+mz!Vw0!dQYupxaZp9Ayr+ev4&Kue2sq-g_%j4IWa&3_pH4)xhFVA1K zb}=g@=bcKWZl$7}w?%<2b+g7jE&BGocSr>R{;q4T^T}^_5fdl?kazgFA%0WTXh6<} zA@EMKb;s7Hbf_?_l(>^g?J*{pGxZJ~bIIz}u&^F-ta6@JJE8pMxQoIKHtWIBkqykx zyzK?=_%GfswuaK&?@(pYr6vP{$Cjxku zwnuHZE?N6T%%-nR9%eSkpQ~AlF5HZ$(_tXbxrT#ixz% zL$k|r$4)nVIJy!~j1sk;(Qj~n z#AoewPt0w^XYq8p3q&@P8fVX4u0Myir%3~xpDNWWFDP*Gjjmso!QgRpH{Y};LG9k# zeZ1DRcDthFT8=d=6H{e6IUNsb%EpTLV=jvyyL^>7HDRs#bqB2 z6(8f5ZrN9Rk6~rC9rZ^vCL!p!-^%l({&@;%)^-V+qkyo@`-z3mxHbNKv@w(uEkB>| zzz-KdR9C3)M(LvIx-N?le>#=vLc)ahltsOfRmlm-kP~C2yzvE-lBa6-@`nFXgjtya zDrEv-Z>?E}24))=-k474THox9aSDLV8#0tJAT7P@?Q8NRSL4mtA$V!!^~_~N0oUUu zI=i&U({F&u*mSh@_=#P!}?JXtWiD|A355HNt$t z*!sTal||iM8o4fE#eH;v3au^{>x*q7s}DqJHb^{DVMsUjZK_c9xY04l*6YrputU(o zVhqSHU2xz=T)bIhM$E0&A4PG8CV38ZO?Ig15zS>0-Zt8YaPh+muUB(b_Pvk0 zERGD9EK)QZLCd#GJu*Bn6=5W#KKy+c2x0l3!@#dsbKhv6fdFhYkK%6^u-}T8jBgk4 zRp`==9}Uo7njEO4us!+nrI>VxaY`gVWdV=c?AUt!t9Vwmf|6}Q;sLtv4j7+sjLZ5E zgh_0Fo%AfyEhBL0gVBI%N>0}5&jnbeJ;%B6nTZ4&7#&Jce7#F5s01lBEasqVO!zc4 zs}`(rB$Ox-{We!8YT4-`OIC7n9vgQZG`nbbQlK<2Z$qf0A`XJ&rzR_}7bVYd4! z9&7LG7Q_q(CA>L(Ejz=^Our2YL#pC z&B;&)>Guo?8I%wdlNx`o(kz)X!a_fN|KQ;96eJgqJUZ>Cp3>NH-6;Bbe%?^C4- z2Iu8Dt#LLzS#>ZDWq!OtLNFo7Ka|j{SD@08=k;FcQu5ACBpOrxts6^{eWFm>H|o4v z&+#S&@CYU!=6Iv7^y;x!Q|fsr1n8RIB5k77rsV|HJ#mj|sX)7{SxtJA+VDo@P6Ts7 zX5#|2q*w`#YIPMISA}!-I6|1F0wvnHkh?{^XSYCIOw(tnCLS-Nx2KuE_s%v(AZ*lA-%qUlKs^sfL?F+q(GR{z5iI?D zw6(sv(ovF^K)y#4r>fKWHTFktP`q=?{X8urG!>~=TP&ia&L<{pd=;SEROYyz?<~4| z3@JXePHQW42rfGOW_QwfdqCZ6pH6O>ec6q=^21!rx^G40OuD_AR55m+)o&elQD1$6 zSwiSxGOv!l?Frr^gwK68ED0ieb_DT=wVUs4r*sT;2z76k_4Gay>_R$Ft8RzZxF`N- zii2(&1p3Ecf)7Vm%Wh;mJZe)$=C6CLXU4Ik)TY7(cPV{!^dH4_aT5?N&K;j$YaSA> zwyW+E{0jJ7qsjF_IZB?(7*c6__y9o-xMm% zM@gagMEOrQ%WJn)zYEJ+`bRjMf`qfN_M))&&{Q>nXa<%#8-WZXAfsCw)qiYjsH;Jx zuQyhebchLaR{4s90R%92zd^Lla{d7DT*P<`!7gtRWmkkO?9I_~G?MTl*k!0qLdWN) z{kHsTkqjf5V`PjUb(=d?P8ojs#y2d?)}NO&%OHu|0%8n^psg2o84e&Yvvd1@{P^d( z@bR_@N|eskxVveWoT4EFxjolA#Zx$Nd-Y6<55scFT793f?0(oO-?ZRXT=N2HyJAiEinwhZG>ZJA!AEfb!4E1WrB{m^rk zDkP&qfSrHq;sGfH@%LZ99q8dO|MW`L>`>EQ0C$TZ2iOn(+#d2nYD%Yk>hD8)=*H*a zEVx6Fc_;`303N>w0CV#75265Q%5ePL??FSq=W4B>-6lq~#G^fO3D9{u0G%g5jPRiU z##IAi@~>a@nZGXMfA^{Z%>VDYY5p&EnMUT*? zpsyJh%KHLDK=2=k0Q5}%y0$^pc|mRakGU6noJrvL1fcKBe;!OpLk3d}+(%188vK_X z;2gqgtMX4jy#vW=&Wk)2hCwt(+(JS}3 z&2pJT3!2AVQMsfNY&cJpIYo&Py=`@|F!(3(a`2O1jhJB|o5_dAs^%jdMc4h>Z;vl2 z_T%X<_UF@7xX_%sD-(wg1wY+>M8b8p8v`Lv18ow5#Z$5~^IMR`uB$vF$X6fG-)rmNz-*iZ_8w1Qt_x9p4M09IHk#)K}@PWZAKw)Fi>1_ z!DsHz!4M}@R>T@bcG&l@zGY|IEF$hWDwI|!v-+SPFQu>^u${H{zQ=aX#o|ksue0}t zT^9LqNyS`?mkl>3u5kd$p{7~N7&Se`#^mI70pU52lK2e<(i%=>y{G2;uZ5$jE|A-& zsZ&J(mlGQR9s)r`K>!G$&*}r3cwm&M#%gZ9lY+Y^=G{(!g~&b9JI-nc^D!dn+rzbs zC{w-ENyVnJHCgN21=iLBM^LZy9p)5c6Lqna*M|>_S8E@3-T`rC6lBqJI$O%zT^Zib z9amOz!9-_~KVF#M)!0NQV3$ewF4Jnl%p%I|d9>E^&BJe3Yks(x`k1p&R+OLR6aa&% zmAt$1qdpi5<*RZM?=oSQx|?1&1u@*VzbO16u*;=T+Be&( z5j2bZ=VeaG)*bc`%gx;Cz0)T~T+4Lzq*2qGPtCxU^RYeZsjU!qom|wtmq|^PX#5Wun{EO)xfq8Pc9s(nFrb&+1u9Q9C~el z_Zt@g-__ApRbHm(?TIkjn^_@=b?kd49o?-cEAu5q^7;OEi6Igskz_56_16cMJEWXK~^ZPYrW2AJFz5&cvk z1;^GxvK&NSJD44#F6-|{rxiQ-H>Gw7&kZDR76T}8i#MLU;;J(;<}jU*|EJmgIp?r? z6w{D#2V?F_=1n&%*PNS4Jz@aZzO1$A{`EZ*MnbcW;o@gBa#5mjud1)9a_+wL@!C)q zcg)jeL7Y*Bg_EM|_)({lG)*QfD$m`J4S2oMl#JQPll31)Cp=( z7K3}`*RQKIw5j058%3W=U!H&rp&k2{J+SO;*{rKmO^8csLEB%Ul+N93*$TkOrkypR zGBoqzcG0pX(wSEm&lEwXK&>Mh0Ykx#dxyCDk?!j2x$F#F;6FVj)KSQW*UweK!DElF z=tRnK=O7rJA3Z1fCfjcGZQ|;ex2_A0Tse1$ECBjBiehTT-VfxfPmLV_5OhLOJ&Ydw zRQhQ(juz{es6Ob=o)nGD5%^{SqdY-Z%S{l$54iU+wEJq4bw-yn%D^uT>A++@#Vo!7 z&4Jq`s?%-@hcLMGH7Jf2AaI}SKfsl~yVjW)zfF;IP8A(t@?lgP7i;*i@jAXZQU{a~ zyRjI~A!Pn>lnLZy^&jf-@0OD%$P1h$L9+XO0dz(FR|5sS>wn=6kgA;ww&G9I_b|rc z1QS*N>ns%h7=l zjcwPNF*14aUl|nUF0eX;jOu+-a_i(%P^$vZ)VheC5a3-%v(ho3IGf!Me9rRBW}62) zZq4o~t~mkKT~fduXHKQ)Qq$Lq8ITNItnPCwx@p+F6ppypTPf4;OtGnXmis9ifLj&0 z-9DGT+Ne+QAu?(J;Z^zu;r)8=T24`NVFjM;SL2HmMd5HGS@VDAH}}KH41a>WMT?yM zXVcb+)c{26o`Xw2ASGb4OQ^Hmx$pWjNy&kY&{`iIqC5^&N?V5ZRZM{LvE8SSJ43;x z4}zf2VRPpd?ohAqsO55Q&zkgg^w{WEEk$D;U*R?~B0w^bH6cGaTM_PT-idyDeF+*U z`#fZ+<2#$d&Fdyas)xJdI}HFsD0nmsGw z?r5j>n`ZlT7kzDxT|EY-z;U2I{y{skKa#?{bOnZ3nnObOSILb*C&FB>*3g8azp)~6 z?__o|=-zO55M)Y*GsqM!8uae#6fPu@^Lf~fhIWzk{NG z^~00FDs0ZD$j{y$_%&6oZz8ndnvlsto$WtO79x6}^?9$+O_ovV+Wz>=N_{MK;q;2jFGO_C%n6X%ctAsn46aRZNN;`d$S@Te`}X8db#G2+pDF@)svXUE z`&hTwaEaS+zE{{R730@fv$KLPbIM7&WXXzkZwzUp43m?&Rim1&L^R(!XIw1MbkOSC zcSH0PsQc<7Ua=O~sW{4LlR)xWx$RW)+w6(d1gq7X4hN1CDF=FLa2#;rbEj+vFoJ zw=&n>gC*N0`BW&ZeS4Sj5U=CA4DPO-XqnL;lx>wR1WFjYyc&+DKoKa}BvInxrj$KZ zasPr_$pC=kX=1-k>yde`MmY6}q}3eGG{L6d?|nAaGFfRiLmH!XTupTeuXd_q$lE*r zKm^8P%Q5FGLI;3&Gcp}I;h*&$kn0SqvH_LP^FFE*aN9OOqxlHng)`YqRGPzvKc0v2m(r1*=Cgn+hd06V`lS>vE7f5o*0D#_)rb|(a0F-M?`-{C;#Zdy? zIwo#Uu_8S+Av}!E10eq#@wVNDlrm=X-fHX{n7=sJps1JK0(^+w>BIKp9ZJk16Aj(2 z4Xu-y?yi(7S`O>=Xv_LDk$Z-4Ho$tDS-w59DHr2b345FOV6$B$08j94wrOt#kd_`y z>yD^#wcOQqbGK{F0OCh3WekT(ZOvC-eer}L4CVEck${W-tg&9AV8971n%+uo@BU0= z5Cl+|J7)abj0b^quqsmK%e|;(AfddE1xHku(QH4|$}X7T3~2CvI=Qo8g;-fhZ(ZqX zzNx%L4Tj>|2_=L<4(X?)lOnYixzyJrL-RdjHmmH+&Y13+cq*CQu-|5fW0-YW>3-5W zybV~HE*FQdk*Jr_mV5%Vh`;1aX7dpl&(z(VUF=6X%XH>8={M=NYt&gO$_$J-g!f%v zBBIWeJeUzk9X#Gp%5H$J8hFz=(a^fzE>Mgbg~Htko!wp%_}iBk&uD{0?W}gI_j~!s zvTDKGW}eMV*J+Mm>G<$I`@3)UP0*wE_xdKU!qlF+z+(>+mDB!JH^>UW@7W~a6S}_fS0ubcDQHsat}EXfg&@5u_3pxSxA6mFZ-Ap*AY(W zQ1i0^#XDJ58ivrmzRtcn<3d7)MAC9yGXfu;YjvF*A$O!7xW!?IBVgZ6)*CxbG&CyC zSi$gwp{3e}5krceEfx>DaV39bPB$`i__IE|)KUr!jIjHhHXE)>lryn^3wyjHE{7Cs zp_rC(lYz{(MkA6|yLAfH-5;W7eLs`(rMPt`(A5$ZX4_e(jtFJ-Of(KzuGf1{XH|}* zjSHvv$HWdq*5==06y%$Jzsx03a52#P=8*h$cBcbCyUJf|>5iNLs&)1LXB2+|VN$eD z;@J6;Tkiz&E1g@$w$qa?Pz-o?o^R^eo;#Zc@cTnK!#AU5>qZZX=HJQ^lcEqUh~2q$ z1{PH`x2cydgX<0TzF>h?37c1^FqKcG#s|A7X&7FenOO@BjH~UttxkI2H@$zEgUlxN zf9p_!OPNy$q9RSQNxaaD-6;;V-aZ{xG#7+aNp)TYo>$%gR(xEU76skqt}!t;=T3&O`Q@0PMe{IGz$o zJ@k5D{9sYYCz|bnetp@c!gH{J_o)mbjA+OP?Q0V{U2>O>PDT{6(hs#H7$@GuV`K4> z6YVgy;B_HWUYoV`ORk|+(NJ@0oxxCUNO=6rQ0tUe_*rqml3AiKYb9;(i;)i@!yR6H zZf{-bGm4|F2Q01O-`;#SJ2bEg1B}m#&BW6Fl~SIh1JZ+U5{Ri)u7f)rr)=z2@I6T< z*ETkTVIy_Bc8NMSJGW-qyWxh5?rf||2aItk_SJ>*ZO$q0&r>AMTz=*@G+`7yg1G+h zjF!{VtMb)Foi-W*g;D>xZ~c|Iuu=ES_0-Dsdc)WTY@1?RqznKjlj)YcW-Pk#c2KQI zhe?r?9A1V1u*I9qhEsDAO8o)=RocAr#JI_{o>=Z|U4t}F+xG17V~XR(d^0B}5|p0p zqkBYk^YjgAogR8bJ9VFLKGw`_z_(sVcV##L6V)iO$w;me* zz)Fls8ex5|!=aI3O5G?sn(`bkr!f72^_=|?mlrM#)qd;^`_ZzOhyuRGUFco4ow%Kq zqkN)w@-Q9`GWZ}HAgHW#?8P3 zk6576fpz$?L#ElO+^zFNV(&%VhcPi)B`JP31V7NS#{(GZj!2Kp6!$b4^KW3O!OINtjmevmef< zvvcnuR@u)L9RzaUezyA!=DUxsQouur4gW{ILnQm?c25@VHul78?rBP&%T2Y{Ffj@Tl4jmr{kD()SlSQsPx&< z@{~=_hDN##NfLm?H7rxXLr8@!f9=Z~*!ro54QEgl4A@5bNi|fidkh2PJ7PItjSR~&DI)j+b~xp z$tEj+H{U9a0Fl1(QFf+IGO0H`s-^cB5+LW;&e2yJ?ixoR8SYr(zbWF}EwNOz^ zwSuq*JFLh$0VfaqgQEca$>p0YwK51xwN-ccajIp`?X++vZ@TO51p zUUR)!V!BsyM70xZS5;}#JftyJ9S%)=8AH3*Tso*p+qEoH#hb?IZI9zrbjv~zuw*uR zHYtEQ;?FI6N%+vcfB|*sw{>dD;-fx>VMDahkW6Y-N(rxpRpaHcjINcC^TlVHd(ObH zU3ZB^>ZjmSx#{-XCqJ)Q0Y|@qWU%{3`ZhQmhG{){PogXwe;M-dOc==eNVJh=8g|#b zpmS`|gOM$BpKVJguB8Kec5|hyq}>_9y=A-({F$zeQV1pFWOaynhuDhVkrD8aSMxRs zrnv?xbB6Qoj`I$clvW0{(rMl#o5J$e168a&!(x!NdttAx@|sUQx3jfe?7GR7b59ZLE=&Gs z@kLFc=b?WF$O%8)ASWQ#qRFn2=dcH|;`F?%(sXhoIU<-E9&NSSJ$u6GfpdkKfqdI; zge_mT6F2mQuSTS0`r$puZUD^<+DlT;T|O(*=?9TTBc}%Rm&>oYt2Zhe0N{- zye|by3f@lj;?ru9IF_|GK~B0p-1JMugAmu*>HK#SC(MIRXpfA2q#R7kHG~RvxQ=0} zG4KhFUApuNtjPX(PLH`Hcs^nw2@OlN-&md8SeIREu_XITs96e@Q(De&8vM0D=FQK- zqeC!RB%f=%Z#g6@GbBT{*H+0inBX;@kghgv!f{FxjluJIELoBK+xgS9&oHr9F{l+e z9X;fxU{VUu4oS- z?w6AIF`v7&nTMD8&9xogHzQ$9GQ(&RisEp{f%LkOd$v`PSTO}d)}hqSvLG4@Eu;T% zrPF=aCRXR3M_$daprCX34#_wxq0A#NVyhHq_gtzrnJGHfw$ILIgHw<2sORti6UJ?- zF!IeZJ{JNX{hV0iMxKj6meNXT*?qOPLbhUfmC(aJypxn$Eq z)#tge5recHgsF;4oi|*4@7|ERFHDNRNRc*`AVo2BP)`)(i(_Ur{kX;c5gvw#@Izf< zyilQNy*6@8Oo3RN2SD+cx@K{y+AwuxMl`gBY1L<04gDg|!br4EMJ}p_w~gI&KU`{! zqHM`L;U+&*GzW}m`9;nW_4qt)*wo}6^k;Ll$>|DCco(_FO-M-Wr5G`458rH?+bLGR z{gj<>U*_55J@)N4Lx?u(avfkcXh4PQ1L7Vl8htaM5UM`2eK=3+x4j$F{I=Jzz z7H4A&xpUS}QYW0TwLXJuA02NTnvY~&A50i^XU1#^Jb^{V={di3c}8x4!n?KKT;DV` zVm31-(;YTM!AeMfxmT5`NPUr{e=H)PnUjw~P)nvC*=?TGzl`3$p7I9ga--|*>Rj!u z7i13`bo)tvxk#6|H}JYKzqLTe{Mk&U{i@mZ*PcZ6gfJ1fLs+6;Ib7#VI@pKrX*1T*{HrDr zO2q-r`oe-xd!ahdLhf^vx-|`lw?v}1+34UxJhF2j@j?i@;z|m?7aU#X~ONzd;%_`IOgu#zHu-(kvxj?H=KY9mv zeYV#LZ^M~R4vkLkWIip#gwHke<@z*;zWcxeTA-8?U(9YtjvcV`dnAi4Rb8KCI(}1> zHa;&-4!fhNyOAoN8id7P`NMu$?GCskevBq;WFpD@yLPr)Rv=6>K|geTHUC+^04T~n zVp)Ra7kl87k8Q&t9j+i~;LJhDA_64KYJ+xJo5fBN^mW~K%ylb9fnT934s|LQso=I( zi2`+A?mA=WxcoHF2JSD`@pu}Yk7dDfI~1lz6O4oy$jo1JQu~1u@<1_hB56}G0`)EU zibeq<$A9(_^JQfE0bJyw9~oT!T662)*!F?J&$J6R2y5dACfzLfGyP$#y?vad;x;7Z zxV82&haW^B4-xkj7y<-k3*O;(EekHJ0T9a3gHVd_ zkl%yJ|62D7>STSnOUr+TanBdX|CtEQm%Yh`K1tA;{0Z2d1KaB6foo^eVe1JnykhSC zmQP^PJ>s@uPMn@Hf+RdOc;~~`lc+TB>%GL@tXl;%ObiAMAplHr3je=$4K5wGg`1Mk zl{YQrzTBp5;CODX6OAx@H3RCP8HQGxJ-N-Ll-;(tt(-TMFIPYqdi^jhld#zR;}YUf z_X7*s=toT(^(ucJv`SfHZ7DP6bD60o(9)XS{atS?VFfkRHlLUSg@hHXuE{8Z&M?zf zs5n>L-&x|zxwAM9W_8fSXB{Q;18@&B@}L4Z7~yKSpS&Jv1a1{KZ|ZAMYmw|5K3XHA zSKkgmi&wQp(DP;CYKSW@~1@R z@(;F)%5wmj$yU1M>IC9OrmNaUDHVHcW8yO|U0@BZ{{TpfdXLz)t4tW}Yc=uDz;soZ zeQmj_AC{WuNIz@mOB6&8`6TL^z>X^O>gfIGp=Cs2+zkr%b zyq(7ye--t#OlKUUTCco7=5pd*DURcw^r507`uLmti1e=!EN60-(Z#o0stDW{P2ZPpa|yBM zTD7)UM6h4PJw)!}8<`E-zBc=iD|xPR4HIko+~(a{=J%Gi4_C=!mO4ex@_=K}{^nS| z;>W1my@@vamZ~cuBXg9enm2?to!X(wtYV@&43y&`%LJ{hNo)3>zb(Z+r>Xmv)Jwx{ zUTrr`(lVi_UML}qZhE23gponu+quH}L5x-#uJ{|=P~OFLHI5=z_@#(;=<-tg*x zYk;Y`KqK2f><%Xpi0(?6OH70(?n%T<=cf4bUm(8_rNzHF3;F*W$kTGOGe_`^n4#acV!y~lkF|lZn6U{1b9BuLf^T)sW&E00j)LTNj{X-gg37V|!BRh62YS+;h z!U;u2Z+Bu2cwCzQ?fE}a?|1D2?(zk(r`m$3`y7|VBbIG=B63jPaexiyP^^CYi0|*; z0{_PY_Tk(3ipxbrWkz)1zJBge_+exCVIB|mDwGKOOLq9=Z~wi~PKF4H3N+drZwy%fdCjWTc^d&B5iA}Oc=+ho}r2_t75z3=%CS5m@7Vt;M}Sr8 zz|LT&(bFq26ICe3B_&X1| zzC9`OM_!}bIMIy_J}-ipFi&rA;krQt%6=^ZE@u}K0U$_%*SzuLYvPdCJTJOwJ-nvS z>DB;vO-^{tZA;NJ7jUFaj#D?`xLbcXuBG{+NR;&Cs*kxjM$s*otS-PqZQ3&TP#^*O zf0Tem_F)`;fK>Dm)=Ow9vlJtub?(;^ps=6>sLZ{l@{g~Hf!ExVvNmu9ye3DS1p!_Y z2d}yAI-a@{V?c_K7<~`N4gTS{Qfb#TabeEO`V0-lk6EO?*$FSTI%w{tKm>$-6oCOI zhhl==#Mw(5J@6urmUbc{ko;>Ah+IenJ`14WH8~#r@S1y&*L=|6U3!xVv*|m92GjzL z6P>xA*R36d_uvo54b#mN##Cl6CMSrYUZkCFx(^R^`@q~ofmU$*s1FwJ+0r?n7jah-Ie8s%eKwEsrVlz`S5S}k0bHW2C%1(hWMF&gl8=G#{C%qrP*D^RFRB#4{td`>QpvT;)D&ybCyGD`-y?XNR-fCkAw9#&)4Nov-zsl+W>hF;d?x zr3rI!Rc2Q9T%dZK+?xIEaSRxMeO<0P5xMTRqQdI0pPuUb=T9%*gox8r7+$+Vg^p@l0m!P6##AR^dZb(PSXExD2Lqo`BnVNo0yu5N#HG@;5 z7a9&EME!X0{Ywh`tK^)r+ejfhdHN+}ukwe?>mQQf%*Q(@4Nk1{) zVb5zRq~cWrPGd!#={82ekdP{`NbIU!6I7!{+RXjeGxP~DUV>AAt@1ZP1c`Ki&ET~x zF30JqI+G%-w`gt_o=RXfW#dqgzT@U36f2m^8`CAay*Xs^O@tW8j>YR9zg_Q84~$}k z<8oIjDo>A$iW(?lX=mvVE)@u?cj!0_60)7`y)y0TYzO_mk1F^RmD>Q~z5&({&lHaB zD>&Z1TCiR6y>7wjTvR;ZXtNO~Rf8oSW`}x@HwZ3<~>s@Q_=i49KWOn0@l&Odh z8UrO77lxgjSFCtQ+c6cD6H|BrLcPze`7`V zYZ1fWLVah0!(hw+E$N&QH~xKuQvaePr&0cJwdHikYtw3@nCIqsvgs>0L)jW+pEz?S zeoRtdklJH&Ky0a;tQuWXYc!GAJ53!;t#(>8H#<-|t85(muw>_Yur@GbmiFMR(PfXM zBWCKG-f6D8BbxOfS-7eR`!?qHAAPM%;tWpC9@>Be9_beCb}Tw=T0ml(bC}NVxuz<{ z(Y2=>3?54Eb3B!TKxuzBj+ba4hn1MNg1UW(g+;w4?yorpoW?DT)Y|d6giS^o!a;tY zU1COOKM~b6es#7*x)}WP$Nmy~T=HoyqsFj|i48}s&@Ko~4Gd@a(LyJ2cdJa{?Gb4e zN_*>B?h~SJ@>ykmNY2QblB`F2I62DB@mI>*Wm+I}ZIM#?Qr+fePlcbSRF*KTBXZrL zEyl0~(is$zp{zFhq`#nv?m`qdoJ(*B5kN$z`E$uHiCDkuO>^azPDa(1qXfPe+Umxm z6S70dg^d{B5Smb?qrIwC4h2VD6L2wU0xn6PCU{ocJ?ak6C4yQ<%2J4Og+XhQ&gh6B zR(2O5wYxyrE~PWd#giYUv9kwxr2*s1GQ>@8*}SczAHzNe1?t)RPUBkI+-_>&F1ZG zU3AIcmnbGkS)JYdaGE>@a4{w^ZKOi@vKoy zX_LO-jSXBqslkQ2h*sO(ScX_g@vI_iq;*tXSV1WwcMgHfj`8uX#x|aTC`DY`3V-~R z8MC=PL;`nc;9p@IPO0`6F_0Ald+DYZia@}}4JC5$V|fidMyR3zQ3yi>k|#cDc+Q{m zMM1l@$xm-vx*}_+D9|c5sI{kgq;)!&18B;Y2PMRv=#FCJBiMT_J72z}Wi&CKL=RGm zD!=H4+Wf=9uSE^lKEbfGMFI>3y9tTf?)FTQkPiZsFTP%lJ97@AKoCJH%Si88EiiyL z7-Pk1UW+xMeuA|A#4zJU`7O&kt%?Ltm=kUui|a_y44}+^H>syr?KDd8hBH`%z@7nt zuT%_J$AMlIZx|zV}&ksqdwDoi4k+T$Nk%H zUbPYRe{{a=DRBNgUBqIrWKZ;hPWpx$e%X6_hWVhD3sEtkOUh*yGv6x?7eMx4iJJYx zeb$fF46&pj#Un}pl4Bp|GYjVk%FN1~h}!OJx8M5b62?RoQr;9Ihy{d=%`Q1c*IVE5 zo;MPtFzfO9Ji!H2^<^C@TBXw_9&c;7Vr~X_aXACZJc!+yK5>{ZB^ghdl)M)pZCAkO zwVGP#d^c~-mS2yHOtNftWCvK?;2xsux#v~gxofRNdJ?X|WEkDM0TGD!fgnM}eAsuJ z@69pRdDys~$~-N{@QysvJHGR9}FS$qT3-iWBioD}T?6tT+SrvJdz zqXdzivJ#A6w;6mR1X;j|j*uJ~TDY9oCImN;6}oWH zyQ|P?=s%kDgW`t5DPTXC*pj1v{&lkKhk;GM!7o{|I8bGcg(Psc+3Qa*tyAxo^sS^e z|MX_~Z2Q(PSi84(B*CRL_*OqIZ z?^u6=<7Rb(?|6`JVtgO#q6PB>UAPCW5SGnSxEc?yg7mIh;1QTH(ZPlse*w>BqPqQ#s3wO1o|yY z5rZeq_fb>zEIykKn=DWzRcBGTLB28(JicHEgPvPh#p8G)J@ZeN#+p@C(W2bah^gHO zQ*T>uxSY?o1LNz~>I{bS*jey611yej0A=ti-~a-J<{*WAtB;n#;R0!e4gli;KAG^8 zna>DE5Pi}!3*3*N9Qt@bU#hyBev+R4mDK?cu(?5dA9 literal 0 HcmV?d00001 diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index def34bb066..e338918550 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -18,8 +18,14 @@ export default async function Page({ }: { searchParams: Record; }) { - const { loginName, checkAfter, authRequestId, organization, sessionId } = - searchParams; + const { + loginName, + checkAfter, + force, + authRequestId, + organization, + sessionId, + } = searchParams; const sessionWithData = sessionId ? await loadSessionById(sessionId, organization) diff --git a/apps/login/src/app/(login)/passkey/add/page.tsx b/apps/login/src/app/(login)/passkey/add/page.tsx index beec6902c6..15aaa6436a 100644 --- a/apps/login/src/app/(login)/passkey/add/page.tsx +++ b/apps/login/src/app/(login)/passkey/add/page.tsx @@ -10,18 +10,17 @@ export default async function Page({ }: { searchParams: Record; }) { - const { loginName, promptPasswordless, organization, authRequestId } = - searchParams; + const { loginName, prompt, organization, authRequestId } = searchParams; const session = await loadMostRecentSession({ loginName, organization, }); - const title = !!promptPasswordless + const title = !!prompt ? "Authenticate with a passkey" : "Use your passkey to confirm it's really you"; - const description = !!promptPasswordless + const description = !!prompt ? "When set up, you will be able to authenticate without a password." : "Your device will ask for your fingerprint, face, or screen lock"; @@ -68,7 +67,7 @@ export default async function Page({ {session?.id && ( diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 7476eb3967..9b35b5c70c 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -4,14 +4,14 @@ import Alert from "@/ui/Alert"; import DynamicTheme from "@/ui/DynamicTheme"; import PasswordForm from "@/ui/PasswordForm"; import UserAvatar from "@/ui/UserAvatar"; +import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; export default async function Page({ searchParams, }: { searchParams: Record; }) { - const { loginName, organization, promptPasswordless, authRequestId, alt } = - searchParams; + const { loginName, organization, authRequestId, alt } = searchParams; const sessionFactors = await loadMostRecentSession({ loginName, @@ -51,7 +51,9 @@ export default async function Page({ authRequestId={authRequestId} organization={organization} loginSettings={loginSettings} - promptPasswordless={promptPasswordless === "true"} + promptPasswordless={ + loginSettings?.passkeysType === PasskeysType.ALLOWED + } isAlternative={alt === "true"} /> )} diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 20475e4104..a566b30ad8 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -1,6 +1,5 @@ "use server"; -import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; import { redirect } from "next/navigation"; @@ -108,13 +107,6 @@ export async function sendLoginname(command: SendLoginnameCommand) { command.organization ?? session.factors?.user?.organizationId; } - if ( - loginSettings?.passkeysType && - loginSettings?.passkeysType === PasskeysType.ALLOWED - ) { - paramsPassword.promptPasswordless = `true`; - } - if (command.authRequestId) { paramsPassword.authRequestId = command.authRequestId; } @@ -164,10 +156,6 @@ export async function sendLoginname(command: SendLoginnameCommand) { // user has no passkey setup and login settings allow passkeys const paramsPasswordDefault: any = { loginName: command.loginName }; - if (loginSettings?.passkeysType === PasskeysType.ALLOWED) { - paramsPasswordDefault.promptPasswordless = `true`; // PasskeysType.PASSKEYS_TYPE_ALLOWED, - } - if (command.authRequestId) { paramsPasswordDefault.authRequestId = command.authRequestId; } @@ -235,10 +223,6 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (loginSettings?.ignoreUnknownUsernames) { const paramsPasswordDefault: any = { loginName: command.loginName }; - if (loginSettings?.passkeysType === PasskeysType.ALLOWED) { - paramsPasswordDefault.promptPasswordless = `true`; - } - if (command.authRequestId) { paramsPasswordDefault.authRequestId = command.authRequestId; } diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index 4afa89c320..d7b50979ca 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -153,29 +153,10 @@ export default function PasswordForm({ } return router.push(`/mfa?` + params); - } else if ( - submitted.factors && - !submitted.factors.webAuthN && // if session was not verified with a passkey - promptPasswordless && // if explicitly prompted due policy - !isAlternative // escaped if password was used as an alternative method - ) { - const params = new URLSearchParams({ - loginName: submitted.factors.user.loginName, - promptPasswordless: "true", - }); - - if (authRequestId) { - params.append("authRequestId", authRequestId); - } - - if (organization) { - params.append("organization", organization); - } - - return router.push(`/passkey/add?` + params); } else if (loginSettings?.forceMfa && !availableSecondFactors.length) { const params = new URLSearchParams({ loginName: submitted.factors.user.loginName, + force: "true", // this defines if the mfa is forced in the settings checkAfter: "true", // this defines if the check is directly made after the setup }); @@ -189,6 +170,26 @@ export default function PasswordForm({ // TODO: provide a way to setup passkeys on mfa page? return router.push(`/mfa/set?` + params); + } else if ( + submitted.factors && + !submitted.factors.webAuthN && // if session was not verified with a passkey + promptPasswordless && // if explicitly prompted due policy + !isAlternative // escaped if password was used as an alternative method + ) { + const params = new URLSearchParams({ + loginName: submitted.factors.user.loginName, + prompt: "true", + }); + + if (authRequestId) { + params.append("authRequestId", authRequestId); + } + + if (organization) { + params.append("organization", organization); + } + + return router.push(`/passkey/add?` + params); } else if (authRequestId && submitted.sessionId) { const params = new URLSearchParams({ sessionId: submitted.sessionId, From afedbe5ab3bf377b337ac66ca353b84d9858f1ca Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 16 Sep 2024 11:56:50 +0200 Subject: [PATCH 177/640] otp page --- apps/login/readme.md | 23 ++++++++++++++++++++++- apps/login/screenshots/otp.png | Bin 0 -> 84122 bytes 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 apps/login/screenshots/otp.png diff --git a/apps/login/readme.md b/apps/login/readme.md index cd355a5485..15386cb266 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -61,6 +61,8 @@ Requests to the APIs made: - `listUsers(org?)` - `listAuthenticationMethodTypes` - `getOrgsByDomain` +- `createSession()` +- `getSession()` After a loginname is entered, a `listUsers` request is made using the loginName query to identify already registered users. @@ -79,7 +81,7 @@ If no previous condition is met we throw an error stating the user was not found **EXCEPTIONS:** If the outcome after this order produces a no authentication methods found, or user not found, we check whether `loginSettings?.ignoreUnknownUsernames` is set to `true` as in this case we redirect to the /password page regardless (to not leak information about a registered user). -> NOTE: We ignore `loginSettings.allowExternalIdp` as the information whether IDPs are available comes as response from `getActiveIdentityProviders(org?)`. +> NOTE: We ignore `loginSettings.allowExternalIdp` as the information whether IDPs are available comes as response from `getActiveIdentityProviders(org?)`. If a user has a cookie for the same loginname, a new session is created regardless and overwrites the old session. The old session is not deleted from the login as for now. ### /password @@ -93,6 +95,8 @@ Requests to the APIs made: - `getLoginSettings(org?)` - `getBrandingSettings(org?)` - `listAuthenticationMethodTypes` +- `getSession()` +- `updateSession()` **MFA AVAILABLE:** After the password has been submitted, additional authentication Methods are loaded. If the user has set up an additional **single** second factor, it is redirected to add the next factor. Depending on the available method he is redirected to `/otp/time-based`,`/otp/sms?`, `/otp/email?` or `/u2f?`. If the user has multiple second factors, he is redirected to `/mfa` to select his preferred method to continue. @@ -102,3 +106,20 @@ If the user has set up an additional **single** second factor, it is redirected **PROMPT PASSKEY** If the settings do not enforce MFA, we check if passkeys are allowed with `loginSettings?.passkeysType === PasskeysType.ALLOWED` and redirect the user to `/passkey/add` if no passkeys are setup. This step can be skipped. If none of the previous conditions apply, we continue to sign in. + +### /otp/[method] + +/otp/[method] + +This page shows a code field to check an otp method. The session of the user is then hydrated with the respective factor. Supported methods are `time-based`, `sms` and `email`. + +Requests to the APIs made: + +- `getBrandingSettings(org?)` +- `getSession()` +- `updateSession()` + +If `email` or `sms` is requested as method, the current session of the user is updated to request the challenge. This will trigger an email or sms which can be entered in the code field. +The `time-based` (TOTP) method does not require a trigger, therefore no `updateSession()` is performed and no resendLink under the code field is shown. + +The submission of the code updates the session and continues to sign in the user. diff --git a/apps/login/screenshots/otp.png b/apps/login/screenshots/otp.png new file mode 100644 index 0000000000000000000000000000000000000000..3818a5ad5faf73763cdc8a6cbf61e73ae41283cd GIT binary patch literal 84122 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 Date: Mon, 16 Sep 2024 12:04:43 +0200 Subject: [PATCH 178/640] u2f, passkey page --- apps/login/cypress/integration/login.cy.ts | 7 ++-- apps/login/readme.md | 32 ++++++++++++++++++- .../app/(login)/passkey/{login => }/page.tsx | 0 apps/login/src/lib/server/loginname.ts | 6 ++-- apps/login/src/ui/RegisterPasskey.tsx | 2 +- 5 files changed, 36 insertions(+), 11 deletions(-) rename apps/login/src/app/(login)/passkey/{login => }/page.tsx (100%) diff --git a/apps/login/cypress/integration/login.cy.ts b/apps/login/cypress/integration/login.cy.ts index d4edf570d6..1d6472d5e6 100644 --- a/apps/login/cypress/integration/login.cy.ts +++ b/apps/login/cypress/integration/login.cy.ts @@ -156,12 +156,9 @@ describe("login", () => { }, }); }); - it("should redirect a user with passwordless authentication to /passkey/login", () => { + it("should redirect a user with passwordless authentication to /passkey", () => { cy.visit("/loginname?loginName=john%40zitadel.com&submit=true"); - cy.location("pathname", { timeout: 10_000 }).should( - "eq", - "/passkey/login", - ); + cy.location("pathname", { timeout: 10_000 }).should("eq", "/passkey"); }); }); }); diff --git a/apps/login/readme.md b/apps/login/readme.md index 15386cb266..a085df7ea3 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -68,7 +68,7 @@ After a loginname is entered, a `listUsers` request is made using the loginName **USER FOUND:** If only one user is found, we query `listAuthenticationMethodTypes` to identify future steps. If no authentication methods are found, we render an error stating: _User has no available authentication methods._ (exception see below.) -Now if only one method is found, we continue with the corresponding step (/password, /passkey/login). +Now if only one method is found, we continue with the corresponding step (/password, /passkey). If multiple methods are set, we prefer passkeys over any other method, so we redirect to /passkey, second option is IDP, and third is password. If password is the next step, we check `loginSettings.passkeysType` for PasskeysType.ALLOWED, and prompt the user to setup passkeys afterwards. @@ -123,3 +123,33 @@ If `email` or `sms` is requested as method, the current session of the user is u The `time-based` (TOTP) method does not require a trigger, therefore no `updateSession()` is performed and no resendLink under the code field is shown. The submission of the code updates the session and continues to sign in the user. + +### /u2f + +/u2f + +This page requests a webAuthN challenge for the user and updates the session afterwards. + +Requests to the APIs made: + +- `getBrandingSettings(org?)` +- `getSession()` +- `updateSession()` + +When updating the session for the webAuthN challenge, we set `userVerificationRequirement` to `UserVerificationRequirement.DISCOURAGED` as this will request the webAuthN method as second factor and not as primary method. +After updating the session, the user is signed in. + +### /passkey + +/passkey + +This page requests a webAuthN challenge for the user and updates the session afterwards. + +Requests to the APIs made: + +- `getBrandingSettings(org?)` +- `getSession()` +- `updateSession()` + +When updating the session for the webAuthN challenge, we set `userVerificationRequirement` to `UserVerificationRequirement.REQUIRED` as this will request the webAuthN method as primary method to login. +After updating the session, the user is signed in. diff --git a/apps/login/src/app/(login)/passkey/login/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx similarity index 100% rename from apps/login/src/app/(login)/passkey/login/page.tsx rename to apps/login/src/app/(login)/passkey/page.tsx diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index a566b30ad8..66fd2d824a 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -123,9 +123,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { command.organization ?? session.factors?.user?.organizationId; } - return redirect( - "/passkey/login?" + new URLSearchParams(paramsPasskey), - ); + return redirect("/passkey?" + new URLSearchParams(paramsPasskey)); } } else { // prefer passkey in favor of other methods @@ -144,7 +142,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { command.organization ?? session.factors?.user?.organizationId; } - return redirect("/passkey/login?" + new URLSearchParams(passkeyParams)); + return redirect("/passkey?" + new URLSearchParams(passkeyParams)); } else if ( methods.authMethodTypes.includes(AuthenticationMethodType.IDP) ) { diff --git a/apps/login/src/ui/RegisterPasskey.tsx b/apps/login/src/ui/RegisterPasskey.tsx index 2b5c1b12e4..9391e62f63 100644 --- a/apps/login/src/ui/RegisterPasskey.tsx +++ b/apps/login/src/ui/RegisterPasskey.tsx @@ -151,7 +151,7 @@ export default function RegisterPasskey({ // params.set("altPassword", ${false}); // without setting altPassword this does not allow password // params.set("loginName", resp.loginName); - router.push("/passkey/login?" + params); + router.push("/passkey?" + params); } else { router.push("/accounts?" + params); } From db7eec6cad7bde1c6e31260008300e225f676fe1 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 16 Sep 2024 13:57:08 +0200 Subject: [PATCH 179/640] try catch --- apps/login/src/ui/UsernameForm.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/login/src/ui/UsernameForm.tsx b/apps/login/src/ui/UsernameForm.tsx index 68beacf7e9..0baac56768 100644 --- a/apps/login/src/ui/UsernameForm.tsx +++ b/apps/login/src/ui/UsernameForm.tsx @@ -46,13 +46,18 @@ export default function UsernameForm({ async function submitLoginName(values: Inputs, organization?: string) { setLoading(true); - const res = await sendLoginname({ - loginName: values.loginName, - organization, - authRequestId, - }).catch((error: Error) => { - setError(error.message ?? "An internal error occurred"); - }); + let res; + try { + res = await sendLoginname({ + loginName: values.loginName, + organization, + authRequestId, + }); + } catch (error: unknown) { + if (error instanceof Error) { + setError(error.message ?? "An internal error occurred"); + } + } setLoading(false); From 7126f86c337e7247aafc2c858940130ffd8621c9 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 16 Sep 2024 14:03:39 +0200 Subject: [PATCH 180/640] render server errors --- apps/login/next.config.mjs | 3 +++ apps/login/src/ui/UsernameForm.tsx | 19 +++++++------------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/apps/login/next.config.mjs b/apps/login/next.config.mjs index 94df2408a0..099d37e81d 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -33,6 +33,9 @@ const secureHeaders = [ const nextConfig = { reactStrictMode: true, // Recommended for the `pages` directory, default in `app`. swcMinify: true, + experimental: { + serverComponentsErrorOverride: true, + }, images: { remotePatterns: [ { diff --git a/apps/login/src/ui/UsernameForm.tsx b/apps/login/src/ui/UsernameForm.tsx index 0baac56768..68beacf7e9 100644 --- a/apps/login/src/ui/UsernameForm.tsx +++ b/apps/login/src/ui/UsernameForm.tsx @@ -46,18 +46,13 @@ export default function UsernameForm({ async function submitLoginName(values: Inputs, organization?: string) { setLoading(true); - let res; - try { - res = await sendLoginname({ - loginName: values.loginName, - organization, - authRequestId, - }); - } catch (error: unknown) { - if (error instanceof Error) { - setError(error.message ?? "An internal error occurred"); - } - } + const res = await sendLoginname({ + loginName: values.loginName, + organization, + authRequestId, + }).catch((error: Error) => { + setError(error.message ?? "An internal error occurred"); + }); setLoading(false); From 4077353048be3aa8bc425b7fdeaa87f9e48be07c Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 16 Sep 2024 14:41:38 +0200 Subject: [PATCH 181/640] return error as object from server actions --- apps/login/next.config.mjs | 3 --- apps/login/src/lib/server/loginname.ts | 13 +++++++------ apps/login/src/ui/UsernameForm.tsx | 6 +++++- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/login/next.config.mjs b/apps/login/next.config.mjs index 099d37e81d..94df2408a0 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -33,9 +33,6 @@ const secureHeaders = [ const nextConfig = { reactStrictMode: true, // Recommended for the `pages` directory, default in `app`. swcMinify: true, - experimental: { - serverComponentsErrorOverride: true, - }, images: { remotePatterns: [ { diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 66fd2d824a..18145c614d 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -79,7 +79,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { ); if (!session.factors?.user?.id) { - throw Error("Could not create session for user"); + return { error: "Could not create session for user" }; } const methods = await listAuthenticationMethodTypes( @@ -87,9 +87,10 @@ export async function sendLoginname(command: SendLoginnameCommand) { ); if (!methods.authMethodTypes || !methods.authMethodTypes.length) { - throw Error( - "User has no available authentication methods. Contact your administrator to setup authentication for the requested user.", - ); + return { + error: + "User has no available authentication methods. Contact your administrator to setup authentication for the requested user.", + }; } if (methods.authMethodTypes.length == 1) { @@ -175,7 +176,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { // TODO: do we need to handle login hints for IDPs here? await redirectUserToSingleIDPIfAvailable(); - throw Error("Could not find user"); + return { error: "Could not find user" }; } else if ( loginSettings?.allowRegister && loginSettings?.allowUsernamePassword @@ -232,5 +233,5 @@ export async function sendLoginname(command: SendLoginnameCommand) { return redirect("/password?" + new URLSearchParams(paramsPasswordDefault)); } - throw Error("Could not find user"); + return { error: "Could not find user" }; } diff --git a/apps/login/src/ui/UsernameForm.tsx b/apps/login/src/ui/UsernameForm.tsx index 68beacf7e9..93d21bcfdf 100644 --- a/apps/login/src/ui/UsernameForm.tsx +++ b/apps/login/src/ui/UsernameForm.tsx @@ -51,9 +51,13 @@ export default function UsernameForm({ organization, authRequestId, }).catch((error: Error) => { - setError(error.message ?? "An internal error occurred"); + setError("An internal error occurred"); }); + if (res?.error) { + setError(res.error); + } + setLoading(false); return res; From 604c56758bb4b71be033720193b11b7f6cc602de Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 16 Sep 2024 15:57:42 +0200 Subject: [PATCH 182/640] typing server action responses --- apps/login/src/lib/server/password.ts | 2 +- apps/login/src/lib/server/register.ts | 11 ++++++++- apps/login/src/lib/server/u2f.ts | 13 +++++++---- apps/login/src/lib/zitadel.ts | 2 +- apps/login/src/ui/PasswordForm.tsx | 7 +++++- .../src/ui/RegisterFormWithoutPassword.tsx | 11 ++++++--- apps/login/src/ui/RegisterU2F.tsx | 23 ++++++++++++------- apps/login/src/ui/SetPasswordForm.tsx | 21 +++++++++++------ apps/login/src/ui/UsernameForm.tsx | 1 + 9 files changed, 65 insertions(+), 26 deletions(-) diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 88f3c628d2..fdd3b448e5 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -18,7 +18,7 @@ export async function resetPassword(command: ResetPasswordCommand) { users.details.totalResult !== BigInt(1) || !users.result[0].userId ) { - throw Error("Could not find user"); + return { error: "Could not find user" }; } const userId = users.result[0].userId; diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index c6ed2b7a9d..0c906c60c6 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -2,6 +2,7 @@ import { addHumanUser } from "@/lib/zitadel"; import { createSessionForUserIdAndUpdateCookie } from "@/utils/session"; +import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; type RegisterUserCommand = { email: string; @@ -11,6 +12,13 @@ type RegisterUserCommand = { organization?: string; authRequestId?: string; }; + +export type RegisterUserResponse = { + userId: string; + sessionId: string; + factors: Factors | undefined; +}; + export async function registerUser(command: RegisterUserCommand) { const human = await addHumanUser({ email: command.email, @@ -19,8 +27,9 @@ export async function registerUser(command: RegisterUserCommand) { password: command.password ? command.password : undefined, organization: command.organization, }); + if (!human) { - throw Error("Could not create user"); + return { error: "Could not create user" }; } return createSessionForUserIdAndUpdateCookie( diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index e8a780449c..3f61f526fa 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -23,6 +23,10 @@ export async function addU2F(command: RegisterU2FCommand) { sessionId: command.sessionId, }); + if (!sessionCookie) { + return { error: "Could not get session" }; + } + const session = await getSession({ sessionId: sessionCookie.id, sessionToken: sessionCookie.token, @@ -31,14 +35,15 @@ export async function addU2F(command: RegisterU2FCommand) { const domain = headers().get("host"); if (!domain) { - throw Error("Could not get domain"); + return { error: "Could not get domain" }; } const userId = session?.session?.factors?.user?.id; - if (!userId) { - throw Error("Could not get session"); + if (!session || !userId) { + return { error: "Could not get session" }; } + return registerU2F(userId, domain); } @@ -65,7 +70,7 @@ export async function verifyU2F(command: VerifyU2FCommand) { const userId = session?.session?.factors?.user?.id; if (!userId) { - throw new Error("Could not get session"); + return { error: "Could not get session" }; } const req = create(VerifyU2FRegistrationRequestSchema, { diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 3bf51753e5..43715a94cb 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -451,7 +451,7 @@ export function createUser( * @param userId the id of the user where the email should be set * @returns the newly set email */ -export async function passwordReset(userId: string): Promise { +export async function passwordReset(userId: string) { return userService.passwordReset( { userId, diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index d7b50979ca..919e56c774 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -77,10 +77,15 @@ export default function PasswordForm({ loginName, organization, }).catch((error: Error) => { + console.error(error); setLoading(false); - setError(error.message ?? "Could not reset password"); + setError("Could not reset password"); }); + if (response && "error" in response) { + setError(response.error); + } + setLoading(false); if (response) { diff --git a/apps/login/src/ui/RegisterFormWithoutPassword.tsx b/apps/login/src/ui/RegisterFormWithoutPassword.tsx index e5e7687723..64999f75f4 100644 --- a/apps/login/src/ui/RegisterFormWithoutPassword.tsx +++ b/apps/login/src/ui/RegisterFormWithoutPassword.tsx @@ -1,6 +1,6 @@ "use client"; -import { registerUser } from "@/lib/server/register"; +import { registerUser, RegisterUserResponse } from "@/lib/server/register"; import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; import { useRouter } from "next/navigation"; import { useState } from "react"; @@ -63,10 +63,15 @@ export default function RegisterFormWithoutPassword({ lastName: values.lastname, organization: organization, }).catch((error) => { - setError(error.message ?? "Could not register user"); + console.error(error); + setError("Could not register user"); setLoading(false); }); + if (response && "error" in response) { + setError(response.error); + } + setLoading(false); return response; @@ -89,7 +94,7 @@ export default function RegisterFormWithoutPassword({ if (withPassword) { return router.push(`/register?` + new URLSearchParams(registerParams)); } else { - const session = await submitAndRegister(value); + const session = (await submitAndRegister(value)) as RegisterUserResponse; const params = new URLSearchParams({}); if (session?.factors?.user?.loginName) { diff --git a/apps/login/src/ui/RegisterU2F.tsx b/apps/login/src/ui/RegisterU2F.tsx index 9a04120590..d117eaeed2 100644 --- a/apps/login/src/ui/RegisterU2F.tsx +++ b/apps/login/src/ui/RegisterU2F.tsx @@ -2,6 +2,7 @@ import { addU2F, verifyU2F } from "@/lib/server/u2f"; import { coerceToArrayBuffer, coerceToBase64Url } from "@/utils/base64"; +import { RegisterU2FResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { useRouter } from "next/navigation"; import { useState } from "react"; import Alert from "./Alert"; @@ -9,8 +10,6 @@ import BackButton from "./BackButton"; import { Button, ButtonVariants } from "./Button"; import { Spinner } from "./Spinner"; -type Inputs = {}; - type Props = { sessionId: string; authRequestId?: string; @@ -41,8 +40,9 @@ export default function RegisterU2F({ publicKeyCredential, sessionId, }).catch((error: Error) => { + console.error(error); setLoading(false); - setError(error.message); + setError("An error on verifying passkey occurred"); }); setLoading(false); @@ -55,20 +55,27 @@ export default function RegisterU2F({ setLoading(true); const response = await addU2F({ sessionId, - }).catch((error) => { + }).catch((error: Error) => { + console.error(error); setLoading(false); - setError(error.message); + setError("An error on registering passkey"); }); - if (!response) { + if (response && "error" in response && response?.error) { + setError(response?.error); + } + + if (!response || "u2fId" in response) { setLoading(false); setError("An error on registering passkey"); return; } - const u2fId = response?.u2fId; + const u2fResponse = response as unknown as RegisterU2FResponse; + + const u2fId = u2fResponse.u2fId; const options: CredentialCreationOptions = - (response?.publicKeyCredentialCreationOptions as CredentialCreationOptions) ?? + (u2fResponse?.publicKeyCredentialCreationOptions as CredentialCreationOptions) ?? {}; if (options.publicKey) { diff --git a/apps/login/src/ui/SetPasswordForm.tsx b/apps/login/src/ui/SetPasswordForm.tsx index d61c5f0761..c657de0b02 100644 --- a/apps/login/src/ui/SetPasswordForm.tsx +++ b/apps/login/src/ui/SetPasswordForm.tsx @@ -1,6 +1,6 @@ "use client"; -import { registerUser } from "@/lib/server/register"; +import { registerUser, RegisterUserResponse } from "@/lib/server/register"; import { lowerCaseValidator, numberValidator, @@ -66,9 +66,14 @@ export default function SetPasswordForm({ authRequestId: authRequestId, password: values.password, }).catch((error: Error) => { - setError(error.message ?? "Could not register user"); + console.error(error); + setError("Could not register user"); }); + if (response && "error" in response) { + setError(response.error); + } + setLoading(false); if (!response) { @@ -76,10 +81,12 @@ export default function SetPasswordForm({ return; } - const params = new URLSearchParams({ userId: response.userId }); + const userReponse = response as RegisterUserResponse; - if (response.factors?.user?.loginName) { - params.append("loginName", response.factors.user.loginName); + const params = new URLSearchParams({ userId: userReponse.userId }); + + if (userReponse.factors?.user?.loginName) { + params.append("loginName", userReponse.factors.user.loginName); } if (authRequestId) { params.append("authRequestId", authRequestId); @@ -87,8 +94,8 @@ export default function SetPasswordForm({ if (organization) { params.append("organization", organization); } - if (response && response.sessionId) { - params.append("sessionId", response.sessionId); + if (userReponse && userReponse.sessionId) { + params.append("sessionId", userReponse.sessionId); } return router.push(`/verify?` + params); diff --git a/apps/login/src/ui/UsernameForm.tsx b/apps/login/src/ui/UsernameForm.tsx index 93d21bcfdf..e08744acab 100644 --- a/apps/login/src/ui/UsernameForm.tsx +++ b/apps/login/src/ui/UsernameForm.tsx @@ -51,6 +51,7 @@ export default function UsernameForm({ organization, authRequestId, }).catch((error: Error) => { + console.error(error); setError("An internal error occurred"); }); From f7a8b4a17a72b55a9c0969189700686dba1104f3 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 16 Sep 2024 16:07:27 +0200 Subject: [PATCH 183/640] no u2fId in response --- apps/login/src/ui/RegisterU2F.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/ui/RegisterU2F.tsx b/apps/login/src/ui/RegisterU2F.tsx index d117eaeed2..3b9c2e55b8 100644 --- a/apps/login/src/ui/RegisterU2F.tsx +++ b/apps/login/src/ui/RegisterU2F.tsx @@ -65,7 +65,7 @@ export default function RegisterU2F({ setError(response?.error); } - if (!response || "u2fId" in response) { + if (!response || !("u2fId" in response)) { setLoading(false); setError("An error on registering passkey"); return; From fdb2711af07e9c5112175ee566ce167c411054ed Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 17 Sep 2024 09:41:20 +0200 Subject: [PATCH 184/640] screenshots --- apps/login/readme.md | 15 +++++++++++++++ apps/login/screenshots/mfa.png | Bin 0 -> 104053 bytes apps/login/screenshots/mfaset.png | Bin 0 -> 116794 bytes apps/login/screenshots/passkey.png | Bin 0 -> 86883 bytes apps/login/screenshots/u2f.png | Bin 0 -> 76779 bytes 5 files changed, 15 insertions(+) create mode 100644 apps/login/screenshots/mfa.png create mode 100644 apps/login/screenshots/mfaset.png create mode 100644 apps/login/screenshots/passkey.png create mode 100644 apps/login/screenshots/u2f.png diff --git a/apps/login/readme.md b/apps/login/readme.md index a085df7ea3..fac7969afa 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -153,3 +153,18 @@ Requests to the APIs made: When updating the session for the webAuthN challenge, we set `userVerificationRequirement` to `UserVerificationRequirement.REQUIRED` as this will request the webAuthN method as primary method to login. After updating the session, the user is signed in. + +### /mfa/set + +/mfa/set + +This page requests a webAuthN challenge for the user and updates the session afterwards. + +Requests to the APIs made: + +- `getBrandingSettings(org?)` +- `getSession()` +- `updateSession()` + +When updating the session for the webAuthN challenge, we set `userVerificationRequirement` to `UserVerificationRequirement.REQUIRED` as this will request the webAuthN method as primary method to login. +After updating the session, the user is signed in. diff --git a/apps/login/screenshots/mfa.png b/apps/login/screenshots/mfa.png new file mode 100644 index 0000000000000000000000000000000000000000..1fd73f205c7c93d5f937db766f1e185d4b7a93be GIT binary patch literal 104053 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

CT37;UN9RMUH1iCB_8&;Y)H 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®|6fdP2w<7l)dU*F1c2uv% zA&rJg3ia1*cLbyaD=7VnBUr)2bTQGpcI112YYdZ$IF7@%Lhg3M9SRF5%Ig3*}A%mb92mGSRZYblJK+SO2z=?#W?&WQLPP7Tp z25M$C@^Zxku8aAlAGEw843okt@I`T zx_w9fJ1+H_onxX-=*0rZ=Sfua%#J_@-b0GX=J}Okljo&zI7|X2Y!~4N?85M z%&K=Yyt&h6c5Hf^F9^+Nu{gK$1AP+YqANIS|k#V+*t9FG9bw$-Cnk^x954%U|rID;HI8%E2Ui;Hch#LQ?;Xr9-3lU z-cuoGSRCu=m|EWCjI6Pl^n$q_uG}ge%aoD5q|Uuo82N;gpA5{B33^EV2H6cC)r;hC z;(npiB6&RD!kE;x zgU?tPIt_6jp4N;vAHzl~3!;&mg|qrQ;J3XdT}-9F9#vrbCC+oX9oD0XBk=%D+6xTHS#juK41W-cF>Ce zJ2POiX5n+-wYoNR)(fjVQmZAx`ETx-YB`}0_ut0C^ zIV==7mTZijrbUp{6mrL1G3o3{UX@v1aSsj;rLYdvPcZd3ycvGGbSzWazF;mS*-U16 zk0_IC7|yWh=0&PLwF+5Q{kGZ{N0FnWDOthc<#Q_UTI8mf#krO>(s!u&oMjIgE9KcV zImUw7vV)I}6wdA>0B-U*-RVc z{Gcan0Su(bc^`LLT5dk}<@?;p>*Zh9$^sLx=pBAtlQXjOZSz>AwyPi~xj>d@Vmh0S zf8{qf&$qw#`y;D}PhbAN{g3ZFaIVu8YpzNN?I51+M70y^o&Q;mz^ zx^6O6cNaeH|MVfx38{{5Y-8yQ{bBLNj#qGRVQ`>9WaY{DtuqSWyben%szy$5OSt!B zeSCD|`cnHHEt`Q&)HFj~>ldNj@~L6#i*t{CJ$>o~O37~17_M9L#kgYb>-gul{{6ex zuMLconD-}lZ;kpdv-HL8vH13P4KzP2OASBav3N=c za?)MN#HG4&&RLaUgUmF|b@MtFoxJMihV0P?Gnl$mk6*ESw-=aFUe7HuZ=EdS=84?h z`EUwo#(JOfT)hy3oo3#m{vjV#$^_wSIe?yY*w&!rxh>=NPZ6J6ar19(4z`%-o2r1E zl=*}LG%Pz_r?6?ym~$qGkAG2xQN#}yWY2^P2B=huZoRzA?ckFw*NQ)7iUg`<&Or*n z2fX3|GL?tx8Pw$B52i3Z^X1T9w1;!`8%dPcV)^i&U&Al|ukSr8Nd_SBboFyt=akR{0Dg(J A5&!@I literal 0 HcmV?d00001 diff --git a/apps/login/screenshots/mfaset.png b/apps/login/screenshots/mfaset.png new file mode 100644 index 0000000000000000000000000000000000000000..c00ee4edf572b86ab49cedc8e530db94e11b0e7a GIT binary patch literal 116794 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 C~;JDzVo*d8hPAX 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 literal 0 HcmV?d00001 diff --git a/apps/login/screenshots/passkey.png b/apps/login/screenshots/passkey.png new file mode 100644 index 0000000000000000000000000000000000000000..7a5686c736b4c6459c1065ca8a5a2adb896cd314 GIT binary patch literal 86883 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

L#9vymg3EdaJpF-ds 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�kd_=iGXRX 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 Date: Tue, 17 Sep 2024 10:12:03 +0200 Subject: [PATCH 185/640] fix passkey retry, cleanup mfa set --- apps/login/readme.md | 14 +- apps/login/src/app/(login)/mfa/set/page.tsx | 66 ++++---- .../src/ui/ChooseSecondFactorToSetup.tsx | 52 ++++--- apps/login/src/ui/LoginPasskey.tsx | 141 ++++++++++-------- 4 files changed, 151 insertions(+), 122 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index fac7969afa..d59b172d11 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -158,13 +158,19 @@ After updating the session, the user is signed in. /mfa/set -This page requests a webAuthN challenge for the user and updates the session afterwards. +This page loads login Settings and the authentication methods for a user and shows setup options. Requests to the APIs made: - `getBrandingSettings(org?)` +- `getLoginSettings(user.org)` - `getSession()` -- `updateSession()` +- `listAuthenticationMethodTypes()` +- `getUserByID()` -When updating the session for the webAuthN challenge, we set `userVerificationRequirement` to `UserVerificationRequirement.REQUIRED` as this will request the webAuthN method as primary method to login. -After updating the session, the user is signed in. +If a user has already setup a certain method, a checkbox is shown alongside the button and the button is disabled. +OTP Email and OTP SMS only show up if the user has verified email or phone. +If the user chooses a method he is redirected to one of `/otp/time-based/set`, `/u2f/set`, `/otp/email/set`, or `/otp/sms/set`. +At the moment, U2F methods are hidden if a method is already added on the users resource. Reasoning is that the page should only be invoked for prompts. A self service page which shows up multiple u2f factors is implemented at a later stage. + +> NOTE: The session and therefore the user factor defines which login settings are checked for available options. diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index e338918550..8c2dfcc09f 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -12,6 +12,7 @@ import BackButton from "@/ui/BackButton"; import ChooseSecondFactorToSetup from "@/ui/ChooseSecondFactorToSetup"; import DynamicTheme from "@/ui/DynamicTheme"; import UserAvatar from "@/ui/UserAvatar"; +import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; export default async function Page({ searchParams, @@ -31,6 +32,28 @@ export default async function Page({ ? await loadSessionById(sessionId, organization) : await loadSessionByLoginname(loginName, organization); + async function getAuthMethodsAndUser(session?: Session) { + const userId = session?.factors?.user?.id; + + if (!userId) { + throw Error("Could not get user id from session"); + } + + return listAuthenticationMethodTypes(userId).then((methods) => { + return getUserByID(userId).then((user) => { + const humanUser = + user.user?.type.case === "human" ? user.user?.type.value : undefined; + + return { + factors: session?.factors, + authMethods: methods.authMethodTypes ?? [], + phoneVerified: humanUser?.phone?.isVerified ?? false, + emailVerified: humanUser?.email?.isVerified ?? false, + }; + }); + }); + } + async function loadSessionByLoginname( loginName?: string, organization?: string, @@ -39,24 +62,7 @@ export default async function Page({ loginName, organization, }).then((session) => { - if (session && session.factors?.user?.id) { - const userId = session.factors.user.id; - return listAuthenticationMethodTypes(userId).then((methods) => { - return getUserByID(userId).then((user) => { - const humanUser = - user.user?.type.case === "human" - ? user.user?.type.value - : undefined; - - return { - factors: session?.factors, - authMethods: methods.authMethodTypes ?? [], - phoneVerified: humanUser?.phone?.isVerified ?? false, - emailVerified: humanUser?.email?.isVerified ?? false, - }; - }); - }); - } + return getAuthMethodsAndUser(session); }); } @@ -65,29 +71,15 @@ export default async function Page({ return getSession({ sessionId: recent.id, sessionToken: recent.token, - }).then((response) => { - if (response?.session && response.session.factors?.user?.id) { - const userId = response.session.factors.user.id; - return listAuthenticationMethodTypes(userId).then((methods) => { - return getUserByID(userId).then((user) => { - const humanUser = - user.user?.type.case === "human" - ? user.user?.type.value - : undefined; - return { - factors: response.session?.factors, - authMethods: methods.authMethodTypes ?? [], - phoneVerified: humanUser?.phone?.isVerified ?? false, - emailVerified: humanUser?.email?.isVerified ?? false, - }; - }); - }); - } + }).then((sessionResponse) => { + return getAuthMethodsAndUser(sessionResponse.session); }); } const branding = await getBrandingSettings(organization); - const loginSettings = await getLoginSettings(organization); + const loginSettings = await getLoginSettings( + sessionWithData.factors?.user?.organizationId, + ); return ( diff --git a/apps/login/src/ui/ChooseSecondFactorToSetup.tsx b/apps/login/src/ui/ChooseSecondFactorToSetup.tsx index 94315a40e6..eb1b47806e 100644 --- a/apps/login/src/ui/ChooseSecondFactorToSetup.tsx +++ b/apps/login/src/ui/ChooseSecondFactorToSetup.tsx @@ -1,6 +1,9 @@ "use client"; -import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { + LoginSettings, + SecondFactorType, +} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { EMAIL, SMS, TOTP, U2F } from "./AuthMethods"; @@ -47,28 +50,37 @@ export default function ChooseSecondFactorToSetup({ return (

- {loginSettings.secondFactors.map((factor, i) => { - return factor === 1 - ? TOTP( + {loginSettings.secondFactors.map((factor) => { + switch (factor) { + case SecondFactorType.OTP: + return TOTP( userMethods.includes(AuthenticationMethodType.TOTP), "/otp/time-based/set?" + params, - ) - : factor === 2 - ? U2F( - userMethods.includes(AuthenticationMethodType.U2F), - "/u2f/set?" + params, + ); + case SecondFactorType.U2F: + return U2F( + userMethods.includes(AuthenticationMethodType.U2F), + "/u2f/set?" + params, + ); + case SecondFactorType.OTP_EMAIL: + return ( + emailVerified && + EMAIL( + userMethods.includes(AuthenticationMethodType.OTP_EMAIL), + "/otp/email/set?" + params, ) - : factor === 3 && emailVerified - ? EMAIL( - userMethods.includes(AuthenticationMethodType.OTP_EMAIL), - "/otp/email/set?" + params, - ) - : factor === 4 && phoneVerified - ? SMS( - userMethods.includes(AuthenticationMethodType.OTP_SMS), - "/otp/sms/set?" + params, - ) - : null; + ); + case SecondFactorType.OTP_SMS: + return ( + phoneVerified && + SMS( + userMethods.includes(AuthenticationMethodType.OTP_SMS), + "/otp/sms/set?" + params, + ) + ); + default: + return null; + } })}
); diff --git a/apps/login/src/ui/LoginPasskey.tsx b/apps/login/src/ui/LoginPasskey.tsx index 448d0a8220..00dc283ae4 100644 --- a/apps/login/src/ui/LoginPasskey.tsx +++ b/apps/login/src/ui/LoginPasskey.tsx @@ -50,19 +50,20 @@ export default function LoginPasskey({ const pK = response?.challenges?.webAuthN?.publicKeyCredentialRequestOptions ?.publicKey; - if (pK) { - submitLoginAndContinue(pK) - .then(() => { - setLoading(false); - }) - .catch((error) => { - setError(error); - setLoading(false); - }); - } else { + + if (!pK) { setError("Could not request passkey challenge"); setLoading(false); } + + return submitLoginAndContinue(pK) + .then(() => { + setLoading(false); + }) + .catch((error) => { + setError(error); + setLoading(false); + }); }) .catch((error) => { setError(error); @@ -135,59 +136,57 @@ export default function LoginPasskey({ publicKey, }) .then((assertedCredential: any) => { - if (assertedCredential) { - const authData = new Uint8Array( - assertedCredential.response.authenticatorData, - ); - const clientDataJSON = new Uint8Array( - assertedCredential.response.clientDataJSON, - ); - const rawId = new Uint8Array(assertedCredential.rawId); - const sig = new Uint8Array(assertedCredential.response.signature); - const userHandle = new Uint8Array( - assertedCredential.response.userHandle, - ); - const data = { - id: assertedCredential.id, - rawId: coerceToBase64Url(rawId, "rawId"), - type: assertedCredential.type, - response: { - authenticatorData: coerceToBase64Url(authData, "authData"), - clientDataJSON: coerceToBase64Url( - clientDataJSON, - "clientDataJSON", - ), - signature: coerceToBase64Url(sig, "sig"), - userHandle: coerceToBase64Url(userHandle, "userHandle"), - }, - }; - return submitLogin(data).then((resp) => { - if (authRequestId && resp && resp.sessionId) { - return router.push( - `/login?` + - new URLSearchParams({ - sessionId: resp.sessionId, - authRequest: authRequestId, - }), - ); - } else { - const params = new URLSearchParams({}); - - if (authRequestId) { - params.set("authRequestId", authRequestId); - } - if (resp?.factors?.user?.loginName) { - params.set("loginName", resp.factors.user.loginName); - } - - return router.push(`/signedin?` + params); - } - }); - } else { + if (!assertedCredential) { setLoading(false); setError("An error on retrieving passkey"); - return null; + return; } + + const authData = new Uint8Array( + assertedCredential.response.authenticatorData, + ); + const clientDataJSON = new Uint8Array( + assertedCredential.response.clientDataJSON, + ); + const rawId = new Uint8Array(assertedCredential.rawId); + const sig = new Uint8Array(assertedCredential.response.signature); + const userHandle = new Uint8Array( + assertedCredential.response.userHandle, + ); + const data = { + id: assertedCredential.id, + rawId: coerceToBase64Url(rawId, "rawId"), + type: assertedCredential.type, + response: { + authenticatorData: coerceToBase64Url(authData, "authData"), + clientDataJSON: coerceToBase64Url(clientDataJSON, "clientDataJSON"), + signature: coerceToBase64Url(sig, "sig"), + userHandle: coerceToBase64Url(userHandle, "userHandle"), + }, + }; + + return submitLogin(data).then((resp) => { + if (authRequestId && resp && resp.sessionId) { + return router.push( + `/login?` + + new URLSearchParams({ + sessionId: resp.sessionId, + authRequest: authRequestId, + }), + ); + } else { + const params = new URLSearchParams({}); + + if (authRequestId) { + params.set("authRequestId", authRequestId); + } + if (resp?.factors?.user?.loginName) { + params.set("loginName", resp.factors.user.loginName); + } + + return router.push(`/signedin?` + params); + } + }); }) .catch((error) => { console.error(error); @@ -245,7 +244,27 @@ export default function LoginPasskey({ className="self-end" variant={ButtonVariants.Primary} disabled={loading} - onClick={() => updateSessionForChallenge()} + onClick={async () => { + const response = await updateSessionForChallenge(); + + const pK = + response?.challenges?.webAuthN?.publicKeyCredentialRequestOptions + ?.publicKey; + + if (!pK) { + setError("Could not request passkey challenge"); + setLoading(false); + } + + return submitLoginAndContinue(pK) + .then(() => { + setLoading(false); + }) + .catch((error) => { + setError(error); + setLoading(false); + }); + }} > {loading && } continue From 23867f36ef0c47ad13fed4e90d99dffe2c9e6c87 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 17 Sep 2024 10:26:09 +0200 Subject: [PATCH 186/640] idp site --- apps/login/readme.md | 16 ++++++++++++++++ apps/login/src/app/(login)/idp/page.tsx | 14 ++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index d59b172d11..f11e85fb86 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -174,3 +174,19 @@ If the user chooses a method he is redirected to one of `/otp/time-based/set`, ` At the moment, U2F methods are hidden if a method is already added on the users resource. Reasoning is that the page should only be invoked for prompts. A self service page which shows up multiple u2f factors is implemented at a later stage. > NOTE: The session and therefore the user factor defines which login settings are checked for available options. + +### /passkey/set + +### /otp/[method]/set + +### /u2f/set + +### /register + +### /idp/[method]/success /idp/[method]/failure + +### /verify + +### /accounts + +### /signedin diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index 9796378e81..a41fad781d 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -1,8 +1,4 @@ -import { - getBrandingSettings, - getLegalAndSupportSettings, - settingsService, -} from "@/lib/zitadel"; +import { getBrandingSettings, settingsService } from "@/lib/zitadel"; import DynamicTheme from "@/ui/DynamicTheme"; import { SignInWithIDP } from "@/ui/SignInWithIDP"; import { makeReqCtx } from "@zitadel/client/v2"; @@ -23,8 +19,6 @@ export default async function Page({ const authRequestId = searchParams?.authRequestId; const organization = searchParams?.organization; - const legal = await getLegalAndSupportSettings(organization); - const identityProviders = await getIdentityProviders(organization); const host = process.env.VERCEL_URL @@ -36,12 +30,12 @@ export default async function Page({ return (
-

Register

+

Sign in with SSO

- Select one of the following providers to register + Select one of the following providers to sign in

- {legal && identityProviders && process.env.ZITADEL_API_URL && ( + {identityProviders && ( Date: Tue, 17 Sep 2024 10:29:49 +0200 Subject: [PATCH 187/640] docs --- apps/login/readme.md | 14 ++++++++++++++ apps/login/screenshots/idp.png | Bin 0 -> 86616 bytes 2 files changed, 14 insertions(+) create mode 100644 apps/login/screenshots/idp.png diff --git a/apps/login/readme.md b/apps/login/readme.md index f11e85fb86..402926ebcd 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -183,8 +183,22 @@ At the moment, U2F methods are hidden if a method is already added on the users ### /register +### /idp + +This page doubles as /loginname but limits it to choose from IDPs + +/idp + +Requests to the APIs made: + +- `getBrandingSettings(org?)` +- `getActiveIdentityProviders(org?)` +- `startIdentityProviderFlow()` + ### /idp/[method]/success /idp/[method]/failure +Both /success and /failure pages are designed to intercept the responses from the IDPs and decide on how to continue with the process. + ### /verify ### /accounts diff --git a/apps/login/screenshots/idp.png b/apps/login/screenshots/idp.png new file mode 100644 index 0000000000000000000000000000000000000000..9bf58c69b056c63d3c5cdc3b07c2bf2948c6400f GIT binary patch literal 86616 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
  • N!2Ta4E{9-_0?8K6Vy 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 DHvilZ literal 0 HcmV?d00001 From 18db6e4908669391eb39ca95c551c7ec0118ded8 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 17 Sep 2024 10:33:27 +0200 Subject: [PATCH 188/640] warning --- apps/login/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 402926ebcd..03a9941cd0 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -163,7 +163,7 @@ This page loads login Settings and the authentication methods for a user and sho Requests to the APIs made: - `getBrandingSettings(org?)` -- `getLoginSettings(user.org)` +- `getLoginSettings(user.org)` > :warning: context taken from session - `getSession()` - `listAuthenticationMethodTypes()` - `getUserByID()` From d55a409bf7dea4e4dc92b89855bdb211820874f5 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 17 Sep 2024 10:33:59 +0200 Subject: [PATCH 189/640] md --- apps/login/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 03a9941cd0..e2d809767a 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -163,7 +163,7 @@ This page loads login Settings and the authentication methods for a user and sho Requests to the APIs made: - `getBrandingSettings(org?)` -- `getLoginSettings(user.org)` > :warning: context taken from session +- `getLoginSettings(user.org)` :warning: context taken from session - `getSession()` - `listAuthenticationMethodTypes()` - `getUserByID()` From c1a487c2334a71716d0f781f4464bc61848f7adc Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 17 Sep 2024 11:58:07 +0200 Subject: [PATCH 190/640] page consistency, docs --- apps/login/cypress/integration/login.cy.ts | 2 +- apps/login/cypress/integration/register.cy.ts | 2 +- apps/login/readme.md | 37 ++++++++++++------- .../app/(login)/passkey/{add => set}/page.tsx | 0 apps/login/src/ui/PasswordForm.tsx | 4 +- .../src/ui/RegisterFormWithoutPassword.tsx | 2 +- 6 files changed, 29 insertions(+), 18 deletions(-) rename apps/login/src/app/(login)/passkey/{add => set}/page.tsx (100%) diff --git a/apps/login/cypress/integration/login.cy.ts b/apps/login/cypress/integration/login.cy.ts index 1d6472d5e6..a286e00d44 100644 --- a/apps/login/cypress/integration/login.cy.ts +++ b/apps/login/cypress/integration/login.cy.ts @@ -111,7 +111,7 @@ describe("login", () => { cy.get('button[type="submit"]').click(); cy.location("pathname", { timeout: 10_000 }).should( "eq", - "/passkey/add", + "/passkey/set", ); }); }); diff --git a/apps/login/cypress/integration/register.cy.ts b/apps/login/cypress/integration/register.cy.ts index 4cad0dbd81..9eb4e8be15 100644 --- a/apps/login/cypress/integration/register.cy.ts +++ b/apps/login/cypress/integration/register.cy.ts @@ -17,6 +17,6 @@ describe("register", () => { cy.get('input[type="checkbox"][value="privacypolicy"]').check(); cy.get('input[type="checkbox"][value="tos"]').check(); cy.get('button[type="submit"]').click(); - cy.location("pathname", { timeout: 10_000 }).should("eq", "/passkey/add"); + cy.location("pathname", { timeout: 10_000 }).should("eq", "/passkey/set"); }); }); diff --git a/apps/login/readme.md b/apps/login/readme.md index e2d809767a..6b07e30e69 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -45,11 +45,11 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a ### /loginname -/loginame - This page shows a loginname field and Identity Providers to login or register. If `loginSettings(org?).allowRegister` is `true`, if will also show a link to jump to /register +/loginame + Requests to the APIs made: - `getLoginSettings(org?)` @@ -85,11 +85,11 @@ If no previous condition is met we throw an error stating the user was not found ### /password -/password - This page shows a password field to hydrate the current session with password as a factor. Below the password field, a reset password link is shown which allows to send a reset email. +/password + Requests to the APIs made: - `getLoginSettings(org?)` @@ -103,16 +103,16 @@ If the user has set up an additional **single** second factor, it is redirected **NO MFA, FORCE MFA:** If no MFA method is available, and the settings force MFA, the user is sent to `/mfa/set` which prompts to setup a second factor. -**PROMPT PASSKEY** If the settings do not enforce MFA, we check if passkeys are allowed with `loginSettings?.passkeysType === PasskeysType.ALLOWED` and redirect the user to `/passkey/add` if no passkeys are setup. This step can be skipped. +**PROMPT PASSKEY** If the settings do not enforce MFA, we check if passkeys are allowed with `loginSettings?.passkeysType === PasskeysType.ALLOWED` and redirect the user to `/passkey/set` if no passkeys are setup. This step can be skipped. If none of the previous conditions apply, we continue to sign in. ### /otp/[method] -/otp/[method] - This page shows a code field to check an otp method. The session of the user is then hydrated with the respective factor. Supported methods are `time-based`, `sms` and `email`. +/otp/[method] + Requests to the APIs made: - `getBrandingSettings(org?)` @@ -126,10 +126,10 @@ The submission of the code updates the session and continues to sign in the user ### /u2f -/u2f - This page requests a webAuthN challenge for the user and updates the session afterwards. +/u2f + Requests to the APIs made: - `getBrandingSettings(org?)` @@ -141,10 +141,10 @@ After updating the session, the user is signed in. ### /passkey -/passkey - This page requests a webAuthN challenge for the user and updates the session afterwards. +/passkey + Requests to the APIs made: - `getBrandingSettings(org?)` @@ -156,10 +156,10 @@ After updating the session, the user is signed in. ### /mfa/set -/mfa/set - This page loads login Settings and the authentication methods for a user and shows setup options. +/mfa/set + Requests to the APIs made: - `getBrandingSettings(org?)` @@ -177,6 +177,17 @@ At the moment, U2F methods are hidden if a method is already added on the users ### /passkey/set +/passkey/set + +This page sets a passkey method for a user. This page can be either enforced, or optional depending on the Login Settings. + +Requests to the APIs made: + +- `getBrandingSettings(org?)` +- `getSession()` +- `registerPasskeyLink()` +- `verifyPasskey()` + ### /otp/[method]/set ### /u2f/set diff --git a/apps/login/src/app/(login)/passkey/add/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx similarity index 100% rename from apps/login/src/app/(login)/passkey/add/page.tsx rename to apps/login/src/app/(login)/passkey/set/page.tsx diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index 919e56c774..1c1870e7ba 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -102,7 +102,7 @@ export default function PasswordForm({ setInfo(""); // if user has mfa -> /otp/[method] or /u2f // if mfa is forced and user has no mfa -> /mfa/set - // if no passwordless -> /passkey/add + // if no passwordless -> /passkey/set // exclude password and passwordless if ( @@ -194,7 +194,7 @@ export default function PasswordForm({ params.append("organization", organization); } - return router.push(`/passkey/add?` + params); + return router.push(`/passkey/set?` + params); } else if (authRequestId && submitted.sessionId) { const params = new URLSearchParams({ sessionId: submitted.sessionId, diff --git a/apps/login/src/ui/RegisterFormWithoutPassword.tsx b/apps/login/src/ui/RegisterFormWithoutPassword.tsx index 64999f75f4..c7ba74cabc 100644 --- a/apps/login/src/ui/RegisterFormWithoutPassword.tsx +++ b/apps/login/src/ui/RegisterFormWithoutPassword.tsx @@ -109,7 +109,7 @@ export default function RegisterFormWithoutPassword({ params.set("authRequestId", authRequestId); } - return router.push(`/passkey/add?` + new URLSearchParams(params)); + return router.push(`/passkey/set?` + new URLSearchParams(params)); } } From f03a50fdcb76026ae609f5672f1d28e27cddd2eb Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 17 Sep 2024 13:28:12 +0200 Subject: [PATCH 191/640] passkeys set --- apps/login/readme.md | 13 ++++++ apps/login/src/ui/RegisterPasskey.tsx | 60 ++++++++++++++------------- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 6b07e30e69..c35eaf0eac 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -83,6 +83,8 @@ If no previous condition is met we throw an error stating the user was not found > NOTE: We ignore `loginSettings.allowExternalIdp` as the information whether IDPs are available comes as response from `getActiveIdentityProviders(org?)`. If a user has a cookie for the same loginname, a new session is created regardless and overwrites the old session. The old session is not deleted from the login as for now. +> NOTE: `listAuthenticationMethodTypes()` does not consider different domains for u2f methods or passkeys. The check whether a user should be redirected to one of the pages `/passkey` or `/u2f`, should be extended to use a domain filter (https://github.com/zitadel/zitadel/issues/8615) + ### /password This page shows a password field to hydrate the current session with password as a factor. @@ -107,6 +109,8 @@ If the user has set up an additional **single** second factor, it is redirected If none of the previous conditions apply, we continue to sign in. +> NOTE: `listAuthenticationMethodTypes()` does not consider different domains for u2f methods or passkeys. The check whether a user should be redirected to one of the pages `/passkey` or `/u2f`, should be extended to use a domain filter (https://github.com/zitadel/zitadel/issues/8615) + ### /otp/[method] This page shows a code field to check an otp method. The session of the user is then hydrated with the respective factor. Supported methods are `time-based`, `sms` and `email`. @@ -154,6 +158,8 @@ Requests to the APIs made: When updating the session for the webAuthN challenge, we set `userVerificationRequirement` to `UserVerificationRequirement.REQUIRED` as this will request the webAuthN method as primary method to login. After updating the session, the user is signed in. +> NOTE: This page currently does not check whether a user contains passkeys. If this method is not available, this page should not be used. + ### /mfa/set This page loads login Settings and the authentication methods for a user and shows setup options. @@ -175,6 +181,8 @@ At the moment, U2F methods are hidden if a method is already added on the users > NOTE: The session and therefore the user factor defines which login settings are checked for available options. +> NOTE: `listAuthenticationMethodTypes()` does not consider different domains for u2f or passkeys. The check whether a user should be redirected to one of the pages `/passkey/set` or `/u2f/set`, should be extended to use a domain filter (https://github.com/zitadel/zitadel/issues/8615) + ### /passkey/set /passkey/set @@ -188,6 +196,11 @@ Requests to the APIs made: - `registerPasskeyLink()` - `verifyPasskey()` +If the loginname decides to redirect the user to this page, a button to skip appears which will sign the user in afterwards. +If a passkey is registered, we redirect the user to `/passkey` to again verify it and sign in with the new method. + +> NOTE: Redirecting the user to `/passkey` will not be required in future and the currently used session will be hydrated directly after registering. (https://github.com/zitadel/zitadel/issues/8611) + ### /otp/[method]/set ### /u2f/set diff --git a/apps/login/src/ui/RegisterPasskey.tsx b/apps/login/src/ui/RegisterPasskey.tsx index 9391e62f63..a56c34933a 100644 --- a/apps/login/src/ui/RegisterPasskey.tsx +++ b/apps/login/src/ui/RegisterPasskey.tsx @@ -148,12 +148,39 @@ export default function RegisterPasskey({ if (authRequestId) { params.set("authRequestId", authRequestId); params.set("sessionId", sessionId); - // params.set("altPassword", ${false}); // without setting altPassword this does not allow password - // params.set("loginName", resp.loginName); router.push("/passkey?" + params); } else { - router.push("/accounts?" + params); + continueAndLogin(); + } + } + + function continueAndLogin() { + if (authRequestId) { + const params = new URLSearchParams({ + authRequest: authRequestId, + }); + + if (sessionId) { + params.set("sessionId", sessionId); + } + + if (organization) { + params.set("organization", organization); + } + + router.push("/login?" + params); + } else { + const params = new URLSearchParams(); + + if (sessionId) { + params.append("sessionId", sessionId); + } + if (organization) { + params.append("organization", organization); + } + + router.push("/signedin?" + params); } } @@ -171,32 +198,7 @@ export default function RegisterPasskey({ type="button" variant={ButtonVariants.Secondary} onClick={() => { - if (authRequestId) { - const params = new URLSearchParams({ - authRequest: authRequestId, - }); - - if (sessionId) { - params.set("sessionId", sessionId); - } - - if (organization) { - params.set("organization", organization); - } - - router.push("/login?" + params); - } else { - const params = new URLSearchParams(); - - if (sessionId) { - params.append("sessionId", sessionId); - } - if (organization) { - params.append("organization", organization); - } - - router.push("/signedin?" + params); - } + continueAndLogin(); }} > skip From dd8c4a83bbca007c374727e0fc6d8482293b2034 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 17 Sep 2024 14:31:43 +0200 Subject: [PATCH 192/640] passkey u2f improvements --- apps/login/readme.md | 51 +++++++++++++++++++++++--- apps/login/screenshots/register.png | Bin 0 -> 161800 bytes apps/login/src/lib/server/passkeys.ts | 7 +++- apps/login/src/lib/server/u2f.ts | 2 +- apps/login/src/lib/zitadel.ts | 38 +++++++++---------- apps/login/src/ui/RegisterPasskey.tsx | 38 +++---------------- 6 files changed, 77 insertions(+), 59 deletions(-) create mode 100644 apps/login/screenshots/register.png diff --git a/apps/login/readme.md b/apps/login/readme.md index c35eaf0eac..c1b55ddc39 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -141,11 +141,12 @@ Requests to the APIs made: - `updateSession()` When updating the session for the webAuthN challenge, we set `userVerificationRequirement` to `UserVerificationRequirement.DISCOURAGED` as this will request the webAuthN method as second factor and not as primary method. -After updating the session, the user is signed in. +After updating the session, the user is **always** signed in. :warning: required as this page is a follow up for setting up a u2f method. ### /passkey This page requests a webAuthN challenge for the user and updates the session afterwards. +It is invoked directly after setting up a passkey `/passkey/set` or when loggin in a user after `/loginname`. /passkey @@ -156,7 +157,7 @@ Requests to the APIs made: - `updateSession()` When updating the session for the webAuthN challenge, we set `userVerificationRequirement` to `UserVerificationRequirement.REQUIRED` as this will request the webAuthN method as primary method to login. -After updating the session, the user is signed in. +After updating the session, the user is **always** signed in. :warning: required as this page is a follow up for setting up a passkey > NOTE: This page currently does not check whether a user contains passkeys. If this method is not available, this page should not be used. @@ -185,19 +186,20 @@ At the moment, U2F methods are hidden if a method is already added on the users ### /passkey/set -/passkey/set - This page sets a passkey method for a user. This page can be either enforced, or optional depending on the Login Settings. +/passkey/set + Requests to the APIs made: - `getBrandingSettings(org?)` - `getSession()` -- `registerPasskeyLink()` +- `createPasskeyRegistrationLink(token)` :warning: This request requires the session token +- `registerPasskey()` - `verifyPasskey()` If the loginname decides to redirect the user to this page, a button to skip appears which will sign the user in afterwards. -If a passkey is registered, we redirect the user to `/passkey` to again verify it and sign in with the new method. +After a passkey is registered, we redirect the user to `/passkey` to verify it again and sign in with the new method. The `createPasskeyRegistrationLink()` uses the token of the session which is determined by the flow. > NOTE: Redirecting the user to `/passkey` will not be required in future and the currently used session will be hydrated directly after registering. (https://github.com/zitadel/zitadel/issues/8611) @@ -205,8 +207,45 @@ If a passkey is registered, we redirect the user to `/passkey` to again verify i ### /u2f/set +This page registers a U2F method for a user. + +/u2f/set + +Requests to the APIs made: + +- `getBrandingSettings(org?)` +- `getSession()` +- `registerU2F(token)` :warning: This request requires the session token +- `verifyU2FRegistration()` + +After a u2f method is registered, we redirect the user to `/passkey` to verify it again and sign in with the new method. The `createPasskeyRegistrationLink()` uses the token of the session which is determined by the flow. + +> NOTE: Redirecting the user to `/passkey` will not be required in future and the currently used session will be hydrated directly after registering. (https://github.com/zitadel/zitadel/issues/8611) + ### /register +This page shows a register page, which gets firstname and lastname of a user as well as the email. It offers to setup a user, using password or passkeys. + +/register + +Requests to the APIs made: + +- `listOrganizations()` :warning: TODO: determine the default organization if no context is set +- `getLegalAndSupportSettings(org)` +- `getPasswordComplexitySettings()` +- `getBrandingSettings()` +- `addHumanUser()` +- `createSession()` +- `getSession()` + +To register a user, the organization where the resource will be created is determined first. If no context is provided via url, we fall back to the default organization of the instance. + +**PASSWORD:** If a password is set, the user is created as a resource, then a session using the password check is created immediately. + +**PASSKEY:** If passkey is selected, the user is created as a resource first, then a session using the userId is created immediately. This session does not yet contain a check, we therefore redirect the user to setup a passkey at `/passkey/set`. As the passkey set page verifies the passkey right afterwards, the process ends with a signed in user. + +> NOTE: https://github.com/zitadel/zitadel/issues/8616 to determine the default organization of an instance must be implemented in order to correctly use the legal-, login-, branding- and complexitysettings. + ### /idp This page doubles as /loginname but limits it to choose from IDPs diff --git a/apps/login/screenshots/register.png b/apps/login/screenshots/register.png new file mode 100644 index 0000000000000000000000000000000000000000..ba9f6951d803ebdd9c4e31ae18111c9244015466 GIT binary patch literal 161800 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 VJJUZRw+;MJKd*Hz`>a{u{{@Vba_Rs8 literal 0 HcmV?d00001 diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 9c507f9577..e003883684 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -49,7 +49,12 @@ export async function registerPasskeyLink( throw new Error("Could not get session"); } // TODO: add org context - const registerLink = await createPasskeyRegistrationLink(userId); + + // use session token to add the passkey + const registerLink = await createPasskeyRegistrationLink( + userId, + sessionCookie.token, + ); if (!registerLink.code) { throw new Error("Missing code in response"); diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index 3f61f526fa..4dba59a0e1 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -44,7 +44,7 @@ export async function addU2F(command: RegisterU2FCommand) { return { error: "Could not get session" }; } - return registerU2F(userId, domain); + return registerU2F(userId, domain, sessionCookie.token); } export async function verifyU2F(command: VerifyU2FCommand) { diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 43715a94cb..e255e4cc7f 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -442,7 +442,6 @@ export function createUser( info: IDPInformation, ) { const userData = PROVIDER_MAPPING[provider](info); - console.log("ud", userData); return userService.addHumanUser(userData, {}); } @@ -468,23 +467,15 @@ export async function passwordReset(userId: string) { */ export async function createPasskeyRegistrationLink( userId: string, - token?: string, + token: string, ) { - // let userService; - // if (token) { - // const authConfig: ZitadelServerOptions = { - // name: "zitadel login", - // apiUrl: process.env.ZITADEL_API_URL ?? "", - // token: token, - // }; - // - // const sessionUser = initializeServer(authConfig); - // userService = user.getUser(sessionUser); - // } else { - // userService = user.getUser(server); - // } + const transport = createServerTransport(token, { + baseUrl: process.env.ZITADEL_API_URL!, + httpVersion: "2", + }); - return userService.createPasskeyRegistrationLink({ + const service = createUserServiceClient(transport); + return service.createPasskeyRegistrationLink({ userId, medium: { case: "returnCode", @@ -499,8 +490,18 @@ export async function createPasskeyRegistrationLink( * @param domain the domain on which the factor is registered * @returns the newly set email */ -export async function registerU2F(userId: string, domain: string) { - return userService.registerU2F({ +export async function registerU2F( + userId: string, + domain: string, + token: string, +) { + const transport = createServerTransport(token, { + baseUrl: process.env.ZITADEL_API_URL!, + httpVersion: "2", + }); + + const service = createUserServiceClient(transport); + return service.registerU2F({ userId, domain, }); @@ -550,7 +551,6 @@ export async function registerPasskey( userId, code, domain, - // authenticator: }); } diff --git a/apps/login/src/ui/RegisterPasskey.tsx b/apps/login/src/ui/RegisterPasskey.tsx index a56c34933a..db96474c01 100644 --- a/apps/login/src/ui/RegisterPasskey.tsx +++ b/apps/login/src/ui/RegisterPasskey.tsx @@ -139,6 +139,10 @@ export default function RegisterPasskey({ return; } + continueAndLogin(); + } + + function continueAndLogin() { const params = new URLSearchParams(); if (organization) { @@ -147,41 +151,11 @@ export default function RegisterPasskey({ if (authRequestId) { params.set("authRequestId", authRequestId); - params.set("sessionId", sessionId); - - router.push("/passkey?" + params); - } else { - continueAndLogin(); } - } - function continueAndLogin() { - if (authRequestId) { - const params = new URLSearchParams({ - authRequest: authRequestId, - }); + params.set("sessionId", sessionId); - if (sessionId) { - params.set("sessionId", sessionId); - } - - if (organization) { - params.set("organization", organization); - } - - router.push("/login?" + params); - } else { - const params = new URLSearchParams(); - - if (sessionId) { - params.append("sessionId", sessionId); - } - if (organization) { - params.append("organization", organization); - } - - router.push("/signedin?" + params); - } + router.push("/passkey?" + params); } return ( From d48db1841c4b48116f287f735e73a8a14cbe9943 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 17 Sep 2024 14:39:43 +0200 Subject: [PATCH 193/640] revert local token --- apps/login/readme.md | 4 ++-- apps/login/src/lib/server/passkeys.ts | 2 +- apps/login/src/lib/server/u2f.ts | 6 ++++- apps/login/src/lib/zitadel.ts | 32 +++++++++++++++------------ 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index c1b55ddc39..cb8c89daed 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -194,7 +194,7 @@ Requests to the APIs made: - `getBrandingSettings(org?)` - `getSession()` -- `createPasskeyRegistrationLink(token)` :warning: This request requires the session token +- `createPasskeyRegistrationLink()` TODO: check if this can be used with the session token (mfa required (AUTHZ-Kl3p0)) - `registerPasskey()` - `verifyPasskey()` @@ -215,7 +215,7 @@ Requests to the APIs made: - `getBrandingSettings(org?)` - `getSession()` -- `registerU2F(token)` :warning: This request requires the session token +- `registerU2F()` :warning: TODO: check if this can be used with the session token (mfa required (AUTHZ-Kl3p0)) - `verifyU2FRegistration()` After a u2f method is registered, we redirect the user to `/passkey` to verify it again and sign in with the new method. The `createPasskeyRegistrationLink()` uses the token of the session which is determined by the flow. diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index e003883684..5ad170f8d3 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -53,7 +53,7 @@ export async function registerPasskeyLink( // use session token to add the passkey const registerLink = await createPasskeyRegistrationLink( userId, - sessionCookie.token, + // sessionCookie.token, ); if (!registerLink.code) { diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index 4dba59a0e1..eb0bccf5af 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -44,7 +44,11 @@ export async function addU2F(command: RegisterU2FCommand) { return { error: "Could not get session" }; } - return registerU2F(userId, domain, sessionCookie.token); + return registerU2F( + userId, + domain, + // sessionCookie.token + ); } export async function verifyU2F(command: VerifyU2FCommand) { diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index e255e4cc7f..bc38b6ac78 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -465,17 +465,19 @@ export async function passwordReset(userId: string) { * @param userId the id of the user where the email should be set * @returns the newly set email */ + +// TODO check for token requirements! export async function createPasskeyRegistrationLink( userId: string, - token: string, + // token: string, ) { - const transport = createServerTransport(token, { - baseUrl: process.env.ZITADEL_API_URL!, - httpVersion: "2", - }); + // const transport = createServerTransport(token, { + // baseUrl: process.env.ZITADEL_API_URL!, + // httpVersion: "2", + // }); - const service = createUserServiceClient(transport); - return service.createPasskeyRegistrationLink({ + // const service = createUserServiceClient(transport); + return userService.createPasskeyRegistrationLink({ userId, medium: { case: "returnCode", @@ -490,18 +492,20 @@ export async function createPasskeyRegistrationLink( * @param domain the domain on which the factor is registered * @returns the newly set email */ + +// TODO check for token requirements! export async function registerU2F( userId: string, domain: string, - token: string, + // token: string, ) { - const transport = createServerTransport(token, { - baseUrl: process.env.ZITADEL_API_URL!, - httpVersion: "2", - }); + // const transport = createServerTransport(token, { + // baseUrl: process.env.ZITADEL_API_URL!, + // httpVersion: "2", + // }); - const service = createUserServiceClient(transport); - return service.registerU2F({ + // const service = createUserServiceClient(transport); + return userService.registerU2F({ userId, domain, }); From 90861564b6319eeb0b97fbb7da6b8e80b1e06ff9 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 17 Sep 2024 15:06:27 +0200 Subject: [PATCH 194/640] docs --- apps/login/readme.md | 38 ++++++++++++++++++++- apps/login/screenshots/accounts.png | Bin 0 -> 159830 bytes apps/login/screenshots/accounts_jumpto.png | Bin 0 -> 15180 bytes apps/login/screenshots/otpset.png | Bin 0 -> 146885 bytes apps/login/screenshots/signedin.png | Bin 0 -> 60794 bytes apps/login/screenshots/u2fset.png | Bin 0 -> 90769 bytes 6 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 apps/login/screenshots/accounts.png create mode 100644 apps/login/screenshots/accounts_jumpto.png create mode 100644 apps/login/screenshots/otpset.png create mode 100644 apps/login/screenshots/signedin.png create mode 100644 apps/login/screenshots/u2fset.png diff --git a/apps/login/readme.md b/apps/login/readme.md index cb8c89daed..f1c2e1e916 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -81,6 +81,8 @@ If no previous condition is met we throw an error stating the user was not found **EXCEPTIONS:** If the outcome after this order produces a no authentication methods found, or user not found, we check whether `loginSettings?.ignoreUnknownUsernames` is set to `true` as in this case we redirect to the /password page regardless (to not leak information about a registered user). +> NOTE: This page at this stage beeing ignores local sessions and executes a reauthentication. This is a feature which is not implemented yet. + > NOTE: We ignore `loginSettings.allowExternalIdp` as the information whether IDPs are available comes as response from `getActiveIdentityProviders(org?)`. If a user has a cookie for the same loginname, a new session is created regardless and overwrites the old session. The old session is not deleted from the login as for now. > NOTE: `listAuthenticationMethodTypes()` does not consider different domains for u2f methods or passkeys. The check whether a user should be redirected to one of the pages `/passkey` or `/u2f`, should be extended to use a domain filter (https://github.com/zitadel/zitadel/issues/8615) @@ -203,7 +205,15 @@ After a passkey is registered, we redirect the user to `/passkey` to verify it a > NOTE: Redirecting the user to `/passkey` will not be required in future and the currently used session will be hydrated directly after registering. (https://github.com/zitadel/zitadel/issues/8611) -### /otp/[method]/set +### /otp/time-based/set + +This page registers a time based OTP method for a user. + +/otp/time-based/set + +### /otp/email/set /otp/sms/set + +This page registers either an Email OTP method or SMS OTP method for a user. ### /u2f/set @@ -266,4 +276,30 @@ Both /success and /failure pages are designed to intercept the responses from th ### /accounts +This page shows an overview of all current sessions. +Sessions with invalid token show a red dot on the right side, Valid session a green dot, and its last verified date. + +/accounts + +This page is a starting point for self management, reauthentication, or can be used to clear local sessions. +This page is also shown if used with OIDC and `prompt: select_account`. + +On all pages, where the current user is shown, you can jump to this page. This way, a session can quickly be reused if valid. + +jump to accounts + ### /signedin + +This is a success page which shows a completed login flow for a user, which did navigate to the login without a OIDC auth requrest. + +/signedin + +In future, self service options to jump to are shown below, like: + +- change password +- setup passkeys +- setup mfa +- change profile +- logout + +> NOTE: This page has to be explicitly enabled or act as a fallback if no default redirect is set. diff --git a/apps/login/screenshots/accounts.png b/apps/login/screenshots/accounts.png new file mode 100644 index 0000000000000000000000000000000000000000..a8591141c6d36b0ea77e56038d9d68d98ed24de5 GIT binary patch literal 159830 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 MQR_m+xjT>l4@@5Z$p8QV literal 0 HcmV?d00001 diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index 22b2246184..a43e0a0840 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -31,8 +31,7 @@ export default async function Page({ organization, }); - let totpResponse: RegisterTOTPResponse | undefined, - totpError: Error | undefined; + let totpResponse: RegisterTOTPResponse | undefined, error: Error | undefined; if (session && session.factors?.user?.id) { if (method === "time-based") { await registerTOTP(session.factors.user.id) @@ -41,15 +40,21 @@ export default async function Page({ totpResponse = resp; } }) - .catch((error) => { - totpError = error; + .catch((err) => { + error = err; }); } else if (method === "sms") { // does not work - await addOTPSMS(session.factors.user.id); + await addOTPSMS(session.factors.user.id).catch((error) => { + console.error(error); + error = new Error("Could not add OTP via SMS"); + }); } else if (method === "email") { // works - await addOTPEmail(session.factors.user.id); + await addOTPEmail(session.factors.user.id).catch((error) => { + console.error(error); + error = new Error("Could not add OTP via Email"); + }); } else { throw new Error("Invalid method"); } @@ -98,9 +103,9 @@ export default async function Page({

    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 literal 0 HcmV?d00001 diff --git a/apps/login/screenshots/otpset.png b/apps/login/screenshots/otpset.png new file mode 100644 index 0000000000000000000000000000000000000000..f75c2154c7f4f4c930167b6cd81458151919660d GIT binary patch literal 146885 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 GU;Gb)+Rz*i#O0lqm= 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 literal 0 HcmV?d00001 From 6f560776106e80b08e0c87cfd0d90fe5653edc79 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 17 Sep 2024 15:13:11 +0200 Subject: [PATCH 195/640] passkey set --- apps/login/readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index f1c2e1e916..dc461f02b8 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -190,7 +190,8 @@ At the moment, U2F methods are hidden if a method is already added on the users This page sets a passkey method for a user. This page can be either enforced, or optional depending on the Login Settings. -/passkey/set + +/passkey/set Requests to the APIs made: From d558f06057015f9da1d8c17236be50c6bbc9e367 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 17 Sep 2024 15:51:52 +0200 Subject: [PATCH 196/640] otp setup docs --- apps/login/readme.md | 38 ++++++++++++++++++ apps/login/screenshots/register_password.png | Bin 0 -> 118094 bytes apps/login/screenshots/verify.png | Bin 0 -> 67934 bytes .../src/app/(login)/otp/[method]/set/page.tsx | 23 ++++++----- apps/login/src/lib/zitadel.ts | 32 +-------------- 5 files changed, 53 insertions(+), 40 deletions(-) create mode 100644 apps/login/screenshots/register_password.png create mode 100644 apps/login/screenshots/verify.png diff --git a/apps/login/readme.md b/apps/login/readme.md index dc461f02b8..547c228378 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -204,6 +204,8 @@ Requests to the APIs made: If the loginname decides to redirect the user to this page, a button to skip appears which will sign the user in afterwards. After a passkey is registered, we redirect the user to `/passkey` to verify it again and sign in with the new method. The `createPasskeyRegistrationLink()` uses the token of the session which is determined by the flow. +> NOTE: this page allows passkeys to be created only if the current session is valid (self service), or no authentication method is set (register). TODO: to be implemented. + > NOTE: Redirecting the user to `/passkey` will not be required in future and the currently used session will be hydrated directly after registering. (https://github.com/zitadel/zitadel/issues/8611) ### /otp/time-based/set @@ -212,10 +214,29 @@ This page registers a time based OTP method for a user. /otp/time-based/set +Requests to the APIs made: + +- `getBrandingSettings(org?)` +- `getSession()` +- `registerTOTP()` +- `verifyTOTP()` + +After the setup is done, the user is redirected to verify the TOTP method on `/otp/time-based`. + +> NOTE: Redirecting the user to `/otp/time-based` will not be required in future and the currently used session will be hydrated directly. (https://github.com/zitadel/zitadel/issues/8611) + ### /otp/email/set /otp/sms/set This page registers either an Email OTP method or SMS OTP method for a user. +Requests to the APIs made: + +- `getBrandingSettings(org?)` +- `getSession()` +- `addOTPEmail()` / `addOTPSMS()` + +This page directly calls `addOTPEmail()` or `addOTPSMS()` when invoked and shows a success message. + ### /u2f/set This page registers a U2F method for a user. @@ -239,6 +260,8 @@ This page shows a register page, which gets firstname and lastname of a user as /register +register with password + Requests to the APIs made: - `listOrganizations()` :warning: TODO: determine the default organization if no context is set @@ -275,6 +298,21 @@ Both /success and /failure pages are designed to intercept the responses from th ### /verify +This page verifies the email to be valid. It page of the login can also be invoked without an active session. +The context of the user is taken from the url and is set in the email template. + +/accounts + +Requests to the APIs made: + +- `getBrandingSettings(org?)` +- `getLoginSettings(org?)` +- `verifyEmail()` + +If the page is invoked with an active session (right after a register with password), the user is signed in or redirected to the loginname if no context is known. + +> NOTE: This page will be extended to support invitations. In such case, authentication methods of the user are loaded and if none available, shown as possible next step (`/passkey/set`, `password/set`). + ### /accounts This page shows an overview of all current sessions. diff --git a/apps/login/screenshots/register_password.png b/apps/login/screenshots/register_password.png new file mode 100644 index 0000000000000000000000000000000000000000..31515bda9ada8b5733fe35d8545158d065d55f97 GIT binary patch literal 118094 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

  • )} - {totpError && ( + {error && (
    - {totpError?.message} + {error?.message}
    )} @@ -119,8 +124,6 @@ export default async function Page({ Scan the QR Code or navigate to the URL manually.

    - {/* {auth &&
    {auth.to}
    } */} - Date: Tue, 17 Sep 2024 16:31:46 +0200 Subject: [PATCH 197/640] restrict register --- apps/login/readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/login/readme.md b/apps/login/readme.md index 547c228378..4851cf8d85 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -280,6 +280,8 @@ To register a user, the organization where the resource will be created is deter > NOTE: https://github.com/zitadel/zitadel/issues/8616 to determine the default organization of an instance must be implemented in order to correctly use the legal-, login-, branding- and complexitysettings. +> NOTE: TODO: check which methods are allowed in the login settings, loginSettings.allowUsernamePassword / check for passkey + ### /idp This page doubles as /loginname but limits it to choose from IDPs From 0e25f2b6766d6f6691f9005b9bfb9cad8fc3d113 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 02:08:47 +0000 Subject: [PATCH 198/640] Bump the dev group across 1 directory with 19 updates Bumps the dev group with 19 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@changesets/cli](https://github.com/changesets/changesets) | `2.27.7` | `2.27.8` | | [eslint](https://github.com/eslint/eslint) | `8.57.0` | `8.57.1` | | [tsup](https://github.com/egoist/tsup) | `8.2.4` | `8.3.0` | | [turbo](https://github.com/vercel/turborepo) | `2.0.12` | `2.1.2` | | [typescript](https://github.com/microsoft/TypeScript) | `5.5.4` | `5.6.2` | | [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `2.0.5` | `2.1.1` | | [@bufbuild/buf](https://github.com/bufbuild/buf) | `1.39.0` | `1.41.0` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.1.0` | `22.5.5` | | [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `18.3.3` | `18.3.7` | | [concurrently](https://github.com/open-cli-tools/concurrently) | `8.2.2` | `9.0.1` | | [cypress](https://github.com/cypress-io/cypress) | `13.14.1` | `13.14.2` | | [lint-staged](https://github.com/lint-staged/lint-staged) | `15.2.8` | `15.2.10` | | [nodemon](https://github.com/remy/nodemon) | `3.1.4` | `3.1.5` | | [postcss](https://github.com/postcss/postcss) | `8.4.41` | `8.4.47` | | [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) | `0.6.5` | `0.6.6` | | [sass](https://github.com/sass/dart-sass) | `1.77.8` | `1.79.1` | | [start-server-and-test](https://github.com/bahmutov/start-server-and-test) | `2.0.5` | `2.0.8` | | [tailwindcss](https://github.com/tailwindlabs/tailwindcss) | `3.4.9` | `3.4.12` | | [ts-proto](https://github.com/stephenh/ts-proto) | `1.181.2` | `2.2.0` | Updates `@changesets/cli` from 2.27.7 to 2.27.8 - [Release notes](https://github.com/changesets/changesets/releases) - [Changelog](https://github.com/changesets/changesets/blob/main/docs/modifying-changelog-format.md) - [Commits](https://github.com/changesets/changesets/compare/@changesets/cli@2.27.7...@changesets/cli@2.27.8) Updates `eslint` from 8.57.0 to 8.57.1 - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v8.57.0...v8.57.1) Updates `tsup` from 8.2.4 to 8.3.0 - [Release notes](https://github.com/egoist/tsup/releases) - [Commits](https://github.com/egoist/tsup/compare/v8.2.4...v8.3.0) Updates `turbo` from 2.0.12 to 2.1.2 - [Release notes](https://github.com/vercel/turborepo/releases) - [Changelog](https://github.com/vercel/turborepo/blob/main/release.md) - [Commits](https://github.com/vercel/turborepo/compare/v2.0.12...v2.1.2) Updates `typescript` from 5.5.4 to 5.6.2 - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.5.4...v5.6.2) Updates `vitest` from 2.0.5 to 2.1.1 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.1/packages/vitest) Updates `@bufbuild/buf` from 1.39.0 to 1.41.0 - [Release notes](https://github.com/bufbuild/buf/releases) - [Changelog](https://github.com/bufbuild/buf/blob/main/CHANGELOG.md) - [Commits](https://github.com/bufbuild/buf/compare/v1.39.0...v1.41.0) Updates `@types/node` from 22.1.0 to 22.5.5 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@types/react` from 18.3.3 to 18.3.7 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `concurrently` from 8.2.2 to 9.0.1 - [Release notes](https://github.com/open-cli-tools/concurrently/releases) - [Commits](https://github.com/open-cli-tools/concurrently/compare/v8.2.2...v9.0.1) Updates `cypress` from 13.14.1 to 13.14.2 - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md) - [Commits](https://github.com/cypress-io/cypress/compare/v13.14.1...v13.14.2) Updates `lint-staged` from 15.2.8 to 15.2.10 - [Release notes](https://github.com/lint-staged/lint-staged/releases) - [Changelog](https://github.com/lint-staged/lint-staged/blob/master/CHANGELOG.md) - [Commits](https://github.com/lint-staged/lint-staged/compare/v15.2.8...v15.2.10) Updates `nodemon` from 3.1.4 to 3.1.5 - [Release notes](https://github.com/remy/nodemon/releases) - [Commits](https://github.com/remy/nodemon/compare/v3.1.4...v3.1.5) Updates `postcss` from 8.4.41 to 8.4.47 - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.41...8.4.47) Updates `prettier-plugin-tailwindcss` from 0.6.5 to 0.6.6 - [Release notes](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/compare/v0.6.5...v0.6.6) Updates `sass` from 1.77.8 to 1.79.1 - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.77.8...1.79.1) Updates `start-server-and-test` from 2.0.5 to 2.0.8 - [Release notes](https://github.com/bahmutov/start-server-and-test/releases) - [Commits](https://github.com/bahmutov/start-server-and-test/compare/v2.0.5...v2.0.8) Updates `tailwindcss` from 3.4.9 to 3.4.12 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/v3.4.12/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v3.4.9...v3.4.12) Updates `ts-proto` from 1.181.2 to 2.2.0 - [Release notes](https://github.com/stephenh/ts-proto/releases) - [Changelog](https://github.com/stephenh/ts-proto/blob/main/CHANGELOG.md) - [Commits](https://github.com/stephenh/ts-proto/compare/v1.181.2...v2.2.0) --- updated-dependencies: - dependency-name: "@changesets/cli" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: tsup dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: turbo dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: vitest dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: "@bufbuild/buf" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: "@types/react" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: concurrently dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev - dependency-name: cypress dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: lint-staged dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: nodemon dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: postcss dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: prettier-plugin-tailwindcss dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: sass dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: start-server-and-test dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: tailwindcss dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: ts-proto dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev ... Signed-off-by: dependabot[bot] --- apps/login/package.json | 28 +- package.json | 12 +- packages/zitadel-node/package.json | 2 +- packages/zitadel-proto/package.json | 2 +- packages/zitadel-tailwind-config/package.json | 2 +- pnpm-lock.yaml | 1253 +++++++++-------- 6 files changed, 671 insertions(+), 628 deletions(-) diff --git a/apps/login/package.json b/apps/login/package.json index 5f43ecb166..071fd9bd01 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -54,12 +54,12 @@ "tinycolor2": "1.4.2" }, "devDependencies": { - "@bufbuild/buf": "^1.36.0", + "@bufbuild/buf": "^1.41.0", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^16.0.1", "@types/ms": "0.7.34", - "@types/node": "22.1.0", - "@types/react": "18.3.3", + "@types/node": "22.5.5", + "@types/react": "18.3.7", "@types/react-dom": "18.3.0", "@types/tinycolor2": "1.4.3", "@types/uuid": "^10.0.0", @@ -67,23 +67,23 @@ "@zitadel/prettier-config": "workspace:*", "@zitadel/tsconfig": "workspace:*", "autoprefixer": "10.4.20", - "concurrently": "^8.1.0", - "cypress": "^13.13.2", + "concurrently": "^9.0.1", + "cypress": "^13.14.2", "del-cli": "5.1.0", "env-cmd": "^10.1.0", "eslint-config-zitadel": "workspace:*", "grpc-tools": "1.12.4", "jsdom": "^25.0.0", - "lint-staged": "15.2.8", + "lint-staged": "15.2.10", "make-dir-cli": "4.0.0", - "nodemon": "^3.1.4", - "postcss": "8.4.41", - "prettier-plugin-tailwindcss": "0.6.5", - "sass": "^1.77.1", - "start-server-and-test": "^2.0.0", - "tailwindcss": "3.4.9", - "ts-proto": "^1.139.0", - "typescript": "^5.4.5", + "nodemon": "^3.1.5", + "postcss": "8.4.47", + "prettier-plugin-tailwindcss": "0.6.6", + "sass": "^1.79.1", + "start-server-and-test": "^2.0.8", + "tailwindcss": "3.4.12", + "ts-proto": "^2.2.0", + "typescript": "^5.6.2", "zitadel-tailwind-config": "workspace:*" } } diff --git a/package.json b/package.json index b05b3bbf77..4ec7f249a3 100644 --- a/package.json +++ b/package.json @@ -24,17 +24,17 @@ } }, "devDependencies": { - "@changesets/cli": "^2.22.0", + "@changesets/cli": "^2.27.8", "@vitejs/plugin-react": "^4.2.1", - "eslint": "8.57.0", + "eslint": "8.57.1", "eslint-config-zitadel": "workspace:*", "@zitadel/prettier-config": "workspace:*", "prettier": "^3.2.5", "prettier-plugin-organize-imports": "^4.0.0", - "tsup": "^8.2.4", - "turbo": "2.0.12", - "typescript": "^5.4.5", + "tsup": "^8.3.0", + "turbo": "2.1.2", + "typescript": "^5.6.2", "vite-tsconfig-paths": "^5.0.1", - "vitest": "^2.0.5" + "vitest": "^2.1.1" } } diff --git a/packages/zitadel-node/package.json b/packages/zitadel-node/package.json index 8a28ffebd2..844e0ea86c 100644 --- a/packages/zitadel-node/package.json +++ b/packages/zitadel-node/package.json @@ -36,7 +36,7 @@ "jose": "^5.3.0" }, "devDependencies": { - "@types/node": "^22.1.0", + "@types/node": "^22.5.5", "@zitadel/client": "workspace:*", "@zitadel/tsconfig": "workspace:*", "eslint-config-zitadel": "workspace:*" diff --git a/packages/zitadel-proto/package.json b/packages/zitadel-proto/package.json index e2d910b6c0..5412b3f371 100644 --- a/packages/zitadel-proto/package.json +++ b/packages/zitadel-proto/package.json @@ -18,6 +18,6 @@ "@bufbuild/protobuf": "^2.0.0" }, "devDependencies": { - "@bufbuild/buf": "^1.36.0" + "@bufbuild/buf": "^1.41.0" } } diff --git a/packages/zitadel-tailwind-config/package.json b/packages/zitadel-tailwind-config/package.json index d8aa54b4b8..03e895a041 100644 --- a/packages/zitadel-tailwind-config/package.json +++ b/packages/zitadel-tailwind-config/package.json @@ -4,7 +4,7 @@ "private": true, "main": "index.js", "devDependencies": { - "tailwindcss": "^3.4.9", + "tailwindcss": "^3.4.12", "@tailwindcss/forms": "0.5.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f3ceeddc7..f748c0973f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,17 +12,17 @@ importers: .: devDependencies: '@changesets/cli': - specifier: ^2.22.0 - version: 2.27.7 + specifier: ^2.27.8 + version: 2.27.8 '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.3.1(vite@5.4.2) + version: 4.3.1(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1)) '@zitadel/prettier-config': specifier: workspace:* version: link:packages/zitadel-prettier-config eslint: - specifier: 8.57.0 - version: 8.57.0 + specifier: 8.57.1 + version: 8.57.1 eslint-config-zitadel: specifier: workspace:* version: link:packages/eslint-config-zitadel @@ -31,37 +31,37 @@ importers: version: 3.3.3 prettier-plugin-organize-imports: specifier: ^4.0.0 - version: 4.0.0(prettier@3.3.3)(typescript@5.5.4) + version: 4.0.0(prettier@3.3.3)(typescript@5.6.2) tsup: - specifier: ^8.2.4 - version: 8.2.4(typescript@5.5.4) + specifier: ^8.3.0 + version: 8.3.0(jiti@1.21.6)(postcss@8.4.47)(typescript@5.6.2)(yaml@2.5.0) turbo: - specifier: 2.0.12 - version: 2.0.12 + specifier: 2.1.2 + version: 2.1.2 typescript: - specifier: ^5.4.5 - version: 5.5.4 + specifier: ^5.6.2 + version: 5.6.2 vite-tsconfig-paths: specifier: ^5.0.1 - version: 5.0.1(typescript@5.5.4)(vite@5.4.2) + version: 5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1)) vitest: - specifier: ^2.0.5 - version: 2.0.5 + specifier: ^2.1.1 + version: 2.1.1(@types/node@22.5.5)(jsdom@25.0.0)(sass@1.79.1) apps/login: dependencies: '@headlessui/react': specifier: ^1.7.18 - version: 1.7.19(react-dom@18.3.1)(react@18.3.1) + version: 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@heroicons/react': specifier: 2.1.3 version: 2.1.3(react@18.3.1) '@tailwindcss/forms': specifier: 0.5.7 - version: 0.5.7(tailwindcss@3.4.9) + version: 0.5.7(tailwindcss@3.4.12) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@14.2.5)(react@18.3.1) + version: 1.3.1(next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -82,10 +82,10 @@ importers: version: 2.30.1 next: specifier: 14.2.5 - version: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8) + version: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@14.2.5)(react-dom@18.3.1)(react@18.3.1) + version: 0.2.1(next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nice-grpc: specifier: 2.0.1 version: 2.0.1 @@ -109,23 +109,23 @@ importers: version: 1.4.2 devDependencies: '@bufbuild/buf': - specifier: ^1.36.0 - version: 1.39.0 + specifier: ^1.41.0 + version: 1.41.0 '@testing-library/jest-dom': specifier: ^6.4.5 version: 6.5.0 '@testing-library/react': specifier: ^16.0.1 - version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/ms': specifier: 0.7.34 version: 0.7.34 '@types/node': - specifier: 22.1.0 - version: 22.1.0 + specifier: 22.5.5 + version: 22.5.5 '@types/react': - specifier: 18.3.3 - version: 18.3.3 + specifier: 18.3.7 + version: 18.3.7 '@types/react-dom': specifier: 18.3.0 version: 18.3.0 @@ -146,13 +146,13 @@ importers: version: link:../../packages/zitadel-tsconfig autoprefixer: specifier: 10.4.20 - version: 10.4.20(postcss@8.4.41) + version: 10.4.20(postcss@8.4.47) concurrently: - specifier: ^8.1.0 - version: 8.2.2 + specifier: ^9.0.1 + version: 9.0.1 cypress: - specifier: ^13.13.2 - version: 13.14.1 + specifier: ^13.14.2 + version: 13.14.2 del-cli: specifier: 5.1.0 version: 5.1.0 @@ -169,35 +169,35 @@ importers: specifier: ^25.0.0 version: 25.0.0 lint-staged: - specifier: 15.2.8 - version: 15.2.8 + specifier: 15.2.10 + version: 15.2.10 make-dir-cli: specifier: 4.0.0 version: 4.0.0 nodemon: - specifier: ^3.1.4 - version: 3.1.4 + specifier: ^3.1.5 + version: 3.1.5 postcss: - specifier: 8.4.41 - version: 8.4.41 + specifier: 8.4.47 + version: 8.4.47 prettier-plugin-tailwindcss: - specifier: 0.6.5 - version: 0.6.5(prettier-plugin-organize-imports@4.0.0)(prettier@3.3.3) + specifier: 0.6.6 + version: 0.6.6(prettier-plugin-organize-imports@4.0.0(prettier@3.3.3)(typescript@5.6.2))(prettier@3.3.3) sass: - specifier: ^1.77.1 - version: 1.77.8 + specifier: ^1.79.1 + version: 1.79.1 start-server-and-test: - specifier: ^2.0.0 - version: 2.0.5 + specifier: ^2.0.8 + version: 2.0.8 tailwindcss: - specifier: 3.4.9 - version: 3.4.9 + specifier: 3.4.12 + version: 3.4.12 ts-proto: - specifier: ^1.139.0 - version: 1.181.2 + specifier: ^2.2.0 + version: 2.2.0 typescript: - specifier: ^5.4.5 - version: 5.5.4 + specifier: ^5.6.2 + version: 5.6.2 zitadel-tailwind-config: specifier: workspace:* version: link:../../packages/zitadel-tailwind-config @@ -206,19 +206,19 @@ importers: dependencies: '@typescript-eslint/parser': specifier: ^7.9.0 - version: 7.18.0(eslint@8.57.0)(typescript@5.5.4) + version: 7.18.0(eslint@8.57.1)(typescript@5.6.2) eslint-config-next: specifier: ^14.2.3 - version: 14.2.7(eslint@8.57.0)(typescript@5.5.4) + version: 14.2.7(eslint@8.57.1)(typescript@5.6.2) eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@8.57.0) + version: 9.1.0(eslint@8.57.1) eslint-config-turbo: specifier: ^2.0.9 - version: 2.1.0(eslint@8.57.0) + version: 2.1.0(eslint@8.57.1) eslint-plugin-react: specifier: ^7.34.1 - version: 7.35.0(eslint@8.57.0) + version: 7.35.0(eslint@8.57.1) packages/zitadel-client: dependencies: @@ -243,17 +243,17 @@ importers: dependencies: '@connectrpc/connect-node': specifier: ^2.0.0-alpha.1 - version: 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1) + version: 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)) '@connectrpc/connect-web': specifier: ^2.0.0-alpha.1 - version: 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1) + version: 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)) jose: specifier: ^5.3.0 version: 5.8.0 devDependencies: '@types/node': - specifier: ^22.1.0 - version: 22.1.0 + specifier: ^22.5.5 + version: 22.5.5 '@zitadel/client': specifier: workspace:* version: link:../zitadel-client @@ -273,17 +273,17 @@ importers: version: 2.0.0 devDependencies: '@bufbuild/buf': - specifier: ^1.36.0 - version: 1.39.0 + specifier: ^1.41.0 + version: 1.41.0 packages/zitadel-tailwind-config: devDependencies: '@tailwindcss/forms': specifier: 0.5.3 - version: 0.5.3(tailwindcss@3.4.9) + version: 0.5.3(tailwindcss@3.4.12) tailwindcss: - specifier: ^3.4.9 - version: 3.4.9 + specifier: ^3.4.12 + version: 3.4.12 packages/zitadel-tsconfig: {} @@ -391,95 +391,95 @@ packages: resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} engines: {node: '>=6.9.0'} - '@bufbuild/buf-darwin-arm64@1.39.0': - resolution: {integrity: sha512-Ptl0uAGssLxQTzoZhGwv1FFTbzUfcstIpEwMhN+XrwiuqsSxOg9eq/n3yXoci5VJsHokjDUHnWkR3y+j5P/5KA==} + '@bufbuild/buf-darwin-arm64@1.41.0': + resolution: {integrity: sha512-+G5DwpIgnm0AkqgxORxoYXVT0RGDcw8P4SXFXcovgvDBkk9rPvEI1dbPF83n3SUxzcu2A2OxC7DxlXszWIh2Gw==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - '@bufbuild/buf-darwin-x64@1.39.0': - resolution: {integrity: sha512-XNCuy9sjQwVJ4NIZqxaTIyzUtlyquSkp/Uuoh5W5thJ3nzZ5RSgvXKF5iXHhZmesrfRGApktwoCx5Am8runsfQ==} + '@bufbuild/buf-darwin-x64@1.41.0': + resolution: {integrity: sha512-qjkJ/LAWqNk3HX65n+JTt18WtKrhrrAhIu3Dpfbe0eujsxafFZKoPzlWJYybxvsaF9CdEyMMm/OalBPpoosMOA==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - '@bufbuild/buf-linux-aarch64@1.39.0': - resolution: {integrity: sha512-Am+hrw94awp/eY027ROXwRQBuwAzOpQ/4zI4dgmgsyhzeWZ8w1LWC8z2SSr8T2cqd0cm52KxtoWMW+B3b2qzbw==} + '@bufbuild/buf-linux-aarch64@1.41.0': + resolution: {integrity: sha512-5E+MLAF4QHPwAjwVVRRP3Is2U3zpIpQQR7S3di9HlKACbgvefJEBrUfRqQZvHrMuuynQRqjFuZD16Sfvxn9rCQ==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - '@bufbuild/buf-linux-x64@1.39.0': - resolution: {integrity: sha512-JXVkHoMrTvmpseqdoQPJJ6MRV7/vlloYtvXHHACEzVytYjljOYCNoVET/E5gLBco/edeXFMNc40cCi1KgL3rSw==} + '@bufbuild/buf-linux-x64@1.41.0': + resolution: {integrity: sha512-W4T+uqmdtypzzatv6OXjUzGacZiNzGECogr+qDkJF38MSZd3jHXhTEN2KhRckl3i9rRAnfHBwG68BjCTxxBCOQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - '@bufbuild/buf-win32-arm64@1.39.0': - resolution: {integrity: sha512-akdGW02mo04wbLfjNMBQqxC4mPQ/L/vTU8/o79I67GSxyFYt7bKifvYIYhAA39C2gibHyB7ZLmoeRPbaU8wbYA==} + '@bufbuild/buf-win32-arm64@1.41.0': + resolution: {integrity: sha512-OsRVoTZHJZYGIphAwaRqcCeYR9Sk5VEMjpCJiFt/dkHxx2acKH4u/7O+633gcCxQL8EnsU2l8AfdbW7sQaOvlg==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - '@bufbuild/buf-win32-x64@1.39.0': - resolution: {integrity: sha512-jos08UMg9iUZsGjPrNpLXP+FNk6q6GizO+bjee/GcI0kSijIzXYMg14goQr0TKlvqs/+IRAM5vZIokQBYlAENQ==} + '@bufbuild/buf-win32-x64@1.41.0': + resolution: {integrity: sha512-2KJLp7Py0GsfRjDxwBzS17RMpaYFGCvzkwY5CtxfPMw8cg6cE7E36r+vcjHh5dBOj/CumaiXLTwxhCSBtp0V1g==} engines: {node: '>=12'} cpu: [x64] os: [win32] - '@bufbuild/buf@1.39.0': - resolution: {integrity: sha512-lm7xb9pc7X04rRjCQ69o9byAAZ7/dsUQGoH+iJ9uBSXQWiwQ1Ts8gneBnuUVsAH2vdW73NFBpmNQGE9XtFauVQ==} + '@bufbuild/buf@1.41.0': + resolution: {integrity: sha512-6pN2fqMrPqnIkrC1q9KpXpu7fv3Rul0ZPhT4MSYYj+8VcyR3kbLVk6K+CzzPvYhr4itfotnI3ZVGQ/X/vupECg==} engines: {node: '>=12'} hasBin: true '@bufbuild/protobuf@2.0.0': resolution: {integrity: sha512-sw2JhwJyvyL0zlhG61aDzOVryEfJg2PDZFSV7i7IdC7nAE41WuXCru3QWLGiP87At0BMzKOoKO/FqEGoKygGZQ==} - '@changesets/apply-release-plan@7.0.4': - resolution: {integrity: sha512-HLFwhKWayKinWAul0Vj+76jVx1Pc2v55MGPVjZ924Y/ROeSsBMFutv9heHmCUj48lJyRfOTJG5+ar+29FUky/A==} + '@changesets/apply-release-plan@7.0.5': + resolution: {integrity: sha512-1cWCk+ZshEkSVEZrm2fSj1Gz8sYvxgUL4Q78+1ZZqeqfuevPTPk033/yUZ3df8BKMohkqqHfzj0HOOrG0KtXTw==} - '@changesets/assemble-release-plan@6.0.3': - resolution: {integrity: sha512-bLNh9/Lgl1VwkjWZTq8JmRqH+hj7/Yzfz0jsQ/zJJ+FTmVqmqPj3szeKOri8O/hEM8JmHW019vh2gTO9iq5Cuw==} + '@changesets/assemble-release-plan@6.0.4': + resolution: {integrity: sha512-nqICnvmrwWj4w2x0fOhVj2QEGdlUuwVAwESrUo5HLzWMI1rE5SWfsr9ln+rDqWB6RQ2ZyaMZHUcU7/IRaUJS+Q==} '@changesets/changelog-git@0.2.0': resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} - '@changesets/cli@2.27.7': - resolution: {integrity: sha512-6lr8JltiiXPIjDeYg4iM2MeePP6VN/JkmqBsVA5XRiy01hGS3y629LtSDvKcycj/w/5Eur1rEwby/MjcYS+e2A==} + '@changesets/cli@2.27.8': + resolution: {integrity: sha512-gZNyh+LdSsI82wBSHLQ3QN5J30P4uHKJ4fXgoGwQxfXwYFTJzDdvIJasZn8rYQtmKhyQuiBj4SSnLuKlxKWq4w==} hasBin: true - '@changesets/config@3.0.2': - resolution: {integrity: sha512-cdEhS4t8woKCX2M8AotcV2BOWnBp09sqICxKapgLHf9m5KdENpWjyrFNMjkLqGJtUys9U+w93OxWT0czorVDfw==} + '@changesets/config@3.0.3': + resolution: {integrity: sha512-vqgQZMyIcuIpw9nqFIpTSNyc/wgm/Lu1zKN5vECy74u95Qx/Wa9g27HdgO4NkVAaq+BGA8wUc/qvbvVNs93n6A==} '@changesets/errors@0.2.0': resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} - '@changesets/get-dependents-graph@2.1.1': - resolution: {integrity: sha512-LRFjjvigBSzfnPU2n/AhFsuWR5DK++1x47aq6qZ8dzYsPtS/I5mNhIGAS68IAxh1xjO9BTtz55FwefhANZ+FCA==} + '@changesets/get-dependents-graph@2.1.2': + resolution: {integrity: sha512-sgcHRkiBY9i4zWYBwlVyAjEM9sAzs4wYVwJUdnbDLnVG3QwAaia1Mk5P8M7kraTOZN+vBET7n8KyB0YXCbFRLQ==} - '@changesets/get-release-plan@4.0.3': - resolution: {integrity: sha512-6PLgvOIwTSdJPTtpdcr3sLtGatT+Jr22+cQwEBJBy6wP0rjB4yJ9lv583J9fVpn1bfQlBkDa8JxbS2g/n9lIyA==} + '@changesets/get-release-plan@4.0.4': + resolution: {integrity: sha512-SicG/S67JmPTrdcc9Vpu0wSQt7IiuN0dc8iR5VScnnTVPfIaLvKmEGRvIaF0kcn8u5ZqLbormZNTO77bCEvyWw==} '@changesets/get-version-range-type@0.4.0': resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} - '@changesets/git@3.0.0': - resolution: {integrity: sha512-vvhnZDHe2eiBNRFHEgMiGd2CT+164dfYyrJDhwwxTVD/OW0FUD6G7+4DIx1dNwkwjHyzisxGAU96q0sVNBns0w==} + '@changesets/git@3.0.1': + resolution: {integrity: sha512-pdgHcYBLCPcLd82aRcuO0kxCDbw/yISlOtkmwmE8Odo1L6hSiZrBOsRl84eYG7DRCab/iHnOkWqExqc4wxk2LQ==} - '@changesets/logger@0.1.0': - resolution: {integrity: sha512-pBrJm4CQm9VqFVwWnSqKEfsS2ESnwqwH+xR7jETxIErZcfd1u2zBSqrHbRHR7xjhSgep9x2PSKFKY//FAshA3g==} + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} '@changesets/parse@0.4.0': resolution: {integrity: sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==} - '@changesets/pre@2.0.0': - resolution: {integrity: sha512-HLTNYX/A4jZxc+Sq8D1AMBsv+1qD6rmmJtjsCJa/9MSRybdxh0mjbTvE6JYZQ/ZiQ0mMlDOlGPXTm9KLTU3jyw==} + '@changesets/pre@2.0.1': + resolution: {integrity: sha512-vvBJ/If4jKM4tPz9JdY2kGOgWmCowUYOi5Ycv8dyLnEE8FgpYYUo1mgJZxcdtGGP3aG8rAQulGLyyXGSLkIMTQ==} - '@changesets/read@0.6.0': - resolution: {integrity: sha512-ZypqX8+/im1Fm98K4YcZtmLKgjs1kDQ5zHpc2U1qdtNBmZZfo/IBiG162RoP0CUF05tvp2y4IspH11PLnPxuuw==} + '@changesets/read@0.6.1': + resolution: {integrity: sha512-jYMbyXQk3nwP25nRzQQGa1nKLY0KfoOV7VLgwucI0bUO8t8ZLCr6LZmgjXsiKuRDc+5A6doKPr9w2d+FEJ55zQ==} - '@changesets/should-skip-package@0.1.0': - resolution: {integrity: sha512-FxG6Mhjw7yFStlSM7Z0Gmg3RiyQ98d/9VpQAZ3Fzr59dCOM9G6ZdYbjiSAt0XtFr9JR5U2tBaJWPjrkGGc618g==} + '@changesets/should-skip-package@0.1.1': + resolution: {integrity: sha512-H9LjLbF6mMHLtJIc/eHR9Na+MifJ3VxtgP/Y+XLn4BF7tDTEN1HNYtH6QMcjP1uxp9sjaFYmW8xqloaCi/ckTg==} '@changesets/types@4.1.0': resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} @@ -487,8 +487,8 @@ packages: '@changesets/types@6.0.0': resolution: {integrity: sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==} - '@changesets/write@0.3.1': - resolution: {integrity: sha512-SyGtMXzH3qFqlHKcvFY2eX+6b0NGiFcNav8AFsYwy5l8hejOeoeTDemu5Yjmke2V5jpzY+pBvM0vCCQ3gdZpfw==} + '@changesets/write@0.3.2': + resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} @@ -807,16 +807,16 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.11.0': - resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} + '@eslint-community/regexpp@4.11.1': + resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/eslintrc@2.1.4': resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@eslint/js@8.57.0': - resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} '@fastify/busboy@2.1.1': @@ -850,8 +850,8 @@ packages: peerDependencies: react: '>= 16' - '@humanwhocodes/config-array@0.11.14': - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} deprecated: Use @eslint/config-array instead @@ -1008,83 +1008,83 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - '@rollup/rollup-android-arm-eabi@4.21.1': - resolution: {integrity: sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==} + '@rollup/rollup-android-arm-eabi@4.21.3': + resolution: {integrity: sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.21.1': - resolution: {integrity: sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==} + '@rollup/rollup-android-arm64@4.21.3': + resolution: {integrity: sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.21.1': - resolution: {integrity: sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==} + '@rollup/rollup-darwin-arm64@4.21.3': + resolution: {integrity: sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.21.1': - resolution: {integrity: sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==} + '@rollup/rollup-darwin-x64@4.21.3': + resolution: {integrity: sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.21.1': - resolution: {integrity: sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.21.3': + resolution: {integrity: sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.21.1': - resolution: {integrity: sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==} + '@rollup/rollup-linux-arm-musleabihf@4.21.3': + resolution: {integrity: sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.21.1': - resolution: {integrity: sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==} + '@rollup/rollup-linux-arm64-gnu@4.21.3': + resolution: {integrity: sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.21.1': - resolution: {integrity: sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==} + '@rollup/rollup-linux-arm64-musl@4.21.3': + resolution: {integrity: sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.21.1': - resolution: {integrity: sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==} + '@rollup/rollup-linux-powerpc64le-gnu@4.21.3': + resolution: {integrity: sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.21.1': - resolution: {integrity: sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==} + '@rollup/rollup-linux-riscv64-gnu@4.21.3': + resolution: {integrity: sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.21.1': - resolution: {integrity: sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==} + '@rollup/rollup-linux-s390x-gnu@4.21.3': + resolution: {integrity: sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.21.1': - resolution: {integrity: sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==} + '@rollup/rollup-linux-x64-gnu@4.21.3': + resolution: {integrity: sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.21.1': - resolution: {integrity: sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==} + '@rollup/rollup-linux-x64-musl@4.21.3': + resolution: {integrity: sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.21.1': - resolution: {integrity: sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==} + '@rollup/rollup-win32-arm64-msvc@4.21.3': + resolution: {integrity: sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.21.1': - resolution: {integrity: sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==} + '@rollup/rollup-win32-ia32-msvc@4.21.3': + resolution: {integrity: sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.21.1': - resolution: {integrity: sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==} + '@rollup/rollup-win32-x64-msvc@4.21.3': + resolution: {integrity: sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==} cpu: [x64] os: [win32] @@ -1178,8 +1178,8 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@22.1.0': - resolution: {integrity: sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==} + '@types/node@22.5.5': + resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1190,8 +1190,8 @@ packages: '@types/react-dom@18.3.0': resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} - '@types/react@18.3.3': - resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + '@types/react@18.3.7': + resolution: {integrity: sha512-KUnDCJF5+AiZd8owLIeVHqmW9yM4sqmDVf2JRJiBMFkGvkoZ4/WyV2lL4zVsoinmRS/W3FeEdZLEWFRofnT2FQ==} '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} @@ -1265,23 +1265,35 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 - '@vitest/expect@2.0.5': - resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} + '@vitest/expect@2.1.1': + resolution: {integrity: sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==} - '@vitest/pretty-format@2.0.5': - resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} + '@vitest/mocker@2.1.1': + resolution: {integrity: sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==} + peerDependencies: + '@vitest/spy': 2.1.1 + msw: ^2.3.5 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true - '@vitest/runner@2.0.5': - resolution: {integrity: sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==} + '@vitest/pretty-format@2.1.1': + resolution: {integrity: sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==} - '@vitest/snapshot@2.0.5': - resolution: {integrity: sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==} + '@vitest/runner@2.1.1': + resolution: {integrity: sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==} - '@vitest/spy@2.0.5': - resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} + '@vitest/snapshot@2.1.1': + resolution: {integrity: sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==} - '@vitest/utils@2.0.5': - resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} + '@vitest/spy@2.1.1': + resolution: {integrity: sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==} + + '@vitest/utils@2.1.1': + resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==} abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -1334,8 +1346,8 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} ansi-styles@3.2.1: @@ -1476,8 +1488,8 @@ packages: resolution: {integrity: sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==} engines: {node: '>=4'} - axios@1.7.5: - resolution: {integrity: sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==} + axios@1.7.7: + resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} axobject-query@3.1.1: resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} @@ -1609,6 +1621,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.0: + resolution: {integrity: sha512-mxIojEAQcuEvT/lyXq+jf/3cO/KoA6z4CeNDGGevTybECPOMFCnQy3OPahluUkbqgPNGw5Bi78UC7Po6Lhy+NA==} + engines: {node: '>= 14.16.0'} + chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -1699,9 +1715,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concurrently@8.2.2: - resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} - engines: {node: ^14.13.0 || >=16.0.0} + concurrently@9.0.1: + resolution: {integrity: sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==} + engines: {node: '>=18'} hasBin: true consola@3.2.3: @@ -1742,8 +1758,8 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - cypress@13.14.1: - resolution: {integrity: sha512-Wo+byPmjps66hACEH5udhXINEiN3qS3jWNGRzJOjrRJF3D0+YrcP2LVB1T7oYaVQM/S+eanqEvBWYc8cf7Vcbg==} + cypress@13.14.2: + resolution: {integrity: sha512-lsiQrN17vHMB2fnvxIrKLAjOr9bPwsNbPZNrWf99s4u+DVmCY6U+w7O3GGG9FvP4EUVYaDu+guWeNLiUzBrqvA==} engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -1770,10 +1786,6 @@ packages: resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} engines: {node: '>= 0.4'} - date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} - engines: {node: '>=0.11'} - dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} @@ -1794,6 +1806,15 @@ packages: supports-color: optional: true + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} @@ -2098,8 +2119,8 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true @@ -2194,6 +2215,14 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.3.0: + resolution: {integrity: sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -2214,9 +2243,6 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - find-yarn-workspace-root2@1.2.16: - resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} - flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} @@ -2837,8 +2863,8 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lint-staged@15.2.8: - resolution: {integrity: sha512-PUWFf2zQzsd9EFU+kM1d7UP+AZDbKFKuj+9JNVTBkhUFhbg4MAt6WfyMMwBfM4lYqd4D2Jwac5iuTu9rVj4zCQ==} + lint-staged@15.2.10: + resolution: {integrity: sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==} engines: {node: '>=18.12.0'} hasBin: true @@ -2859,10 +2885,6 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - load-yaml-file@0.2.0: - resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} - engines: {node: '>=6'} - locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -3104,8 +3126,8 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - nodemon@3.1.4: - resolution: {integrity: sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==} + nodemon@3.1.5: + resolution: {integrity: sha512-V5UtfYc7hjFD4SI3EzD5TR8ChAHEZ+Ns7Z5fBk8fAbTVAj+q3G+w7sHJrHxXBkVn6ApLVTljau8wfHwqmGUjMw==} engines: {node: '>=10'} hasBin: true @@ -3249,6 +3271,9 @@ packages: package-json-from-dist@1.0.0: resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + package-manager-detector@0.2.0: + resolution: {integrity: sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -3306,10 +3331,17 @@ packages: picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} @@ -3327,10 +3359,6 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} - possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -3394,14 +3422,10 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.4.41: - resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} - preferred-pm@3.1.4: - resolution: {integrity: sha512-lEHd+yEm22jXdCphDrkvIJQU66EuLojPPtvZkpKIkiD+l0DMThF/niqZKJSoU8Vl7iuvtmzyMhir9LdVy5WMnA==} - engines: {node: '>=10'} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -3419,8 +3443,8 @@ packages: vue-tsc: optional: true - prettier-plugin-tailwindcss@0.6.5: - resolution: {integrity: sha512-axfeOArc/RiGHjOIy9HytehlC0ZLeMaqY09mm8YCkMzznKiDkwFzOpBvtuhuv3xG5qB73+Mj7OCe2j/L1ryfuQ==} + prettier-plugin-tailwindcss@0.6.6: + resolution: {integrity: sha512-OPva5S7WAsPLEsOuOWXATi13QrCKACCiIonFgIR6V4lYv4QLp++UXVhZSzRbZxXGimkQtQT86CC6fQqTOybGng==} engines: {node: '>=14.21.3'} peerDependencies: '@ianvs/prettier-plugin-sort-imports': '*' @@ -3434,6 +3458,7 @@ packages: prettier-plugin-import-sort: '*' prettier-plugin-jsdoc: '*' prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' prettier-plugin-organize-attributes: '*' prettier-plugin-organize-imports: '*' prettier-plugin-sort-imports: '*' @@ -3460,6 +3485,8 @@ packages: optional: true prettier-plugin-marko: optional: true + prettier-plugin-multiline-arrays: + optional: true prettier-plugin-organize-attributes: optional: true prettier-plugin-organize-imports: @@ -3594,6 +3621,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.0.1: + resolution: {integrity: sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==} + engines: {node: '>= 14.16.0'} + redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -3662,8 +3693,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.21.1: - resolution: {integrity: sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==} + rollup@4.21.3: + resolution: {integrity: sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -3693,8 +3724,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.77.8: - resolution: {integrity: sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==} + sass@1.79.1: + resolution: {integrity: sha512-+mA7svoNKeL0DiJqZGeR/ZGUu8he4I8o3jyUcOFyo4eBJrwNgIMmAEwCMo/N2Y3wdjOBcRzoNxZIOtrtMX8EXg==} engines: {node: '>=14.0.0'} hasBin: true @@ -3789,17 +3820,14 @@ packages: resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} engines: {node: '>=18'} - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} - spawn-command@0.0.2: - resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} - spawndamnit@2.0.0: resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} @@ -3829,8 +3857,8 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - start-server-and-test@2.0.5: - resolution: {integrity: sha512-2CV4pz69NJVJKQmJeSr+O+SPtOreu0yxvhPmSXclzmAKkPREuMabyMh+Txpzemjx0RDzXOcG2XkhiUuxjztSQw==} + start-server-and-test@2.0.8: + resolution: {integrity: sha512-v2fV6NV2F7tL1ocwfI4Wpait+IKjRbT5l3ZZ+ZikXdMLmxYsS8ynGAsCQAUVXkVyGyS+UibsRnvgHkMvJIvCsw==} engines: {node: '>=16'} hasBin: true @@ -3962,8 +3990,8 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - tailwindcss@3.4.9: - resolution: {integrity: sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==} + tailwindcss@3.4.12: + resolution: {integrity: sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==} engines: {node: '>=14.0.0'} hasBin: true @@ -4001,6 +4029,13 @@ packages: tinycolor2@1.4.2: resolution: {integrity: sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==} + tinyexec@0.3.0: + resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} + + tinyglobby@0.2.6: + resolution: {integrity: sha512-NbBoFBpqfcgd1tCiO8Lkfdk+xrA7mlLR9zgvZcZWQQwU63XAfUePyd6wZBaU93Hqw347lHnwFzttAkemHzzz4g==} + engines: {node: '>=12.0.0'} + tinypool@1.0.1: resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -4009,8 +4044,8 @@ packages: resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} - tinyspy@3.0.0: - resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} tmp@0.0.33: @@ -4073,11 +4108,11 @@ packages: ts-poet@6.9.0: resolution: {integrity: sha512-roe6W6MeZmCjRmppyfOURklO5tQFQ6Sg7swURKkwYJvV7dbGCrK28um5+51iW3twdPRKtwarqFAVMU6G1mvnuQ==} - ts-proto-descriptors@1.16.0: - resolution: {integrity: sha512-3yKuzMLpltdpcyQji1PJZRfoo4OJjNieKTYkQY8pF7xGKsYz/RHe3aEe4KiRxcinoBmnEhmuI+yJTxLb922ULA==} + ts-proto-descriptors@2.0.0: + resolution: {integrity: sha512-wHcTH3xIv11jxgkX5OyCSFfw27agpInAd6yh89hKG6zqIXnjW9SYqSER2CVQxdPj4czeOhGagNvZBEbJPy7qkw==} - ts-proto@1.181.2: - resolution: {integrity: sha512-knJ8dtjn2Pd0c5ZGZG8z9DMiD4PUY8iGI9T9tb8DvGdWRMkLpf0WcPO7G+7cmbZyxvNTAG6ci3fybEaFgMZIvg==} + ts-proto@2.2.0: + resolution: {integrity: sha512-xzmnyrarUjPnY+Py4RyTh3lYmL9w5t/oTtRTo2rKF8laAAahpGZ/ELxkXFEZns5JVbgkYke3C17HN5iNvZOs4g==} hasBin: true tsconfck@3.1.1: @@ -4096,8 +4131,8 @@ packages: tslib@2.7.0: resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} - tsup@8.2.4: - resolution: {integrity: sha512-akpCPePnBnC/CXgRrcy72ZSntgIEUa1jN0oJbbvpALWKNOz1B7aM+UVDWGRGIO/T/PZugAESWDJUAb5FD48o8Q==} + tsup@8.3.0: + resolution: {integrity: sha512-ALscEeyS03IomcuNdFdc0YWGVIkwH1Ws7nfTbAPuoILvEV2hpGQAY72LIOjglGo4ShWpZfpBqP/jpQVCzqYQag==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -4118,38 +4153,38 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - turbo-darwin-64@2.0.12: - resolution: {integrity: sha512-NAgfgbXxX/JScWQmmQnGbPuFZq7LIswHfcMk5JwyBXQM/xmklNOxxac7MnGGIOf19Z2f6S3qHy17VIj0SeGfnA==} + turbo-darwin-64@2.1.2: + resolution: {integrity: sha512-3TEBxHWh99h2yIzkuIigMEOXt/ItYQp0aPiJjPd1xN4oDcsKK5AxiFKPH9pdtfIBzYsY59kQhZiFj0ELnSP7Bw==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@2.0.12: - resolution: {integrity: sha512-cP02uer5KSJ+fXL+OfRRk5hnVjV0c60hxDgNcJxrZpfhun7HHoKDDR7w2xhQntiA45aC6ZZEXRqMKpj6GAmKbg==} + turbo-darwin-arm64@2.1.2: + resolution: {integrity: sha512-he0miWNq2WxJzsH82jS2Z4MXpnkzn9SH8a79iPXiJkq25QREImucscM4RPasXm8wARp91pyysJMq6aasD45CeA==} cpu: [arm64] os: [darwin] - turbo-linux-64@2.0.12: - resolution: {integrity: sha512-+mQgGfg1eq5qF+wenK/FKJaNMNAo5DQLC4htQy+8osW+fx6U+8+6UlPQPaycAWDEqwOI7NwuqkeHfkEQLQUTyQ==} + turbo-linux-64@2.1.2: + resolution: {integrity: sha512-fKUBcc0rK8Vdqv5a/E3CSpMBLG1bzwv+Q0Q83F8fG2ZfNCNKGbcEYABdonNZkkx141Rj03cZQFCgxu3MVEGU+A==} cpu: [x64] os: [linux] - turbo-linux-arm64@2.0.12: - resolution: {integrity: sha512-KFyEZDXfPU1DK4zimxdCcqAcK7IIttX4mfsgB7NsSEOmH0dhHOih/YFYiyEDC1lTRx0C2RlzQ0Kjjdz48AN5Eg==} + turbo-linux-arm64@2.1.2: + resolution: {integrity: sha512-sV8Bpmm0WiuxgbhxymcC7wSsuxfBBieI98GegSwbr/bs1ANAgzCg93urIrdKdQ3/b31zZxQwcaP4FBF1wx1Qdg==} cpu: [arm64] os: [linux] - turbo-windows-64@2.0.12: - resolution: {integrity: sha512-kJj4KCkZTkDTDCqsSw1m1dbO4WeoQq1mYUm/thXOH0OkeqYbSMt0EyoTcJOgKUDsrMnzZD2gPfYrlYHtV69lVA==} + turbo-windows-64@2.1.2: + resolution: {integrity: sha512-wcmIJZI9ORT9ykHGliFE6kWRQrlH930QGSjSgWC8uFChFFuOyUlvC7ttcxuSvU9VqC7NF4C+GVAcFJQ8lTjN7g==} cpu: [x64] os: [win32] - turbo-windows-arm64@2.0.12: - resolution: {integrity: sha512-TY3ROxguDilN2olCwcZMaePdW01Xhma0pZU7bNhsQEqca9RGAmsZBuzfGnTMcWPmv4tpnb/PlX1hrt1Hod/44Q==} + turbo-windows-arm64@2.1.2: + resolution: {integrity: sha512-zdnXjrhk7YO6CP+Q5wPueEvOCLH4lDa6C4rrwiakcWcPgcQGbVozJlo4uaQ6awo8HLWQEvOwu84RkWTdLAc/Hw==} cpu: [arm64] os: [win32] - turbo@2.0.12: - resolution: {integrity: sha512-8s2KwqjwQj7z8Z53SUZSKVkQOZ2/Sl4D2F440oaBY/k2lGju60dW6srEpnn8/RIDeICZmQn3pQHF79Jfnc5Skw==} + turbo@2.1.2: + resolution: {integrity: sha512-Jb0rbU4iHEVQ18An/YfakdIv9rKnd3zUfSE117EngrfWXFHo3RndVH96US3GsT8VHpwTncPePDBT2t06PaFLrw==} hasBin: true tweetnacl@0.14.5: @@ -4187,8 +4222,8 @@ packages: resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} - typescript@5.5.4: - resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + typescript@5.6.2: + resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} engines: {node: '>=14.17'} hasBin: true @@ -4198,8 +4233,8 @@ packages: undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} - undici-types@6.13.0: - resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} @@ -4252,8 +4287,8 @@ packages: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} - vite-node@2.0.5: - resolution: {integrity: sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==} + vite-node@2.1.1: + resolution: {integrity: sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -4265,8 +4300,8 @@ packages: vite: optional: true - vite@5.4.2: - resolution: {integrity: sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==} + vite@5.4.6: + resolution: {integrity: sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -4296,15 +4331,15 @@ packages: terser: optional: true - vitest@2.0.5: - resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==} + vitest@2.1.1: + resolution: {integrity: sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.0.5 - '@vitest/ui': 2.0.5 + '@vitest/browser': 2.1.1 + '@vitest/ui': 2.1.1 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -4325,8 +4360,8 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} - wait-on@7.2.0: - resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} + wait-on@8.0.1: + resolution: {integrity: sha512-1wWQOyR2LVVtaqrcIL2+OM+x7bkpmzVROa0Nf6FryXkS+er5Sa1kzFGjzZRqLnHa3n1rACFLeTwUqE1ETL9Mig==} engines: {node: '>=12.0.0'} hasBin: true @@ -4369,10 +4404,6 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-pm@2.2.0: - resolution: {integrity: sha512-MOiaDbA5ZZgUjkeMWM5EkJp4loW5ZRoa5bc3/aeMox/PJelMhE6t7S/mLuiY43DBupyxH+S0U1bTui9kWUlmsw==} - engines: {node: '>=8.15'} - which-typed-array@1.1.15: resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} engines: {node: '>= 0.4'} @@ -4487,7 +4518,7 @@ snapshots: '@babel/code-frame@7.24.7': dependencies: '@babel/highlight': 7.24.7 - picocolors: 1.0.1 + picocolors: 1.1.0 '@babel/compat-data@7.25.4': {} @@ -4504,7 +4535,7 @@ snapshots: '@babel/traverse': 7.25.6 '@babel/types': 7.25.6 convert-source-map: 2.0.0 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -4568,7 +4599,7 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.1 + picocolors: 1.1.0 '@babel/parser@7.25.6': dependencies: @@ -4601,7 +4632,7 @@ snapshots: '@babel/parser': 7.25.6 '@babel/template': 7.25.0 '@babel/types': 7.25.6 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -4612,42 +4643,41 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - '@bufbuild/buf-darwin-arm64@1.39.0': + '@bufbuild/buf-darwin-arm64@1.41.0': optional: true - '@bufbuild/buf-darwin-x64@1.39.0': + '@bufbuild/buf-darwin-x64@1.41.0': optional: true - '@bufbuild/buf-linux-aarch64@1.39.0': + '@bufbuild/buf-linux-aarch64@1.41.0': optional: true - '@bufbuild/buf-linux-x64@1.39.0': + '@bufbuild/buf-linux-x64@1.41.0': optional: true - '@bufbuild/buf-win32-arm64@1.39.0': + '@bufbuild/buf-win32-arm64@1.41.0': optional: true - '@bufbuild/buf-win32-x64@1.39.0': + '@bufbuild/buf-win32-x64@1.41.0': optional: true - '@bufbuild/buf@1.39.0': + '@bufbuild/buf@1.41.0': optionalDependencies: - '@bufbuild/buf-darwin-arm64': 1.39.0 - '@bufbuild/buf-darwin-x64': 1.39.0 - '@bufbuild/buf-linux-aarch64': 1.39.0 - '@bufbuild/buf-linux-x64': 1.39.0 - '@bufbuild/buf-win32-arm64': 1.39.0 - '@bufbuild/buf-win32-x64': 1.39.0 + '@bufbuild/buf-darwin-arm64': 1.41.0 + '@bufbuild/buf-darwin-x64': 1.41.0 + '@bufbuild/buf-linux-aarch64': 1.41.0 + '@bufbuild/buf-linux-x64': 1.41.0 + '@bufbuild/buf-win32-arm64': 1.41.0 + '@bufbuild/buf-win32-x64': 1.41.0 '@bufbuild/protobuf@2.0.0': {} - '@changesets/apply-release-plan@7.0.4': + '@changesets/apply-release-plan@7.0.5': dependencies: - '@babel/runtime': 7.25.6 - '@changesets/config': 3.0.2 + '@changesets/config': 3.0.3 '@changesets/get-version-range-type': 0.4.0 - '@changesets/git': 3.0.0 - '@changesets/should-skip-package': 0.1.0 + '@changesets/git': 3.0.1 + '@changesets/should-skip-package': 0.1.1 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 detect-indent: 6.1.0 @@ -4658,12 +4688,11 @@ snapshots: resolve-from: 5.0.0 semver: 7.6.3 - '@changesets/assemble-release-plan@6.0.3': + '@changesets/assemble-release-plan@6.0.4': dependencies: - '@babel/runtime': 7.25.6 '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.1 - '@changesets/should-skip-package': 0.1.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/should-skip-package': 0.1.1 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 semver: 7.6.3 @@ -4672,46 +4701,44 @@ snapshots: dependencies: '@changesets/types': 6.0.0 - '@changesets/cli@2.27.7': + '@changesets/cli@2.27.8': dependencies: - '@babel/runtime': 7.25.6 - '@changesets/apply-release-plan': 7.0.4 - '@changesets/assemble-release-plan': 6.0.3 + '@changesets/apply-release-plan': 7.0.5 + '@changesets/assemble-release-plan': 6.0.4 '@changesets/changelog-git': 0.2.0 - '@changesets/config': 3.0.2 + '@changesets/config': 3.0.3 '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.1 - '@changesets/get-release-plan': 4.0.3 - '@changesets/git': 3.0.0 - '@changesets/logger': 0.1.0 - '@changesets/pre': 2.0.0 - '@changesets/read': 0.6.0 - '@changesets/should-skip-package': 0.1.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/get-release-plan': 4.0.4 + '@changesets/git': 3.0.1 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.1 + '@changesets/read': 0.6.1 + '@changesets/should-skip-package': 0.1.1 '@changesets/types': 6.0.0 - '@changesets/write': 0.3.1 + '@changesets/write': 0.3.2 '@manypkg/get-packages': 1.1.3 '@types/semver': 7.5.8 ansi-colors: 4.1.3 - chalk: 2.4.2 ci-info: 3.9.0 enquirer: 2.4.1 external-editor: 3.1.0 fs-extra: 7.0.1 - human-id: 1.0.2 mri: 1.2.0 outdent: 0.5.0 p-limit: 2.3.0 - preferred-pm: 3.1.4 + package-manager-detector: 0.2.0 + picocolors: 1.1.0 resolve-from: 5.0.0 semver: 7.6.3 spawndamnit: 2.0.0 term-size: 2.2.1 - '@changesets/config@3.0.2': + '@changesets/config@3.0.3': dependencies: '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.1 - '@changesets/logger': 0.1.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/logger': 0.1.1 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 @@ -4721,67 +4748,60 @@ snapshots: dependencies: extendable-error: 0.1.7 - '@changesets/get-dependents-graph@2.1.1': + '@changesets/get-dependents-graph@2.1.2': dependencies: '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 - chalk: 2.4.2 - fs-extra: 7.0.1 + picocolors: 1.1.0 semver: 7.6.3 - '@changesets/get-release-plan@4.0.3': + '@changesets/get-release-plan@4.0.4': dependencies: - '@babel/runtime': 7.25.6 - '@changesets/assemble-release-plan': 6.0.3 - '@changesets/config': 3.0.2 - '@changesets/pre': 2.0.0 - '@changesets/read': 0.6.0 + '@changesets/assemble-release-plan': 6.0.4 + '@changesets/config': 3.0.3 + '@changesets/pre': 2.0.1 + '@changesets/read': 0.6.1 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 '@changesets/get-version-range-type@0.4.0': {} - '@changesets/git@3.0.0': + '@changesets/git@3.0.1': dependencies: - '@babel/runtime': 7.25.6 '@changesets/errors': 0.2.0 - '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 is-subdir: 1.2.0 micromatch: 4.0.8 spawndamnit: 2.0.0 - '@changesets/logger@0.1.0': + '@changesets/logger@0.1.1': dependencies: - chalk: 2.4.2 + picocolors: 1.1.0 '@changesets/parse@0.4.0': dependencies: '@changesets/types': 6.0.0 js-yaml: 3.14.1 - '@changesets/pre@2.0.0': + '@changesets/pre@2.0.1': dependencies: - '@babel/runtime': 7.25.6 '@changesets/errors': 0.2.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 - '@changesets/read@0.6.0': + '@changesets/read@0.6.1': dependencies: - '@babel/runtime': 7.25.6 - '@changesets/git': 3.0.0 - '@changesets/logger': 0.1.0 + '@changesets/git': 3.0.1 + '@changesets/logger': 0.1.1 '@changesets/parse': 0.4.0 '@changesets/types': 6.0.0 - chalk: 2.4.2 fs-extra: 7.0.1 p-filter: 2.1.0 + picocolors: 1.1.0 - '@changesets/should-skip-package@0.1.0': + '@changesets/should-skip-package@0.1.1': dependencies: - '@babel/runtime': 7.25.6 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 @@ -4789,9 +4809,8 @@ snapshots: '@changesets/types@6.0.0': {} - '@changesets/write@0.3.1': + '@changesets/write@0.3.2': dependencies: - '@babel/runtime': 7.25.6 '@changesets/types': 6.0.0 fs-extra: 7.0.1 human-id: 1.0.2 @@ -4800,13 +4819,13 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@connectrpc/connect-node@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1)': + '@connectrpc/connect-node@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0))': dependencies: '@bufbuild/protobuf': 2.0.0 '@connectrpc/connect': 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0) undici: 5.28.4 - '@connectrpc/connect-web@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1)': + '@connectrpc/connect-web@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0))': dependencies: '@bufbuild/protobuf': 2.0.0 '@connectrpc/connect': 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0) @@ -4984,17 +5003,17 @@ snapshots: '@esbuild/win32-x64@0.23.1': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)': dependencies: - eslint: 8.57.0 + eslint: 8.57.1 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.11.0': {} + '@eslint-community/regexpp@4.11.1': {} '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -5005,7 +5024,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@8.57.0': {} + '@eslint/js@8.57.1': {} '@fastify/busboy@2.1.1': {} @@ -5027,9 +5046,9 @@ snapshots: dependencies: '@hapi/hoek': 9.3.0 - '@headlessui/react@1.7.19(react-dom@18.3.1)(react@18.3.1)': + '@headlessui/react@1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@tanstack/react-virtual': 3.10.6(react-dom@18.3.1)(react@18.3.1) + '@tanstack/react-virtual': 3.10.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) client-only: 0.0.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -5038,10 +5057,10 @@ snapshots: dependencies: react: 18.3.1 - '@humanwhocodes/config-array@0.11.14': + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -5182,52 +5201,52 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@rollup/rollup-android-arm-eabi@4.21.1': + '@rollup/rollup-android-arm-eabi@4.21.3': optional: true - '@rollup/rollup-android-arm64@4.21.1': + '@rollup/rollup-android-arm64@4.21.3': optional: true - '@rollup/rollup-darwin-arm64@4.21.1': + '@rollup/rollup-darwin-arm64@4.21.3': optional: true - '@rollup/rollup-darwin-x64@4.21.1': + '@rollup/rollup-darwin-x64@4.21.3': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.21.1': + '@rollup/rollup-linux-arm-gnueabihf@4.21.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.21.1': + '@rollup/rollup-linux-arm-musleabihf@4.21.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.21.1': + '@rollup/rollup-linux-arm64-gnu@4.21.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.21.1': + '@rollup/rollup-linux-arm64-musl@4.21.3': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.21.1': + '@rollup/rollup-linux-powerpc64le-gnu@4.21.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.21.1': + '@rollup/rollup-linux-riscv64-gnu@4.21.3': optional: true - '@rollup/rollup-linux-s390x-gnu@4.21.1': + '@rollup/rollup-linux-s390x-gnu@4.21.3': optional: true - '@rollup/rollup-linux-x64-gnu@4.21.1': + '@rollup/rollup-linux-x64-gnu@4.21.3': optional: true - '@rollup/rollup-linux-x64-musl@4.21.1': + '@rollup/rollup-linux-x64-musl@4.21.3': optional: true - '@rollup/rollup-win32-arm64-msvc@4.21.1': + '@rollup/rollup-win32-arm64-msvc@4.21.3': optional: true - '@rollup/rollup-win32-ia32-msvc@4.21.1': + '@rollup/rollup-win32-ia32-msvc@4.21.3': optional: true - '@rollup/rollup-win32-x64-msvc@4.21.1': + '@rollup/rollup-win32-x64-msvc@4.21.3': optional: true '@rushstack/eslint-patch@1.10.4': {} @@ -5247,17 +5266,17 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.7.0 - '@tailwindcss/forms@0.5.3(tailwindcss@3.4.9)': + '@tailwindcss/forms@0.5.3(tailwindcss@3.4.12)': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.9 + tailwindcss: 3.4.12 - '@tailwindcss/forms@0.5.7(tailwindcss@3.4.9)': + '@tailwindcss/forms@0.5.7(tailwindcss@3.4.12)': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.9 + tailwindcss: 3.4.12 - '@tanstack/react-virtual@3.10.6(react-dom@18.3.1)(react@18.3.1)': + '@tanstack/react-virtual@3.10.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/virtual-core': 3.10.6 react: 18.3.1 @@ -5286,14 +5305,15 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)': + '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.25.6 '@testing-library/dom': 10.4.0 - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.7 + '@types/react-dom': 18.3.0 '@types/aria-query@5.0.4': {} @@ -5328,9 +5348,9 @@ snapshots: '@types/node@12.20.55': {} - '@types/node@22.1.0': + '@types/node@22.5.5': dependencies: - undici-types: 6.13.0 + undici-types: 6.19.8 '@types/normalize-package-data@2.4.4': {} @@ -5338,9 +5358,9 @@ snapshots: '@types/react-dom@18.3.0': dependencies: - '@types/react': 18.3.3 + '@types/react': 18.3.7 - '@types/react@18.3.3': + '@types/react@18.3.7': dependencies: '@types/prop-types': 15.7.12 csstype: 3.1.3 @@ -5357,18 +5377,19 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.1.0 + '@types/node': 22.5.5 optional: true - '@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2)': dependencies: '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.2) '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.6(supports-color@5.5.0) - eslint: 8.57.0 - typescript: 5.5.4 + debug: 4.3.6 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.6.2 transitivePeerDependencies: - supports-color @@ -5379,17 +5400,18 @@ snapshots: '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.5.4)': + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.6.2)': dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.5.4) - typescript: 5.5.4 + ts-api-utils: 1.3.0(typescript@5.6.2) + optionalDependencies: + typescript: 5.6.2 transitivePeerDependencies: - supports-color @@ -5400,55 +5422,63 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/analytics@1.3.1(next@14.2.5)(react@18.3.1)': + '@vercel/analytics@1.3.1(next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1)': dependencies: - next: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8) - react: 18.3.1 server-only: 0.0.1 + optionalDependencies: + next: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + react: 18.3.1 '@vercel/git-hooks@1.0.0': {} - '@vitejs/plugin-react@4.3.1(vite@5.4.2)': + '@vitejs/plugin-react@4.3.1(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1))': dependencies: '@babel/core': 7.25.2 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.2 + vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) transitivePeerDependencies: - supports-color - '@vitest/expect@2.0.5': + '@vitest/expect@2.1.1': dependencies: - '@vitest/spy': 2.0.5 - '@vitest/utils': 2.0.5 + '@vitest/spy': 2.1.1 + '@vitest/utils': 2.1.1 chai: 5.1.1 tinyrainbow: 1.2.0 - '@vitest/pretty-format@2.0.5': + '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1))': + dependencies: + '@vitest/spy': 2.1.1 + estree-walker: 3.0.3 + magic-string: 0.30.11 + optionalDependencies: + vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) + + '@vitest/pretty-format@2.1.1': dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.0.5': + '@vitest/runner@2.1.1': dependencies: - '@vitest/utils': 2.0.5 + '@vitest/utils': 2.1.1 pathe: 1.1.2 - '@vitest/snapshot@2.0.5': + '@vitest/snapshot@2.1.1': dependencies: - '@vitest/pretty-format': 2.0.5 + '@vitest/pretty-format': 2.1.1 magic-string: 0.30.11 pathe: 1.1.2 - '@vitest/spy@2.0.5': + '@vitest/spy@2.1.1': dependencies: - tinyspy: 3.0.0 + tinyspy: 3.0.2 - '@vitest/utils@2.0.5': + '@vitest/utils@2.1.1': dependencies: - '@vitest/pretty-format': 2.0.5 - estree-walker: 3.0.3 + '@vitest/pretty-format': 2.1.1 loupe: 3.1.1 tinyrainbow: 1.2.0 @@ -5464,13 +5494,13 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color agent-base@7.1.1: dependencies: - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -5503,7 +5533,7 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.0.1: {} + ansi-regex@6.1.0: {} ansi-styles@3.2.1: dependencies: @@ -5636,14 +5666,14 @@ snapshots: at-least-node@1.0.0: {} - autoprefixer@10.4.20(postcss@8.4.41): + autoprefixer@10.4.20(postcss@8.4.47): dependencies: browserslist: 4.23.3 caniuse-lite: 1.0.30001654 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.1 - postcss: 8.4.41 + postcss: 8.4.47 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: @@ -5656,9 +5686,9 @@ snapshots: axe-core@4.10.0: {} - axios@1.7.5(debug@4.3.6): + axios@1.7.7(debug@4.3.7): dependencies: - follow-redirects: 1.15.6(debug@4.3.6) + follow-redirects: 1.15.6(debug@4.3.7) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -5797,6 +5827,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.0: + dependencies: + readdirp: 4.0.1 + chownr@2.0.0: {} ci-info@3.9.0: {} @@ -5871,14 +5905,12 @@ snapshots: concat-map@0.0.1: {} - concurrently@8.2.2: + concurrently@9.0.1: dependencies: chalk: 4.1.2 - date-fns: 2.30.0 lodash: 4.17.21 rxjs: 7.8.1 shell-quote: 1.8.1 - spawn-command: 0.0.2 supports-color: 8.1.1 tree-kill: 1.2.2 yargs: 17.7.2 @@ -5917,7 +5949,7 @@ snapshots: csstype@3.1.3: {} - cypress@13.14.1: + cypress@13.14.2: dependencies: '@cypress/request': 3.0.1 '@cypress/xvfb': 1.2.4(supports-color@8.1.1) @@ -5935,7 +5967,7 @@ snapshots: commander: 6.2.1 common-tags: 1.8.2 dayjs: 1.11.13 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) enquirer: 2.4.1 eventemitter2: 6.4.7 execa: 4.1.0 @@ -5991,25 +6023,28 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.1 - date-fns@2.30.0: - dependencies: - '@babel/runtime': 7.25.6 - dayjs@1.11.13: {} debug@3.2.7(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: supports-color: 8.1.1 - debug@4.3.6(supports-color@5.5.0): + debug@4.3.6: dependencies: ms: 2.1.2 + + debug@4.3.7(supports-color@5.5.0): + dependencies: + ms: 2.1.3 + optionalDependencies: supports-color: 5.5.0 - debug@4.3.6(supports-color@8.1.1): + debug@4.3.7(supports-color@8.1.1): dependencies: - ms: 2.1.2 + ms: 2.1.3 + optionalDependencies: supports-color: 8.1.1 decamelize-keys@1.1.1: @@ -6323,32 +6358,33 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-next@14.2.7(eslint@8.57.0)(typescript@5.5.4): + eslint-config-next@14.2.7(eslint@8.57.1)(typescript@5.6.2): dependencies: '@next/eslint-plugin-next': 14.2.7 '@rushstack/eslint-patch': 1.10.4 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) - eslint: 8.57.0 + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.2) + eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) - eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) - eslint-plugin-react: 7.35.0(eslint@8.57.0) - eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) - typescript: 5.5.4 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.1) + eslint-plugin-react: 7.35.0(eslint@8.57.1) + eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) + optionalDependencies: + typescript: 5.6.2 transitivePeerDependencies: - eslint-import-resolver-webpack - eslint-plugin-import-x - supports-color - eslint-config-prettier@9.1.0(eslint@8.57.0): + eslint-config-prettier@9.1.0(eslint@8.57.1): dependencies: - eslint: 8.57.0 + eslint: 8.57.1 - eslint-config-turbo@2.1.0(eslint@8.57.0): + eslint-config-turbo@2.1.0(eslint@8.57.1): dependencies: - eslint: 8.57.0 - eslint-plugin-turbo: 2.1.0(eslint@8.57.0) + eslint: 8.57.1 + eslint-plugin-turbo: 2.1.0(eslint@8.57.1) eslint-import-resolver-node@0.3.9: dependencies: @@ -6358,46 +6394,47 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 - eslint: 8.57.0 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint: 8.57.1 + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 is-glob: 4.0.3 + optionalDependencies: + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): + eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1): dependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) debug: 3.2.7(supports-color@8.1.1) - eslint: 8.57.0 + optionalDependencies: + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.2) + eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 - eslint: 8.57.0 + eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -6407,12 +6444,14 @@ snapshots: object.values: 1.2.0 semver: 6.3.1 tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.0): + eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.1): dependencies: aria-query: 5.1.3 array-includes: 3.1.8 @@ -6423,7 +6462,7 @@ snapshots: damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 es-iterator-helpers: 1.0.19 - eslint: 8.57.0 + eslint: 8.57.1 hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -6432,11 +6471,11 @@ snapshots: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.0 - eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): + eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): dependencies: - eslint: 8.57.0 + eslint: 8.57.1 - eslint-plugin-react@7.35.0(eslint@8.57.0): + eslint-plugin-react@7.35.0(eslint@8.57.1): dependencies: array-includes: 3.1.8 array.prototype.findlast: 1.2.5 @@ -6444,7 +6483,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.0.19 - eslint: 8.57.0 + eslint: 8.57.1 estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -6458,10 +6497,10 @@ snapshots: string.prototype.matchall: 4.0.11 string.prototype.repeat: 1.0.0 - eslint-plugin-turbo@2.1.0(eslint@8.57.0): + eslint-plugin-turbo@2.1.0(eslint@8.57.1): dependencies: dotenv: 16.0.3 - eslint: 8.57.0 + eslint: 8.57.1 eslint-scope@7.2.2: dependencies: @@ -6470,20 +6509,20 @@ snapshots: eslint-visitor-keys@3.4.3: {} - eslint@8.57.0: + eslint@8.57.1: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@eslint-community/regexpp': 4.11.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@eslint-community/regexpp': 4.11.1 '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.0 - '@humanwhocodes/config-array': 0.11.14 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -6603,7 +6642,7 @@ snapshots: extract-zip@2.0.1(supports-color@8.1.1): dependencies: - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -6635,6 +6674,10 @@ snapshots: dependencies: pend: 1.2.0 + fdir@6.3.0(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -6657,11 +6700,6 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - find-yarn-workspace-root2@1.2.16: - dependencies: - micromatch: 4.0.8 - pkg-dir: 4.2.0 - flat-cache@3.2.0: dependencies: flatted: 3.3.1 @@ -6670,9 +6708,9 @@ snapshots: flatted@3.3.1: {} - follow-redirects@1.15.6(debug@4.3.6): - dependencies: - debug: 4.3.6(supports-color@5.5.0) + follow-redirects@1.15.6(debug@4.3.7): + optionalDependencies: + debug: 4.3.7(supports-color@5.5.0) for-each@0.3.3: dependencies: @@ -6914,7 +6952,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -6927,14 +6965,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.1 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -7284,11 +7322,11 @@ snapshots: lines-and-columns@1.2.4: {} - lint-staged@15.2.8: + lint-staged@15.2.10: dependencies: chalk: 5.3.0 commander: 12.1.0 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) execa: 8.0.1 lilconfig: 3.1.2 listr2: 8.2.4 @@ -7303,13 +7341,14 @@ snapshots: dependencies: cli-truncate: 2.1.0 colorette: 2.0.20 - enquirer: 2.4.1 log-update: 4.0.0 p-map: 4.0.0 rfdc: 1.4.1 rxjs: 7.8.1 through: 2.3.8 wrap-ansi: 7.0.0 + optionalDependencies: + enquirer: 2.4.1 listr2@8.2.4: dependencies: @@ -7322,13 +7361,6 @@ snapshots: load-tsconfig@0.2.5: {} - load-yaml-file@0.2.0: - dependencies: - graceful-fs: 4.2.11 - js-yaml: 3.14.1 - pify: 4.0.1 - strip-bom: 3.0.0 - locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -7508,13 +7540,13 @@ snapshots: natural-compare@1.4.0: {} - next-themes@0.2.1(next@14.2.5)(react-dom@18.3.1)(react@18.3.1): + next-themes@0.2.1(next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8) + next: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)(sass@1.77.8): + next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1): dependencies: '@next/env': 14.2.5 '@swc/helpers': 0.5.5 @@ -7524,7 +7556,6 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - sass: 1.77.8 styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 14.2.5 @@ -7536,6 +7567,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 14.2.5 '@next/swc-win32-ia32-msvc': 14.2.5 '@next/swc-win32-x64-msvc': 14.2.5 + sass: 1.79.1 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -7556,10 +7588,10 @@ snapshots: node-releases@2.0.18: {} - nodemon@3.1.4: + nodemon@3.1.5: dependencies: chokidar: 3.6.0 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) ignore-by-default: 1.0.1 minimatch: 3.1.2 pstree.remy: 1.1.8 @@ -7711,6 +7743,8 @@ snapshots: package-json-from-dist@1.0.0: {} + package-manager-detector@0.2.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -7757,8 +7791,12 @@ snapshots: picocolors@1.0.1: {} + picocolors@1.1.0: {} + picomatch@2.3.1: {} + picomatch@4.0.2: {} + pidtree@0.6.0: {} pify@2.3.0: {} @@ -7767,37 +7805,38 @@ snapshots: pirates@4.0.6: {} - pkg-dir@4.2.0: - dependencies: - find-up: 4.1.0 - possible-typed-array-names@1.0.0: {} - postcss-import@15.1.0(postcss@8.4.41): + postcss-import@15.1.0(postcss@8.4.47): dependencies: - postcss: 8.4.41 + postcss: 8.4.47 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-js@4.0.1(postcss@8.4.41): + postcss-js@4.0.1(postcss@8.4.47): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.41 + postcss: 8.4.47 - postcss-load-config@4.0.2(postcss@8.4.41): + postcss-load-config@4.0.2(postcss@8.4.47): dependencies: lilconfig: 3.1.2 - postcss: 8.4.41 + yaml: 2.5.0 + optionalDependencies: + postcss: 8.4.47 + + postcss-load-config@6.0.1(jiti@1.21.6)(postcss@8.4.47)(yaml@2.5.0): + dependencies: + lilconfig: 3.1.2 + optionalDependencies: + jiti: 1.21.6 + postcss: 8.4.47 yaml: 2.5.0 - postcss-load-config@6.0.1: + postcss-nested@6.2.0(postcss@8.4.47): dependencies: - lilconfig: 3.1.2 - - postcss-nested@6.2.0(postcss@8.4.41): - dependencies: - postcss: 8.4.41 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 postcss-selector-parser@6.1.2: @@ -7810,33 +7849,27 @@ snapshots: postcss@8.4.31: dependencies: nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 + picocolors: 1.1.0 + source-map-js: 1.2.1 - postcss@8.4.41: + postcss@8.4.47: dependencies: nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 - - preferred-pm@3.1.4: - dependencies: - find-up: 5.0.0 - find-yarn-workspace-root2: 1.2.16 - path-exists: 4.0.0 - which-pm: 2.2.0 + picocolors: 1.1.0 + source-map-js: 1.2.1 prelude-ls@1.2.1: {} - prettier-plugin-organize-imports@4.0.0(prettier@3.3.3)(typescript@5.5.4): + prettier-plugin-organize-imports@4.0.0(prettier@3.3.3)(typescript@5.6.2): dependencies: prettier: 3.3.3 - typescript: 5.5.4 + typescript: 5.6.2 - prettier-plugin-tailwindcss@0.6.5(prettier-plugin-organize-imports@4.0.0)(prettier@3.3.3): + prettier-plugin-tailwindcss@0.6.6(prettier-plugin-organize-imports@4.0.0(prettier@3.3.3)(typescript@5.6.2))(prettier@3.3.3): dependencies: prettier: 3.3.3 - prettier-plugin-organize-imports: 4.0.0(prettier@3.3.3)(typescript@5.5.4) + optionalDependencies: + prettier-plugin-organize-imports: 4.0.0(prettier@3.3.3)(typescript@5.6.2) prettier@2.8.8: {} @@ -7870,7 +7903,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.1.0 + '@types/node': 22.5.5 long: 5.2.3 proxy-from-env@1.0.0: {} @@ -7962,6 +7995,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.0.1: {} + redent@3.0.0: dependencies: indent-string: 4.0.0 @@ -8035,26 +8070,26 @@ snapshots: dependencies: glob: 7.2.3 - rollup@4.21.1: + rollup@4.21.3: dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.21.1 - '@rollup/rollup-android-arm64': 4.21.1 - '@rollup/rollup-darwin-arm64': 4.21.1 - '@rollup/rollup-darwin-x64': 4.21.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.21.1 - '@rollup/rollup-linux-arm-musleabihf': 4.21.1 - '@rollup/rollup-linux-arm64-gnu': 4.21.1 - '@rollup/rollup-linux-arm64-musl': 4.21.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.21.1 - '@rollup/rollup-linux-riscv64-gnu': 4.21.1 - '@rollup/rollup-linux-s390x-gnu': 4.21.1 - '@rollup/rollup-linux-x64-gnu': 4.21.1 - '@rollup/rollup-linux-x64-musl': 4.21.1 - '@rollup/rollup-win32-arm64-msvc': 4.21.1 - '@rollup/rollup-win32-ia32-msvc': 4.21.1 - '@rollup/rollup-win32-x64-msvc': 4.21.1 + '@rollup/rollup-android-arm-eabi': 4.21.3 + '@rollup/rollup-android-arm64': 4.21.3 + '@rollup/rollup-darwin-arm64': 4.21.3 + '@rollup/rollup-darwin-x64': 4.21.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.21.3 + '@rollup/rollup-linux-arm-musleabihf': 4.21.3 + '@rollup/rollup-linux-arm64-gnu': 4.21.3 + '@rollup/rollup-linux-arm64-musl': 4.21.3 + '@rollup/rollup-linux-powerpc64le-gnu': 4.21.3 + '@rollup/rollup-linux-riscv64-gnu': 4.21.3 + '@rollup/rollup-linux-s390x-gnu': 4.21.3 + '@rollup/rollup-linux-x64-gnu': 4.21.3 + '@rollup/rollup-linux-x64-musl': 4.21.3 + '@rollup/rollup-win32-arm64-msvc': 4.21.3 + '@rollup/rollup-win32-ia32-msvc': 4.21.3 + '@rollup/rollup-win32-x64-msvc': 4.21.3 fsevents: 2.3.3 rrweb-cssom@0.6.0: {} @@ -8086,11 +8121,11 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.77.8: + sass@1.79.1: dependencies: - chokidar: 3.6.0 + chokidar: 4.0.0 immutable: 4.3.7 - source-map-js: 1.2.0 + source-map-js: 1.2.1 saxes@6.0.0: dependencies: @@ -8181,14 +8216,12 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 5.0.0 - source-map-js@1.2.0: {} + source-map-js@1.2.1: {} source-map@0.8.0-beta.0: dependencies: whatwg-url: 7.1.0 - spawn-command@0.0.2: {} - spawndamnit@2.0.0: dependencies: cross-spawn: 5.1.0 @@ -8228,16 +8261,16 @@ snapshots: stackback@0.0.2: {} - start-server-and-test@2.0.5: + start-server-and-test@2.0.8: dependencies: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 - wait-on: 7.2.0(debug@4.3.6) + wait-on: 8.0.1(debug@4.3.7) transitivePeerDependencies: - supports-color @@ -8327,7 +8360,7 @@ snapshots: strip-ansi@7.1.0: dependencies: - ansi-regex: 6.0.1 + ansi-regex: 6.1.0 strip-bom@3.0.0: {} @@ -8347,9 +8380,10 @@ snapshots: styled-jsx@5.1.1(@babel/core@7.25.2)(react@18.3.1): dependencies: - '@babel/core': 7.25.2 client-only: 0.0.1 react: 18.3.1 + optionalDependencies: + '@babel/core': 7.25.2 sucrase@3.35.0: dependencies: @@ -8383,7 +8417,7 @@ snapshots: symbol-tree@3.2.4: {} - tailwindcss@3.4.9: + tailwindcss@3.4.12: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -8398,12 +8432,12 @@ snapshots: micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 - picocolors: 1.0.1 - postcss: 8.4.41 - postcss-import: 15.1.0(postcss@8.4.41) - postcss-js: 4.0.1(postcss@8.4.41) - postcss-load-config: 4.0.2(postcss@8.4.41) - postcss-nested: 6.2.0(postcss@8.4.41) + picocolors: 1.1.0 + postcss: 8.4.47 + postcss-import: 15.1.0(postcss@8.4.47) + postcss-js: 4.0.1(postcss@8.4.47) + postcss-load-config: 4.0.2(postcss@8.4.47) + postcss-nested: 6.2.0(postcss@8.4.47) postcss-selector-parser: 6.1.2 resolve: 1.22.8 sucrase: 3.35.0 @@ -8441,11 +8475,18 @@ snapshots: tinycolor2@1.4.2: {} + tinyexec@0.3.0: {} + + tinyglobby@0.2.6: + dependencies: + fdir: 6.3.0(picomatch@4.0.2) + picomatch: 4.0.2 + tinypool@1.0.1: {} tinyrainbow@1.2.0: {} - tinyspy@3.0.0: {} + tinyspy@3.0.2: {} tmp@0.0.33: dependencies: @@ -8484,9 +8525,9 @@ snapshots: trim-newlines@4.1.1: {} - ts-api-utils@1.3.0(typescript@5.5.4): + ts-api-utils@1.3.0(typescript@5.6.2): dependencies: - typescript: 5.5.4 + typescript: 5.6.2 ts-error@1.0.6: {} @@ -8496,21 +8537,20 @@ snapshots: dependencies: dprint-node: 1.0.8 - ts-proto-descriptors@1.16.0: + ts-proto-descriptors@2.0.0: dependencies: - long: 5.2.3 - protobufjs: 7.4.0 + '@bufbuild/protobuf': 2.0.0 - ts-proto@1.181.2: + ts-proto@2.2.0: dependencies: + '@bufbuild/protobuf': 2.0.0 case-anything: 2.1.13 - protobufjs: 7.4.0 ts-poet: 6.9.0 - ts-proto-descriptors: 1.16.0 + ts-proto-descriptors: 2.0.0 - tsconfck@3.1.1(typescript@5.5.4): - dependencies: - typescript: 5.5.4 + tsconfck@3.1.1(typescript@5.6.2): + optionalDependencies: + typescript: 5.6.2 tsconfig-paths@3.15.0: dependencies: @@ -8521,25 +8561,27 @@ snapshots: tslib@2.7.0: {} - tsup@8.2.4(typescript@5.5.4): + tsup@8.3.0(jiti@1.21.6)(postcss@8.4.47)(typescript@5.6.2)(yaml@2.5.0): dependencies: bundle-require: 5.0.0(esbuild@0.23.1) cac: 6.7.14 chokidar: 3.6.0 consola: 3.2.3 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) esbuild: 0.23.1 execa: 5.1.1 - globby: 11.1.0 joycon: 3.1.1 - picocolors: 1.0.1 - postcss-load-config: 6.0.1 + picocolors: 1.1.0 + postcss-load-config: 6.0.1(jiti@1.21.6)(postcss@8.4.47)(yaml@2.5.0) resolve-from: 5.0.0 - rollup: 4.21.1 + rollup: 4.21.3 source-map: 0.8.0-beta.0 sucrase: 3.35.0 + tinyglobby: 0.2.6 tree-kill: 1.2.2 - typescript: 5.5.4 + optionalDependencies: + postcss: 8.4.47 + typescript: 5.6.2 transitivePeerDependencies: - jiti - supports-color @@ -8550,32 +8592,32 @@ snapshots: dependencies: safe-buffer: 5.2.1 - turbo-darwin-64@2.0.12: + turbo-darwin-64@2.1.2: optional: true - turbo-darwin-arm64@2.0.12: + turbo-darwin-arm64@2.1.2: optional: true - turbo-linux-64@2.0.12: + turbo-linux-64@2.1.2: optional: true - turbo-linux-arm64@2.0.12: + turbo-linux-arm64@2.1.2: optional: true - turbo-windows-64@2.0.12: + turbo-windows-64@2.1.2: optional: true - turbo-windows-arm64@2.0.12: + turbo-windows-arm64@2.1.2: optional: true - turbo@2.0.12: + turbo@2.1.2: optionalDependencies: - turbo-darwin-64: 2.0.12 - turbo-darwin-arm64: 2.0.12 - turbo-linux-64: 2.0.12 - turbo-linux-arm64: 2.0.12 - turbo-windows-64: 2.0.12 - turbo-windows-arm64: 2.0.12 + turbo-darwin-64: 2.1.2 + turbo-darwin-arm64: 2.1.2 + turbo-linux-64: 2.1.2 + turbo-linux-arm64: 2.1.2 + turbo-windows-64: 2.1.2 + turbo-windows-arm64: 2.1.2 tweetnacl@0.14.5: {} @@ -8621,7 +8663,7 @@ snapshots: is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 - typescript@5.5.4: {} + typescript@5.6.2: {} unbox-primitive@1.0.2: dependencies: @@ -8632,7 +8674,7 @@ snapshots: undefsafe@2.0.5: {} - undici-types@6.13.0: {} + undici-types@6.19.8: {} undici@5.28.4: dependencies: @@ -8650,7 +8692,7 @@ snapshots: dependencies: browserslist: 4.23.3 escalade: 3.2.0 - picocolors: 1.0.1 + picocolors: 1.1.0 uri-js@4.4.1: dependencies: @@ -8680,13 +8722,12 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - vite-node@2.0.5: + vite-node@2.1.1(@types/node@22.5.5)(sass@1.79.1): dependencies: cac: 6.7.14 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) pathe: 1.1.2 - tinyrainbow: 1.2.0 - vite: 5.4.2 + vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) transitivePeerDependencies: - '@types/node' - less @@ -8698,48 +8739,55 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@5.0.1(typescript@5.5.4)(vite@5.4.2): + vite-tsconfig-paths@5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1)): dependencies: - debug: 4.3.6(supports-color@5.5.0) + debug: 4.3.6 globrex: 0.1.2 - tsconfck: 3.1.1(typescript@5.5.4) - vite: 5.4.2 + tsconfck: 3.1.1(typescript@5.6.2) + optionalDependencies: + vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) transitivePeerDependencies: - supports-color - typescript - vite@5.4.2: + vite@5.4.6(@types/node@22.5.5)(sass@1.79.1): dependencies: esbuild: 0.21.5 - postcss: 8.4.41 - rollup: 4.21.1 + postcss: 8.4.47 + rollup: 4.21.3 optionalDependencies: + '@types/node': 22.5.5 fsevents: 2.3.3 + sass: 1.79.1 - vitest@2.0.5: + vitest@2.1.1(@types/node@22.5.5)(jsdom@25.0.0)(sass@1.79.1): dependencies: - '@ampproject/remapping': 2.3.0 - '@vitest/expect': 2.0.5 - '@vitest/pretty-format': 2.0.5 - '@vitest/runner': 2.0.5 - '@vitest/snapshot': 2.0.5 - '@vitest/spy': 2.0.5 - '@vitest/utils': 2.0.5 + '@vitest/expect': 2.1.1 + '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1)) + '@vitest/pretty-format': 2.1.1 + '@vitest/runner': 2.1.1 + '@vitest/snapshot': 2.1.1 + '@vitest/spy': 2.1.1 + '@vitest/utils': 2.1.1 chai: 5.1.1 - debug: 4.3.6(supports-color@5.5.0) - execa: 8.0.1 + debug: 4.3.7(supports-color@5.5.0) magic-string: 0.30.11 pathe: 1.1.2 std-env: 3.7.0 tinybench: 2.9.0 + tinyexec: 0.3.0 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.2 - vite-node: 2.0.5 + vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) + vite-node: 2.1.1(@types/node@22.5.5)(sass@1.79.1) why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.5.5 + jsdom: 25.0.0 transitivePeerDependencies: - less - lightningcss + - msw - sass - sass-embedded - stylus @@ -8751,9 +8799,9 @@ snapshots: dependencies: xml-name-validator: 5.0.0 - wait-on@7.2.0(debug@4.3.6): + wait-on@8.0.1(debug@4.3.7): dependencies: - axios: 1.7.5(debug@4.3.6) + axios: 1.7.7(debug@4.3.7) joi: 17.13.3 lodash: 4.17.21 minimist: 1.2.8 @@ -8819,11 +8867,6 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.3 - which-pm@2.2.0: - dependencies: - load-yaml-file: 0.2.0 - path-exists: 4.0.0 - which-typed-array@1.1.15: dependencies: available-typed-arrays: 1.0.7 From 985fede6a5902d066eb3a1a8d5335666b67bc9fb Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 18 Sep 2024 14:13:04 +0200 Subject: [PATCH 199/640] cleanup session creation --- apps/login/src/app/(login)/password/page.tsx | 33 +++++--- apps/login/src/lib/cookies.ts | 1 - apps/login/src/lib/server/loginname.ts | 48 ++++++----- apps/login/src/lib/server/password.ts | 87 +++++++++++++++++++- apps/login/src/lib/server/register.ts | 14 +++- apps/login/src/lib/server/session.ts | 39 ++++----- apps/login/src/ui/IdpSignin.tsx | 1 - apps/login/src/ui/PasswordForm.tsx | 5 +- apps/login/src/ui/SessionItem.tsx | 6 +- apps/login/src/utils/session.ts | 81 ++---------------- 10 files changed, 173 insertions(+), 142 deletions(-) diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 9b35b5c70c..81e6ba240a 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -13,10 +13,17 @@ export default async function Page({ }) { const { loginName, organization, authRequestId, alt } = searchParams; - const sessionFactors = await loadMostRecentSession({ - loginName, - organization, - }); + // also allow no session to be found (ignoreUnkownUsername) + let sessionFactors; + try { + sessionFactors = await loadMostRecentSession({ + loginName, + organization, + }); + } catch (error) { + // ignore error to continue to show the password form + console.warn(error); + } const branding = await getBrandingSettings(organization); const loginSettings = await getLoginSettings(organization); @@ -27,14 +34,16 @@ export default async function Page({

    {sessionFactors?.factors?.user?.displayName ?? "Password"}

    Enter your password.

    - {(!sessionFactors || !loginName) && ( -
    - - Could not get the context of the user. Make sure to enter the - username first or provide a loginName as searchParam. - -
    - )} + {/* show error only if usernames should be shown to be unknown */} + {(!sessionFactors || !loginName) && + !loginSettings?.ignoreUnknownUsernames && ( +
    + + Could not get the context of the user. Make sure to enter the + username first or provide a loginName as searchParam. + +
    + )} {sessionFactors && ( ({ if (latest) { return latest; } else { - console.error("sessions", sessions, loginName, organization); return Promise.reject("Could not get the context or retrieve a session"); } } else { diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 18145c614d..e95b43b2ee 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -1,9 +1,11 @@ "use server"; +import { create } from "@zitadel/client"; +import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; import { redirect } from "next/navigation"; -import { createSessionForUserIdAndUpdateCookie } from "../../utils/session"; +import { createSessionAndUpdateCookie } from "../../utils/session"; import { idpTypeToSlug } from "../idp"; import { getActiveIdentityProviders, @@ -71,9 +73,13 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (users.details?.totalResult == BigInt(1) && users.result[0].userId) { const userId = users.result[0].userId; - const session = await createSessionForUserIdAndUpdateCookie( - userId, - undefined, + + const checks = create(ChecksSchema, { + user: { search: { case: "userId", value: userId } }, + }); + + const session = await createSessionAndUpdateCookie( + checks, undefined, command.authRequestId, ); @@ -202,36 +208,38 @@ export async function sendLoginname(command: SendLoginnameCommand) { } } - const params = new URLSearchParams(); - + // TODO: check if ignoreUnknownUsernames or register has a higher priority if (orgToRegisterOn) { - params.set("organization", orgToRegisterOn); - } - if (command.authRequestId) { - params.set("authRequestId", command.authRequestId); - } - if (command.loginName) { - params.set("loginName", command.loginName); - } + const params = new URLSearchParams({ organization: orgToRegisterOn }); - const registerUrl = "/register?" + params; + if (command.authRequestId) { + params.set("authRequestId", command.authRequestId); + } + if (command.loginName) { + params.set("loginName", command.loginName); + } - return redirect(registerUrl); + return redirect("/register?" + params); + } } if (loginSettings?.ignoreUnknownUsernames) { - const paramsPasswordDefault: any = { loginName: command.loginName }; + const paramsPasswordDefault = new URLSearchParams({ + loginName: command.loginName, + }); if (command.authRequestId) { - paramsPasswordDefault.authRequestId = command.authRequestId; + paramsPasswordDefault.append("authRequestId", command.authRequestId); } if (command.organization) { - paramsPasswordDefault.organization = command.organization; + paramsPasswordDefault.append("organization", command.organization); } - return redirect("/password?" + new URLSearchParams(paramsPasswordDefault)); + return redirect("/password?" + paramsPasswordDefault); } + // fallbackToPassword + return { error: "Could not find user" }; } diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index fdd3b448e5..e3d6657603 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -1,6 +1,20 @@ "use server"; -import { listUsers, passwordReset } from "@/lib/zitadel"; +import { + listAuthenticationMethodTypes, + listUsers, + passwordReset, +} from "@/lib/zitadel"; +import { + createSessionAndUpdateCookie, + setSessionAndUpdateCookie, +} from "@/utils/session"; +import { create } from "@zitadel/client"; +import { + Checks, + ChecksSchema, +} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { getSessionCookieByLoginName } from "../cookies"; type ResetPasswordCommand = { loginName: string; @@ -24,3 +38,74 @@ export async function resetPassword(command: ResetPasswordCommand) { return passwordReset(userId); } + +export type UpdateSessionCommand = { + loginName: string; + organization?: string; + checks: Checks; + authRequestId?: string; +}; + +export async function sendPassword(command: UpdateSessionCommand) { + let sessionCookie = await getSessionCookieByLoginName({ + loginName: command.loginName, + organization: command.organization, + }); + + let session; + if (!sessionCookie) { + const users = await listUsers({ + loginName: command.loginName, + organizationId: command.organization, + }); + + if (users.details?.totalResult == BigInt(1) && users.result[0].userId) { + const checks = create(ChecksSchema, { + user: { search: { case: "userId", value: users.result[0].userId } }, + password: { password: command.checks.password?.password }, + }); + + session = await createSessionAndUpdateCookie( + checks, + undefined, + command.authRequestId, + ); + + if (!session?.factors?.user?.id || !sessionCookie) { + return { error: "Could not create session for user" }; + } + } + + // this is a fake error message to hide that the user does not even exist + return { error: "The password is wrong!" }; + } else { + const updatedSession = await setSessionAndUpdateCookie( + sessionCookie, + command.checks, + undefined, + command.authRequestId, + ); + + // if password, check if user has MFA methods + let authMethods; + if ( + command.checks && + command.checks.password && + updatedSession.factors?.user?.id + ) { + const response = await listAuthenticationMethodTypes( + updatedSession.factors.user.id, + ); + if (response.authMethodTypes && response.authMethodTypes.length) { + authMethods = response.authMethodTypes; + } + } + + return { + sessionId: updatedSession.id, + factors: updatedSession.factors, + challenges: updatedSession.challenges, + authMethods, + }; + } +} diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 0c906c60c6..3bb80a115b 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -1,8 +1,10 @@ "use server"; import { addHumanUser } from "@/lib/zitadel"; -import { createSessionForUserIdAndUpdateCookie } from "@/utils/session"; +import { createSessionAndUpdateCookie } from "@/utils/session"; +import { create } from "@zitadel/client"; import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; type RegisterUserCommand = { email: string; @@ -32,9 +34,13 @@ export async function registerUser(command: RegisterUserCommand) { return { error: "Could not create user" }; } - return createSessionForUserIdAndUpdateCookie( - human.userId, - command.password, + const checks = create(ChecksSchema, { + user: { search: { case: "userId", value: human.userId } }, + password: { password: command.password }, + }); + + return createSessionAndUpdateCookie( + checks, undefined, command.authRequestId, ).then((session) => { diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 3220ab408d..9967486392 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -6,8 +6,12 @@ import { createSessionForIdpAndUpdateCookie, setSessionAndUpdateCookie, } from "@/utils/session"; +import { create } from "@zitadel/client"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; -import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { + Checks, + ChecksSchema, +} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { headers } from "next/headers"; import { getMostRecentSessionCookie, @@ -24,35 +28,26 @@ type CreateNewSessionCommand = { }; loginName?: string; password?: string; - organization?: string; authRequestId?: string; }; export async function createNewSession(options: CreateNewSessionCommand) { - const { - userId, - idpIntent, - loginName, - password, - organization, - authRequestId, - } = options; + const { userId, idpIntent, loginName, password, authRequestId } = options; if (userId && idpIntent) { - return createSessionForIdpAndUpdateCookie( - userId, - idpIntent, - organization, - authRequestId, - ); + return createSessionForIdpAndUpdateCookie(userId, idpIntent, authRequestId); } else if (loginName) { - return createSessionAndUpdateCookie( - loginName, - password, - undefined, - organization, - authRequestId, + const checks = create( + ChecksSchema, + password + ? { + user: { search: { case: "loginName", value: loginName } }, + password: { password }, + } + : { user: { search: { case: "loginName", value: loginName } } }, ); + + return createSessionAndUpdateCookie(checks, undefined, authRequestId); } else { throw new Error("No userId or loginName provided"); } diff --git a/apps/login/src/ui/IdpSignin.tsx b/apps/login/src/ui/IdpSignin.tsx index 018ddd2aa2..e186d1c681 100644 --- a/apps/login/src/ui/IdpSignin.tsx +++ b/apps/login/src/ui/IdpSignin.tsx @@ -34,7 +34,6 @@ export default function IdpSignin({ idpIntentToken, }, authRequestId, - // organization: props.organization, }) .then((session) => { if (authRequestId && session && session.id) { diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index 1c1870e7ba..9201f7e050 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -1,7 +1,6 @@ "use client"; -import { resetPassword } from "@/lib/server/password"; -import { updateSession } from "@/lib/server/session"; +import { resetPassword, sendPassword } from "@/lib/server/password"; import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; @@ -51,7 +50,7 @@ export default function PasswordForm({ setError(""); setLoading(true); - const response = await updateSession({ + const response = await sendPassword({ loginName, organization, checks: create(ChecksSchema, { diff --git a/apps/login/src/ui/SessionItem.tsx b/apps/login/src/ui/SessionItem.tsx index 94aa1f2ef4..77f3737924 100644 --- a/apps/login/src/ui/SessionItem.tsx +++ b/apps/login/src/ui/SessionItem.tsx @@ -90,13 +90,13 @@ export default function SessionItem({ />
    -
    +
    {session.factors?.user?.displayName} - + {session.factors?.user?.loginName} {validUser && ( - + {validDate && moment(timestampDate(validDate)).fromNow()} )} diff --git a/apps/login/src/utils/session.ts b/apps/login/src/utils/session.ts index e0b4e2e91a..65dcc1494a 100644 --- a/apps/login/src/utils/session.ts +++ b/apps/login/src/utils/session.ts @@ -7,16 +7,13 @@ import { getSession, setSession, } from "@/lib/zitadel"; -import { create, timestampDate, toDate } from "@zitadel/client"; +import { timestampDate } from "@zitadel/client"; import { Challenges, RequestChallenges, } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { - Checks, - ChecksSchema, -} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; type CustomCookieData = { id: string; @@ -30,76 +27,10 @@ type CustomCookieData = { }; export async function createSessionAndUpdateCookie( - loginName: string, - password: string | undefined, - challenges: RequestChallenges | undefined, - organization?: string, - authRequestId?: string, -) { - const checks = create( - ChecksSchema, - password - ? { - user: { search: { case: "loginName", value: loginName } }, - password: { password }, - } - : { user: { search: { case: "loginName", value: loginName } } }, - ); - - const createdSession = await createSessionFromChecks(checks, challenges); - - if (createdSession) { - return getSession({ - sessionId: createdSession.sessionId, - sessionToken: createdSession.sessionToken, - }).then((response) => { - if (response?.session && response.session?.factors?.user?.loginName) { - const sessionCookie: CustomCookieData = { - id: createdSession.sessionId, - token: createdSession.sessionToken, - creationDate: `${toDate(response.session.creationDate)?.getTime() ?? ""}`, - expirationDate: `${toDate(response.session.expirationDate)?.getTime() ?? ""}`, - changeDate: `${toDate(response.session.changeDate)?.getTime() ?? ""}`, - loginName: response.session.factors.user.loginName ?? "", - organization: response.session.factors.user.organizationId ?? "", - }; - - if (authRequestId) { - sessionCookie.authRequestId = authRequestId; - } - - if (organization) { - sessionCookie.organization = organization; - } - - return addSessionToCookie(sessionCookie).then(() => { - return response.session as Session; - }); - } else { - throw "could not get session or session does not have loginName"; - } - }); - } else { - throw "Could not create session"; - } -} - -export async function createSessionForUserIdAndUpdateCookie( - userId: string, - password: string | undefined, + checks: Checks, challenges: RequestChallenges | undefined, authRequestId: string | undefined, ): Promise { - const checks = create( - ChecksSchema, - password - ? { - user: { search: { case: "userId", value: userId } }, - password: { password }, - // totp: { code: totpCode }, - } - : { user: { search: { case: "userId", value: userId } } }, - ); const createdSession = await createSessionFromChecks(checks, challenges); if (createdSession) { @@ -150,7 +81,6 @@ export async function createSessionForIdpAndUpdateCookie( idpIntentId?: string | undefined; idpIntentToken?: string | undefined; }, - organization: string | undefined, authRequestId: string | undefined, ): Promise { const createdSession = await createSessionForUserIdAndIdpIntent( @@ -184,8 +114,9 @@ export async function createSessionForIdpAndUpdateCookie( sessionCookie.authRequestId = authRequestId; } - if (organization) { - sessionCookie.organization = organization; + if (response.session.factors.user.organizationId) { + sessionCookie.organization = + response.session.factors.user.organizationId; } return addSessionToCookie(sessionCookie).then(() => { From ebe5da088085287233e2509beef049380d3437d7 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 18 Sep 2024 15:57:26 +0200 Subject: [PATCH 200/640] password error --- apps/login/src/lib/server/password.ts | 54 +++++++++++++-------------- apps/login/src/ui/PasswordForm.tsx | 11 ++++-- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index e3d6657603..994eb7759f 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -50,6 +50,8 @@ export async function sendPassword(command: UpdateSessionCommand) { let sessionCookie = await getSessionCookieByLoginName({ loginName: command.loginName, organization: command.organization, + }).catch((error) => { + console.warn("Ignored error:", error); }); let session; @@ -70,42 +72,38 @@ export async function sendPassword(command: UpdateSessionCommand) { undefined, command.authRequestId, ); - - if (!session?.factors?.user?.id || !sessionCookie) { - return { error: "Could not create session for user" }; - } } // this is a fake error message to hide that the user does not even exist - return { error: "The password is wrong!" }; + return { error: "Could not verify password!" }; } else { - const updatedSession = await setSessionAndUpdateCookie( + session = await setSessionAndUpdateCookie( sessionCookie, command.checks, undefined, command.authRequestId, ); - - // if password, check if user has MFA methods - let authMethods; - if ( - command.checks && - command.checks.password && - updatedSession.factors?.user?.id - ) { - const response = await listAuthenticationMethodTypes( - updatedSession.factors.user.id, - ); - if (response.authMethodTypes && response.authMethodTypes.length) { - authMethods = response.authMethodTypes; - } - } - - return { - sessionId: updatedSession.id, - factors: updatedSession.factors, - challenges: updatedSession.challenges, - authMethods, - }; } + + if (!session?.factors?.user?.id || !sessionCookie) { + return { error: "Could not create session for user" }; + } + + // if password, check if user has MFA methods + let authMethods; + if (command.checks && command.checks.password && session.factors?.user?.id) { + const response = await listAuthenticationMethodTypes( + session.factors.user.id, + ); + if (response.authMethodTypes && response.authMethodTypes.length) { + authMethods = response.authMethodTypes; + } + } + + return { + sessionId: session.id, + factors: session.factors, + challenges: session.challenges, + authMethods, + }; } diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index 9201f7e050..003d40befd 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -62,6 +62,10 @@ export default function PasswordForm({ setLoading(false); }); + if (response && "error" in response && response.error) { + setError(response.error); + } + setLoading(false); return response; @@ -109,7 +113,6 @@ export default function PasswordForm({ !submitted.authMethods || !submitted.factors?.user?.loginName ) { - setError("Could not verify password"); return; } @@ -119,9 +122,9 @@ export default function PasswordForm({ m !== AuthenticationMethodType.PASSKEY, ); - if (availableSecondFactors.length == 1) { + if (availableSecondFactors?.length == 1) { const params = new URLSearchParams({ - loginName: submitted.factors.user.loginName, + loginName: submitted.factors?.user.loginName, }); if (authRequestId) { @@ -143,7 +146,7 @@ export default function PasswordForm({ } else if (factor === AuthenticationMethodType.U2F) { return router.push(`/u2f?` + params); } - } else if (availableSecondFactors.length >= 1) { + } else if (availableSecondFactors?.length >= 1) { const params = new URLSearchParams({ loginName: submitted.factors.user.loginName, }); From ae25950e7ff2421fdfd6df7d06a0f3653835df50 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 18 Sep 2024 16:28:17 +0200 Subject: [PATCH 201/640] turn of caching --- apps/login/src/lib/zitadel.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 9615595fcc..184bb9ffdc 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -26,7 +26,6 @@ import { SearchQuery, SearchQuerySchema, } from "@zitadel/proto/zitadel/user/v2/query_pb"; -import { unstable_cache } from "next/cache"; import { PROVIDER_MAPPING } from "./idp"; const SESSION_LIFETIME_S = 3600; @@ -48,15 +47,18 @@ export const orgService = createOrganizationServiceClient(transport); export const settingsService = createSettingsServiceClient(transport); export async function getBrandingSettings(organization?: string) { - return unstable_cache( - async () => { - return await settingsService - .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) - .then((resp) => resp.settings); - }, - ["branding"], - { revalidate: 3600, tags: ["branding"] }, - )(); + return await settingsService + .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) + .then((resp) => resp.settings); + // return unstable_cache( + // async () => { + // return await settingsService + // .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) + // .then((resp) => resp.settings); + // }, + // ["branding"], + // { revalidate: 3600, tags: ["branding"] }, + // )(); } export async function getLoginSettings(orgId?: string) { From 4e9d48e720cd9779813593e8eef738fde7e2764a Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 18 Sep 2024 16:41:30 +0200 Subject: [PATCH 202/640] ignore username for org discovery --- apps/login/src/lib/server/loginname.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index e95b43b2ee..cc1f91a5e1 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -190,6 +190,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { let orgToRegisterOn: string | undefined = command.organization; if ( + !loginSettings?.ignoreUnknownUsernames && !orgToRegisterOn && command.loginName && ORG_SUFFIX_REGEX.test(command.loginName) @@ -208,8 +209,8 @@ export async function sendLoginname(command: SendLoginnameCommand) { } } - // TODO: check if ignoreUnknownUsernames or register has a higher priority - if (orgToRegisterOn) { + // do not register user if ignoreUnknownUsernames is set + if (orgToRegisterOn && !loginSettings?.ignoreUnknownUsernames) { const params = new URLSearchParams({ organization: orgToRegisterOn }); if (command.authRequestId) { From 7ebe13b3f86f07b80842df5a2f36e7a7a1702cfb Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 19 Sep 2024 09:03:06 +0200 Subject: [PATCH 203/640] cache on organization key --- apps/login/src/lib/zitadel.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 184bb9ffdc..254de17224 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -26,6 +26,7 @@ import { SearchQuery, SearchQuerySchema, } from "@zitadel/proto/zitadel/user/v2/query_pb"; +import { unstable_cache } from "next/cache"; import { PROVIDER_MAPPING } from "./idp"; const SESSION_LIFETIME_S = 3600; @@ -47,18 +48,18 @@ export const orgService = createOrganizationServiceClient(transport); export const settingsService = createSettingsServiceClient(transport); export async function getBrandingSettings(organization?: string) { - return await settingsService - .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) - .then((resp) => resp.settings); - // return unstable_cache( - // async () => { - // return await settingsService - // .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) - // .then((resp) => resp.settings); - // }, - // ["branding"], - // { revalidate: 3600, tags: ["branding"] }, - // )(); + // return await settingsService + // .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) + // .then((resp) => resp.settings); + return unstable_cache( + async () => { + return await settingsService + .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) + .then((resp) => resp.settings); + }, + ["branding", organization ?? "default"], + { revalidate: 3600, tags: ["branding"] }, + )(); } export async function getLoginSettings(orgId?: string) { From ccb2ae6cd4e2c745a70120c1436c726efd6c22cd Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 19 Sep 2024 09:16:11 +0200 Subject: [PATCH 204/640] cache and catch error --- apps/login/src/lib/server/password.ts | 2 +- apps/login/src/lib/zitadel.ts | 47 ++++++++++++++++++--------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 994eb7759f..12de7a0964 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -32,7 +32,7 @@ export async function resetPassword(command: ResetPasswordCommand) { users.details.totalResult !== BigInt(1) || !users.result[0].userId ) { - return { error: "Could not find user" }; + return { error: "Could not send Password Reset Link" }; } const userId = users.result[0].userId; diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 254de17224..dcb227d20e 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -48,24 +48,27 @@ export const orgService = createOrganizationServiceClient(transport); export const settingsService = createSettingsServiceClient(transport); export async function getBrandingSettings(organization?: string) { - // return await settingsService - // .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) - // .then((resp) => resp.settings); return unstable_cache( async () => { return await settingsService .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) .then((resp) => resp.settings); }, - ["branding", organization ?? "default"], - { revalidate: 3600, tags: ["branding"] }, + ["brandingSettings", organization ?? "default"], + { revalidate: 3600, tags: ["brandingSettings"] }, )(); } export async function getLoginSettings(orgId?: string) { - return settingsService - .getLoginSettings({ ctx: makeReqCtx(orgId) }, {}) - .then((resp) => resp.settings); + return unstable_cache( + async () => { + return await settingsService + .getLoginSettings({ ctx: makeReqCtx(orgId) }, {}) + .then((resp) => resp.settings); + }, + ["loginSettings", orgId ?? "default"], + { revalidate: 3600, tags: ["loginSettings"] }, + )(); } export async function addOTPEmail(userId: string) { @@ -92,17 +95,29 @@ export async function getGeneralSettings() { } export async function getLegalAndSupportSettings(organization?: string) { - return settingsService - .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) - .then((resp) => { - return resp.settings; - }); + return unstable_cache( + async () => { + return await settingsService + .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) + .then((resp) => { + return resp.settings; + }); + }, + ["legalAndSupportSettings", organization ?? "default"], + { revalidate: 3600, tags: ["legalAndSupportSettings"] }, + )(); } export async function getPasswordComplexitySettings(organization?: string) { - return settingsService - .getPasswordComplexitySettings({ ctx: makeReqCtx(organization) }) - .then((resp) => resp.settings); + return unstable_cache( + async () => { + return await settingsService + .getPasswordComplexitySettings({ ctx: makeReqCtx(organization) }) + .then((resp) => resp.settings); + }, + ["complexitySettings", organization ?? "default"], + { revalidate: 3600, tags: ["complexitySettings"] }, + )(); } export async function createSessionFromChecks( From 3d7c4988e9606b1fe40abf34b8fcf34fcda7d125 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 19 Sep 2024 09:17:58 +0200 Subject: [PATCH 205/640] either or message --- apps/login/src/ui/PasswordForm.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index 003d40befd..45db796e20 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -87,14 +87,12 @@ export default function PasswordForm({ if (response && "error" in response) { setError(response.error); + } else { + setInfo("Password was reset. Please check your email."); } setLoading(false); - if (response) { - setInfo("Password was reset. Please check your email."); - } - return response; } From c3f89d06b7a233371ecdddc8cd915bd5bf8e671e Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 19 Sep 2024 09:32:06 +0200 Subject: [PATCH 206/640] parse to json and back --- apps/login/src/lib/zitadel.ts | 39 ++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index dcb227d20e..60b09cdc9f 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -17,10 +17,15 @@ import { VerifyU2FRegistrationRequest, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { create } from "@zitadel/client"; +import { create, fromJson, toJson } from "@zitadel/client"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; -import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { BrandingSettingsSchema } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; +import { LegalAndSupportSettingsSchema } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; +import { + IdentityProviderType, + LoginSettingsSchema, +} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { SearchQuery, @@ -52,11 +57,17 @@ export async function getBrandingSettings(organization?: string) { async () => { return await settingsService .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) - .then((resp) => resp.settings); + .then((resp) => + resp.settings + ? toJson(BrandingSettingsSchema, resp.settings) + : undefined, + ); }, ["brandingSettings", organization ?? "default"], { revalidate: 3600, tags: ["brandingSettings"] }, - )(); + )().then((resp) => + resp ? fromJson(BrandingSettingsSchema, resp) : undefined, + ); } export async function getLoginSettings(orgId?: string) { @@ -64,11 +75,15 @@ export async function getLoginSettings(orgId?: string) { async () => { return await settingsService .getLoginSettings({ ctx: makeReqCtx(orgId) }, {}) - .then((resp) => resp.settings); + .then((resp) => + resp.settings + ? toJson(LoginSettingsSchema, resp.settings) + : undefined, + ); }, ["loginSettings", orgId ?? "default"], { revalidate: 3600, tags: ["loginSettings"] }, - )(); + )().then((resp) => (resp ? fromJson(LoginSettingsSchema, resp) : undefined)); } export async function addOTPEmail(userId: string) { @@ -99,13 +114,17 @@ export async function getLegalAndSupportSettings(organization?: string) { async () => { return await settingsService .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) - .then((resp) => { - return resp.settings; - }); + .then((resp) => + resp.settings + ? toJson(LegalAndSupportSettingsSchema, resp.settings) + : undefined, + ); }, ["legalAndSupportSettings", organization ?? "default"], { revalidate: 3600, tags: ["legalAndSupportSettings"] }, - )(); + )().then((resp) => + resp ? fromJson(LegalAndSupportSettingsSchema, resp) : undefined, + ); } export async function getPasswordComplexitySettings(organization?: string) { From 536393e9ae90609e6878f5ec2f995b3192cf9b10 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 19 Sep 2024 13:37:00 +0200 Subject: [PATCH 207/640] hide password reset --- apps/login/src/ui/PasswordForm.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index 45db796e20..083fcc66fe 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -236,14 +236,16 @@ export default function PasswordForm({ {...register("password", { required: "This field is required" })} label="Password" /> - + {!loginSettings?.hidePasswordReset && ( + + )} {loginName && ( Date: Thu, 19 Sep 2024 13:51:24 +0200 Subject: [PATCH 208/640] otp verify --- .../src/app/(login)/otp/[method]/set/page.tsx | 42 +++++++------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index a43e0a0840..81da37d871 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -65,28 +65,21 @@ export default async function Page({ const paramsToContinue = new URLSearchParams({}); let urlToContinue = "/accounts"; - if (authRequestId && sessionId) { - if (sessionId) { - paramsToContinue.append("sessionId", sessionId); - } - if (authRequestId) { - paramsToContinue.append("authRequestId", authRequestId); - } - if (organization) { - paramsToContinue.append("organization", organization); - } + if (sessionId) { + paramsToContinue.append("sessionId", sessionId); + } + if (authRequestId) { + paramsToContinue.append("authRequestId", authRequestId); + } + if (organization) { + paramsToContinue.append("organization", organization); + } + + if (checkAfter) { + urlToContinue = `/otp/${method}?` + paramsToContinue; + } else if (authRequestId && sessionId) { urlToContinue = `/login?` + paramsToContinue; } else if (loginName) { - if (loginName) { - paramsToContinue.append("loginName", loginName); - } - if (authRequestId) { - paramsToContinue.append("authRequestId", authRequestId); - } - if (organization) { - paramsToContinue.append("organization", organization); - } - urlToContinue = `/signedin?` + paramsToContinue; } @@ -148,13 +141,8 @@ export default async function Page({
    - + +
    diff --git a/apps/login/src/ui/RegisterU2F.tsx b/apps/login/src/ui/RegisterU2F.tsx index 8c16c3b390..bb80159f18 100644 --- a/apps/login/src/ui/RegisterU2F.tsx +++ b/apps/login/src/ui/RegisterU2F.tsx @@ -11,15 +11,19 @@ import { Button, ButtonVariants } from "./Button"; import { Spinner } from "./Spinner"; type Props = { + loginName?: string; sessionId: string; authRequestId?: string; organization?: string; + checkAfter: boolean; }; export default function RegisterU2F({ + loginName, sessionId, organization, authRequestId, + checkAfter, }: Props) { const [error, setError] = useState(""); @@ -140,23 +144,31 @@ export default function RegisterU2F({ return; } - const params = new URLSearchParams(); + const paramsToContinue = new URLSearchParams({}); + let urlToContinue = "/accounts"; - if (organization) { - params.set("organization", organization); + if (sessionId) { + paramsToContinue.append("sessionId", sessionId); } - - // redirect to verify or sign in if (authRequestId) { - params.set("authRequestId", authRequestId); - params.set("sessionId", sessionId); - // params.set("altPassword", ${false}); // without setting altPassword this does not allow password - // params.set("loginName", resp.loginName); - - router.push("/u2f?" + params); - } else { - router.push("/accounts?" + params); + paramsToContinue.append("authRequestId", authRequestId); } + if (loginName) { + paramsToContinue.append("loginName", loginName); + } + if (organization) { + paramsToContinue.append("organization", organization); + } + + if (checkAfter) { + urlToContinue = `/u2f?` + paramsToContinue; + } else if (authRequestId && sessionId) { + urlToContinue = `/login?` + paramsToContinue; + } else if (loginName) { + urlToContinue = `/signedin?` + paramsToContinue; + } + + router.push(urlToContinue); } setLoading(false); From 0715281d008fc656e78483f1a5813d8c55934002 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 19 Sep 2024 16:00:20 +0200 Subject: [PATCH 212/640] signin after register --- apps/login/readme.md | 3 ++- .../src/app/(login)/otp/[method]/set/page.tsx | 5 +++++ apps/login/src/ui/SetPasswordForm.tsx | 21 ++++++++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 4851cf8d85..9fb2a0217d 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -236,6 +236,7 @@ Requests to the APIs made: - `addOTPEmail()` / `addOTPSMS()` This page directly calls `addOTPEmail()` or `addOTPSMS()` when invoked and shows a success message. +Right afterwards, redirects to verify the method. ### /u2f/set @@ -274,7 +275,7 @@ Requests to the APIs made: To register a user, the organization where the resource will be created is determined first. If no context is provided via url, we fall back to the default organization of the instance. -**PASSWORD:** If a password is set, the user is created as a resource, then a session using the password check is created immediately. +**PASSWORD:** If a password is set, the user is created as a resource, then a session using the password check is created immediately. After creating the session, the user is directly logged in and eventually redirected back to the application. **PASSKEY:** If passkey is selected, the user is created as a resource first, then a session using the userId is created immediately. This session does not yet contain a check, we therefore redirect the user to setup a passkey at `/passkey/set`. As the passkey set page verifies the passkey right afterwards, the process ends with a signed in user. diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index 448cf28742..72cb3a1c94 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -13,6 +13,7 @@ import TOTPRegister from "@/ui/TOTPRegister"; import UserAvatar from "@/ui/UserAvatar"; import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import Link from "next/link"; +import { redirect } from "next/navigation"; export default async function Page({ searchParams, @@ -80,6 +81,10 @@ export default async function Page({ if (checkAfter) { urlToContinue = `/otp/${method}?` + paramsToContinue; + // immediately check the OTP on the next page if sms or email was set up + if (["email", "sms"].includes(method)) { + return redirect(urlToContinue); + } } else if (authRequestId && sessionId) { urlToContinue = `/login?` + paramsToContinue; } else if (loginName) { diff --git a/apps/login/src/ui/SetPasswordForm.tsx b/apps/login/src/ui/SetPasswordForm.tsx index c657de0b02..8fd9e5e7b1 100644 --- a/apps/login/src/ui/SetPasswordForm.tsx +++ b/apps/login/src/ui/SetPasswordForm.tsx @@ -81,12 +81,12 @@ export default function SetPasswordForm({ return; } - const userReponse = response as RegisterUserResponse; + const userResponse = response as RegisterUserResponse; - const params = new URLSearchParams({ userId: userReponse.userId }); + const params = new URLSearchParams({ userId: userResponse.userId }); - if (userReponse.factors?.user?.loginName) { - params.append("loginName", userReponse.factors.user.loginName); + if (userResponse.factors?.user?.loginName) { + params.append("loginName", userResponse.factors.user.loginName); } if (authRequestId) { params.append("authRequestId", authRequestId); @@ -94,11 +94,18 @@ export default function SetPasswordForm({ if (organization) { params.append("organization", organization); } - if (userReponse && userReponse.sessionId) { - params.append("sessionId", userReponse.sessionId); + if (userResponse && userResponse.sessionId) { + params.append("sessionId", userResponse.sessionId); } - return router.push(`/verify?` + params); + // skip verification for now as it is an app based flow + // return router.push(`/verify?` + params); + + if (authRequestId && userResponse.sessionId) { + return router.push(`/login?` + params); + } else { + return router.push(`/signedin?` + params); + } } const { errors } = formState; From c82363a5c0bacfe01c2ed08e61d04b3a6f18f861 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 19 Sep 2024 16:20:53 +0200 Subject: [PATCH 213/640] cleanup session utils, check for valid session on /mfa/set --- apps/login/src/app/(login)/mfa/set/page.tsx | 13 ++++++++++-- .../session.ts => lib/server/cookie.ts} | 0 apps/login/src/lib/server/loginname.ts | 2 +- apps/login/src/lib/server/otp.ts | 2 +- apps/login/src/lib/server/password.ts | 8 ++++---- apps/login/src/lib/server/register.ts | 2 +- apps/login/src/lib/server/session.ts | 5 +++-- apps/login/src/lib/session.ts | 14 +++++++++++++ apps/login/src/ui/SessionItem.tsx | 20 +++++++------------ 9 files changed, 42 insertions(+), 24 deletions(-) rename apps/login/src/{utils/session.ts => lib/server/cookie.ts} (100%) diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index 8c2dfcc09f..131197742a 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -1,5 +1,5 @@ import { getSessionCookieById } from "@/lib/cookies"; -import { loadMostRecentSession } from "@/lib/session"; +import { isSessionValid, loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getLoginSettings, @@ -49,6 +49,7 @@ export default async function Page({ authMethods: methods.authMethodTypes ?? [], phoneVerified: humanUser?.phone?.isVerified ?? false, emailVerified: humanUser?.email?.isVerified ?? false, + expirationDate: session?.expirationDate, }; }); }); @@ -101,7 +102,15 @@ export default async function Page({ Provide your active session as loginName param )} - {loginSettings && sessionWithData ? ( + {isSessionValid(sessionWithData).valid && ( + + You have to have a valid session in order to set a second factor! + + )} + + {isSessionValid(sessionWithData).valid && + loginSettings && + sessionWithData ? ( resp.session); } + +export function isSessionValid(session: Partial) { + const validPassword = session?.factors?.password?.verifiedAt; + const validPasskey = session?.factors?.webAuthN?.verifiedAt; + const stillValid = session.expirationDate + ? timestampDate(session.expirationDate) > new Date() + : true; + + const verifiedAt = validPassword || validPasskey; + const valid = (validPassword || validPasskey) && stillValid; + + return { valid, verifiedAt }; +} diff --git a/apps/login/src/ui/SessionItem.tsx b/apps/login/src/ui/SessionItem.tsx index 77f3737924..ff03d70bdf 100644 --- a/apps/login/src/ui/SessionItem.tsx +++ b/apps/login/src/ui/SessionItem.tsx @@ -1,6 +1,7 @@ "use client"; import { cleanupSession } from "@/lib/server/session"; +import { isSessionValid } from "@/lib/session"; import { XCircleIcon } from "@heroicons/react/24/outline"; import { timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; @@ -32,14 +33,7 @@ export default function SessionItem({ return response; } - const validPassword = session?.factors?.password?.verifiedAt; - const validPasskey = session?.factors?.webAuthN?.verifiedAt; - const stillValid = session.expirationDate - ? timestampDate(session.expirationDate) > new Date() - : true; - - const validDate = validPassword || validPasskey; - const validUser = (validPassword || validPasskey) && stillValid; + const { valid, verifiedAt } = isSessionValid(session); const [error, setError] = useState(null); @@ -47,14 +41,14 @@ export default function SessionItem({ {session.factors?.user?.loginName}
    - {validUser && ( + {valid && ( - {validDate && moment(timestampDate(validDate)).fromNow()} + {verifiedAt && moment(timestampDate(verifiedAt)).fromNow()} )}
    - {validUser ? ( + {valid ? (
    ) : (
    From ce7f53f90ded6293f5f14031398968942bd303fc Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 19 Sep 2024 16:29:02 +0200 Subject: [PATCH 214/640] move client code --- apps/login/src/app/(login)/mfa/set/page.tsx | 3 ++- apps/login/src/lib/session.ts | 14 -------------- apps/login/src/ui/SessionItem.tsx | 14 +++++++++++++- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index 131197742a..323e3aead8 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -1,5 +1,5 @@ import { getSessionCookieById } from "@/lib/cookies"; -import { isSessionValid, loadMostRecentSession } from "@/lib/session"; +import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getLoginSettings, @@ -11,6 +11,7 @@ import Alert from "@/ui/Alert"; import BackButton from "@/ui/BackButton"; import ChooseSecondFactorToSetup from "@/ui/ChooseSecondFactorToSetup"; import DynamicTheme from "@/ui/DynamicTheme"; +import { isSessionValid } from "@/ui/SessionItem"; import UserAvatar from "@/ui/UserAvatar"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; diff --git a/apps/login/src/lib/session.ts b/apps/login/src/lib/session.ts index 6a219a2f5b..29ceb3764b 100644 --- a/apps/login/src/lib/session.ts +++ b/apps/login/src/lib/session.ts @@ -1,4 +1,3 @@ -import { timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { GetSessionResponse } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { getMostRecentCookieWithLoginname } from "./cookies"; @@ -16,16 +15,3 @@ export async function loadMostRecentSession(sessionParams: { .getSession({ sessionId: recent.id, sessionToken: recent.token }, {}) .then((resp: GetSessionResponse) => resp.session); } - -export function isSessionValid(session: Partial) { - const validPassword = session?.factors?.password?.verifiedAt; - const validPasskey = session?.factors?.webAuthN?.verifiedAt; - const stillValid = session.expirationDate - ? timestampDate(session.expirationDate) > new Date() - : true; - - const verifiedAt = validPassword || validPasskey; - const valid = (validPassword || validPasskey) && stillValid; - - return { valid, verifiedAt }; -} diff --git a/apps/login/src/ui/SessionItem.tsx b/apps/login/src/ui/SessionItem.tsx index ff03d70bdf..16748b0d99 100644 --- a/apps/login/src/ui/SessionItem.tsx +++ b/apps/login/src/ui/SessionItem.tsx @@ -1,7 +1,6 @@ "use client"; import { cleanupSession } from "@/lib/server/session"; -import { isSessionValid } from "@/lib/session"; import { XCircleIcon } from "@heroicons/react/24/outline"; import { timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; @@ -10,6 +9,19 @@ import Link from "next/link"; import { useState } from "react"; import { Avatar } from "./Avatar"; +export function isSessionValid(session: Partial) { + const validPassword = session?.factors?.password?.verifiedAt; + const validPasskey = session?.factors?.webAuthN?.verifiedAt; + const stillValid = session.expirationDate + ? timestampDate(session.expirationDate) > new Date() + : true; + + const verifiedAt = validPassword || validPasskey; + const valid = (validPassword || validPasskey) && stillValid; + + return { valid, verifiedAt }; +} + export default function SessionItem({ session, reload, From 97d1ba266f0118c30ec6edf09385f9174e184a7d Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 19 Sep 2024 16:47:07 +0200 Subject: [PATCH 215/640] authrequest param for /login --- .../login/src/app/(login)/otp/[method]/set/page.tsx | 12 +++++++++--- apps/login/src/ui/LoginOTP.tsx | 8 ++++++++ apps/login/src/ui/RegisterU2F.tsx | 13 ++++++++++--- apps/login/src/ui/SetPasswordForm.tsx | 9 ++++++--- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index 72cb3a1c94..9cb12a07c1 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -69,9 +69,6 @@ export default async function Page({ if (sessionId) { paramsToContinue.append("sessionId", sessionId); } - if (authRequestId) { - paramsToContinue.append("authRequestId", authRequestId); - } if (loginName) { paramsToContinue.append("loginName", loginName); } @@ -80,14 +77,23 @@ export default async function Page({ } if (checkAfter) { + if (authRequestId) { + paramsToContinue.append("authRequestId", authRequestId); + } urlToContinue = `/otp/${method}?` + paramsToContinue; // immediately check the OTP on the next page if sms or email was set up if (["email", "sms"].includes(method)) { return redirect(urlToContinue); } } else if (authRequestId && sessionId) { + if (authRequestId) { + paramsToContinue.append("authRequest", authRequestId); + } urlToContinue = `/login?` + paramsToContinue; } else if (loginName) { + if (authRequestId) { + paramsToContinue.append("authRequestId", authRequestId); + } urlToContinue = `/signedin?` + paramsToContinue; } diff --git a/apps/login/src/ui/LoginOTP.tsx b/apps/login/src/ui/LoginOTP.tsx index 0493c6af63..f8b0074809 100644 --- a/apps/login/src/ui/LoginOTP.tsx +++ b/apps/login/src/ui/LoginOTP.tsx @@ -158,6 +158,14 @@ export default function LoginOTP({ params.append("organization", organization); } + if (authRequestId) { + params.append("authRequest", authRequestId); + } + + if (sessionId) { + params.append("sessionId", sessionId); + } + return router.push(`/login?` + params); } else { const params = new URLSearchParams(); diff --git a/apps/login/src/ui/RegisterU2F.tsx b/apps/login/src/ui/RegisterU2F.tsx index bb80159f18..2e7f295a08 100644 --- a/apps/login/src/ui/RegisterU2F.tsx +++ b/apps/login/src/ui/RegisterU2F.tsx @@ -150,9 +150,7 @@ export default function RegisterU2F({ if (sessionId) { paramsToContinue.append("sessionId", sessionId); } - if (authRequestId) { - paramsToContinue.append("authRequestId", authRequestId); - } + if (loginName) { paramsToContinue.append("loginName", loginName); } @@ -161,10 +159,19 @@ export default function RegisterU2F({ } if (checkAfter) { + if (authRequestId) { + paramsToContinue.append("authRequestId", authRequestId); + } urlToContinue = `/u2f?` + paramsToContinue; } else if (authRequestId && sessionId) { + if (authRequestId) { + paramsToContinue.append("authRequest", authRequestId); + } urlToContinue = `/login?` + paramsToContinue; } else if (loginName) { + if (authRequestId) { + paramsToContinue.append("authRequestId", authRequestId); + } urlToContinue = `/signedin?` + paramsToContinue; } diff --git a/apps/login/src/ui/SetPasswordForm.tsx b/apps/login/src/ui/SetPasswordForm.tsx index 8fd9e5e7b1..c6769143b0 100644 --- a/apps/login/src/ui/SetPasswordForm.tsx +++ b/apps/login/src/ui/SetPasswordForm.tsx @@ -88,9 +88,6 @@ export default function SetPasswordForm({ if (userResponse.factors?.user?.loginName) { params.append("loginName", userResponse.factors.user.loginName); } - if (authRequestId) { - params.append("authRequestId", authRequestId); - } if (organization) { params.append("organization", organization); } @@ -102,8 +99,14 @@ export default function SetPasswordForm({ // return router.push(`/verify?` + params); if (authRequestId && userResponse.sessionId) { + if (authRequestId) { + params.append("authRequest", authRequestId); + } return router.push(`/login?` + params); } else { + if (authRequestId) { + params.append("authRequestId", authRequestId); + } return router.push(`/signedin?` + params); } } From 393eda8c553be957ce9aa20ec62d63470c7ad29c Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 19 Sep 2024 17:16:56 +0200 Subject: [PATCH 216/640] fix valid session check --- apps/login/src/app/(login)/mfa/set/page.tsx | 53 ++++++++++++++------- apps/login/src/ui/AuthMethods.tsx | 2 +- apps/login/src/ui/SessionItem.tsx | 9 ++-- apps/login/src/ui/SetPasswordForm.tsx | 2 + 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index 323e3aead8..1a67b57340 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -11,10 +11,26 @@ import Alert from "@/ui/Alert"; import BackButton from "@/ui/BackButton"; import ChooseSecondFactorToSetup from "@/ui/ChooseSecondFactorToSetup"; import DynamicTheme from "@/ui/DynamicTheme"; -import { isSessionValid } from "@/ui/SessionItem"; import UserAvatar from "@/ui/UserAvatar"; +import { Timestamp, timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +export function isSessionValid(session: Partial): { + valid: boolean; + verifiedAt?: Timestamp; +} { + const validPassword = session?.factors?.password?.verifiedAt; + const validPasskey = session?.factors?.webAuthN?.verifiedAt; + const stillValid = session.expirationDate + ? timestampDate(session.expirationDate) > new Date() + : true; + + const verifiedAt = validPassword || validPasskey; + const valid = !!((validPassword || validPasskey) && stillValid); + + return { valid, verifiedAt }; +} + export default async function Page({ searchParams, }: { @@ -83,6 +99,8 @@ export default async function Page({ sessionWithData.factors?.user?.organizationId, ); + const { valid } = isSessionValid(sessionWithData); + return (
    @@ -103,29 +121,28 @@ export default async function Page({ Provide your active session as loginName param )} - {isSessionValid(sessionWithData).valid && ( + {!valid && ( You have to have a valid session in order to set a second factor! + {/* TODO: show reauth button */} )} {isSessionValid(sessionWithData).valid && - loginSettings && - sessionWithData ? ( - - ) : ( - No second factors available to setup. - )} + loginSettings && + sessionWithData && ( + + )}
    diff --git a/apps/login/src/ui/AuthMethods.tsx b/apps/login/src/ui/AuthMethods.tsx index 09e3925075..1ace9b8a2b 100644 --- a/apps/login/src/ui/AuthMethods.tsx +++ b/apps/login/src/ui/AuthMethods.tsx @@ -32,7 +32,7 @@ const LinkWrapper = ({ export const TOTP = (alreadyAdded: boolean, link: string) => { return ( - +
    ) { +export function isSessionValid(session: Partial): { + valid: boolean; + verifiedAt?: Timestamp; +} { const validPassword = session?.factors?.password?.verifiedAt; const validPasskey = session?.factors?.webAuthN?.verifiedAt; const stillValid = session.expirationDate @@ -17,7 +20,7 @@ export function isSessionValid(session: Partial) { : true; const verifiedAt = validPassword || validPasskey; - const valid = (validPassword || validPasskey) && stillValid; + const valid = !!((validPassword || validPasskey) && stillValid); return { valid, verifiedAt }; } diff --git a/apps/login/src/ui/SetPasswordForm.tsx b/apps/login/src/ui/SetPasswordForm.tsx index c6769143b0..3091340ab3 100644 --- a/apps/login/src/ui/SetPasswordForm.tsx +++ b/apps/login/src/ui/SetPasswordForm.tsx @@ -98,6 +98,8 @@ export default function SetPasswordForm({ // skip verification for now as it is an app based flow // return router.push(`/verify?` + params); + // check for mfa force to continue with mfa setup + if (authRequestId && userResponse.sessionId) { if (authRequestId) { params.append("authRequest", authRequestId); From 5c097b8054f767da07ba1f7ac3c2a9e8c364ba0e Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 19 Sep 2024 17:27:54 +0200 Subject: [PATCH 217/640] rm export --- apps/login/src/app/(login)/mfa/set/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index 1a67b57340..cc7db26e50 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -15,7 +15,7 @@ import UserAvatar from "@/ui/UserAvatar"; import { Timestamp, timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -export function isSessionValid(session: Partial): { +function isSessionValid(session: Partial): { valid: boolean; verifiedAt?: Timestamp; } { From dda1d91f84fdac21488ea03617b7f840e6d5d47a Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 20 Sep 2024 09:42:25 +0200 Subject: [PATCH 218/640] Update apps/login/readme.md Co-authored-by: Elio Bischof --- apps/login/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 9fb2a0217d..49e2dbf032 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -46,7 +46,7 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a ### /loginname This page shows a loginname field and Identity Providers to login or register. -If `loginSettings(org?).allowRegister` is `true`, if will also show a link to jump to /register +If `loginSettings(org?).allowRegister` is `true`, it also shows a link to jump to /register /loginame From e15911c926db641bf8767faaa1fe3a5c3c75cc9a Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 20 Sep 2024 09:42:35 +0200 Subject: [PATCH 219/640] Update apps/login/readme.md Co-authored-by: Elio Bischof --- apps/login/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 49e2dbf032..ff1b8d6bfb 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -79,7 +79,7 @@ If no single IDP is set, we check for `loginSettings.allowUsernamePassword` and If no previous condition is met we throw an error stating the user was not found. -**EXCEPTIONS:** If the outcome after this order produces a no authentication methods found, or user not found, we check whether `loginSettings?.ignoreUnknownUsernames` is set to `true` as in this case we redirect to the /password page regardless (to not leak information about a registered user). +**EXCEPTIONS:** If the outcome after this order produces a no authentication methods found, or user not found, we check whether `loginSettings?.ignoreUnknownUsernames` is set to `true` as in this case we redirect to the /password page regardless (to prevent username guessing). > NOTE: This page at this stage beeing ignores local sessions and executes a reauthentication. This is a feature which is not implemented yet. From aa228fe576ad13a292a071e15fcc7863ec6f0aa4 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 20 Sep 2024 09:42:43 +0200 Subject: [PATCH 220/640] Update apps/login/readme.md Co-authored-by: Elio Bischof --- apps/login/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index ff1b8d6bfb..fe76208390 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -102,7 +102,7 @@ Requests to the APIs made: - `getSession()` - `updateSession()` -**MFA AVAILABLE:** After the password has been submitted, additional authentication Methods are loaded. +**MFA AVAILABLE:** After the password has been submitted, additional authentication methods are loaded. If the user has set up an additional **single** second factor, it is redirected to add the next factor. Depending on the available method he is redirected to `/otp/time-based`,`/otp/sms?`, `/otp/email?` or `/u2f?`. If the user has multiple second factors, he is redirected to `/mfa` to select his preferred method to continue. **NO MFA, FORCE MFA:** If no MFA method is available, and the settings force MFA, the user is sent to `/mfa/set` which prompts to setup a second factor. From 6d2edc7c3b0a2a2b16359927a6d3dd4f0576c8cd Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 20 Sep 2024 09:42:48 +0200 Subject: [PATCH 221/640] Update apps/login/readme.md Co-authored-by: Elio Bischof --- apps/login/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index fe76208390..dc32499a1d 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -165,7 +165,7 @@ After updating the session, the user is **always** signed in. :warning: required ### /mfa/set -This page loads login Settings and the authentication methods for a user and shows setup options. +This page loads login settings and the authentication methods for a user and shows setup options. /mfa/set From 97900d4fea8cfadcfb34066e221185a863eee0ff Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 20 Sep 2024 09:43:16 +0200 Subject: [PATCH 222/640] Update apps/login/src/app/(login)/mfa/set/page.tsx Co-authored-by: Elio Bischof --- apps/login/src/app/(login)/mfa/set/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index cc7db26e50..1e1c13fc9a 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -123,7 +123,7 @@ export default async function Page({ {!valid && ( - You have to have a valid session in order to set a second factor! + You need to have a valid session in order to set a second factor! {/* TODO: show reauth button */} )} From 0057bb6cfcc96c0cc734e341866d8b5e9646b38e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 20 Sep 2024 09:44:24 +0200 Subject: [PATCH 223/640] Update apps/login/src/ui/RegisterFormWithoutPassword.tsx Co-authored-by: Elio Bischof --- apps/login/src/ui/RegisterFormWithoutPassword.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/login/src/ui/RegisterFormWithoutPassword.tsx b/apps/login/src/ui/RegisterFormWithoutPassword.tsx index c7ba74cabc..66c3c34045 100644 --- a/apps/login/src/ui/RegisterFormWithoutPassword.tsx +++ b/apps/login/src/ui/RegisterFormWithoutPassword.tsx @@ -63,7 +63,6 @@ export default function RegisterFormWithoutPassword({ lastName: values.lastname, organization: organization, }).catch((error) => { - console.error(error); setError("Could not register user"); setLoading(false); }); From 71dd772015c40014e52a3cb4f9d6866c1f052cd6 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 20 Sep 2024 09:52:53 +0200 Subject: [PATCH 224/640] CACHE_REVALIDATION_INTERVAL_IN_SECONDS --- apps/login/src/lib/zitadel.ts | 24 +++++++++++++++++++----- turbo.json | 1 + 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index cbde130210..4f542f533b 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -35,7 +35,9 @@ import { import { unstable_cache } from "next/cache"; import { PROVIDER_MAPPING } from "./idp"; -const SESSION_LIFETIME_S = 3600; +const SESSION_LIFETIME_S = 3600; // TODO load from oidc settings +const CACHE_REVALIDATION_INTERVAL_IN_SECONDS = + Number(process.env.CACHE_REVALIDATION_INTERVAL_IN_SECONDS) ?? 3600; const transport = createServerTransport( process.env.ZITADEL_SERVICE_USER_TOKEN!, @@ -65,7 +67,10 @@ export async function getBrandingSettings(organization?: string) { ); }, ["brandingSettings", organization ?? "default"], - { revalidate: 3600, tags: ["brandingSettings"] }, + { + revalidate: CACHE_REVALIDATION_INTERVAL_IN_SECONDS, + tags: ["brandingSettings"], + }, )().then((resp) => resp ? fromJson(BrandingSettingsSchema, resp) : undefined, ); @@ -83,7 +88,10 @@ export async function getLoginSettings(orgId?: string) { ); }, ["loginSettings", orgId ?? "default"], - { revalidate: 3600, tags: ["loginSettings"] }, + { + revalidate: CACHE_REVALIDATION_INTERVAL_IN_SECONDS, + tags: ["loginSettings"], + }, )().then((resp) => (resp ? fromJson(LoginSettingsSchema, resp) : undefined)); } @@ -122,7 +130,10 @@ export async function getLegalAndSupportSettings(organization?: string) { ); }, ["legalAndSupportSettings", organization ?? "default"], - { revalidate: 3600, tags: ["legalAndSupportSettings"] }, + { + revalidate: CACHE_REVALIDATION_INTERVAL_IN_SECONDS, + tags: ["legalAndSupportSettings"], + }, )().then((resp) => resp ? fromJson(LegalAndSupportSettingsSchema, resp) : undefined, ); @@ -140,7 +151,10 @@ export async function getPasswordComplexitySettings(organization?: string) { ); }, ["complexitySettings", organization ?? "default"], - { revalidate: 3600, tags: ["complexitySettings"] }, + { + revalidate: CACHE_REVALIDATION_INTERVAL_IN_SECONDS, + tags: ["complexitySettings"], + }, )().then((resp) => resp ? fromJson(PasswordComplexitySettingsSchema, resp) : undefined, ); diff --git a/turbo.json b/turbo.json index 3c2ec89865..7d38ae4c7f 100644 --- a/turbo.json +++ b/turbo.json @@ -12,6 +12,7 @@ "ZITADEL_SYSTEM_API_KEY", "ZITADEL_ISSUER", "ZITADEL_ADMIN_TOKEN", + "CACHE_REVALIDATION_INTERVAL_IN_SECONDS", "VERCEL_URL" ], "tasks": { From ed8813093ed8908b7661816a3b0e72242fb7c8f5 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 20 Sep 2024 09:56:40 +0200 Subject: [PATCH 225/640] revalidation interval for integration test --- apps/login/.env.integration | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/login/.env.integration b/apps/login/.env.integration index d258e2cb55..03bff984d9 100644 --- a/apps/login/.env.integration +++ b/apps/login/.env.integration @@ -1,2 +1,3 @@ ZITADEL_API_URL=http://localhost:22222 +CACHE_REVALIDATION_INTERVAL_IN_SECONDS=3600 DEBUG=true \ No newline at end of file From c28eeb3cabb465e82ce8807e9dab6354e328d632 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 20 Sep 2024 10:21:22 +0200 Subject: [PATCH 226/640] rm client side logs --- apps/login/src/ui/ChangePasswordForm.tsx | 4 ++-- apps/login/src/ui/LoginPasskey.tsx | 8 ++++---- apps/login/src/ui/PasswordForm.tsx | 9 +++------ apps/login/src/ui/RegisterPasskey.tsx | 10 ++++------ apps/login/src/ui/RegisterU2F.tsx | 11 ++++------- apps/login/src/ui/SetPasswordForm.tsx | 3 +-- apps/login/src/ui/SignInWithIDP.tsx | 4 ++-- apps/login/src/ui/UsernameForm.tsx | 7 +++---- apps/login/src/ui/VerifyEmailForm.tsx | 10 ++++------ 9 files changed, 27 insertions(+), 39 deletions(-) diff --git a/apps/login/src/ui/ChangePasswordForm.tsx b/apps/login/src/ui/ChangePasswordForm.tsx index 72d8929894..56bb123ecf 100644 --- a/apps/login/src/ui/ChangePasswordForm.tsx +++ b/apps/login/src/ui/ChangePasswordForm.tsx @@ -55,8 +55,8 @@ export default function ChangePasswordForm({ sessionId: sessionId, userId: userId, password: values.password, - }).catch((error: Error) => { - setError(error.message ?? "Could not change password"); + }).catch(() => { + setError("Could not change password"); }); setLoading(false); diff --git a/apps/login/src/ui/LoginPasskey.tsx b/apps/login/src/ui/LoginPasskey.tsx index 00dc283ae4..06d422fb46 100644 --- a/apps/login/src/ui/LoginPasskey.tsx +++ b/apps/login/src/ui/LoginPasskey.tsx @@ -90,8 +90,8 @@ export default function LoginPasskey({ }, }), authRequestId, - }).catch((error: Error) => { - setError(error.message); + }).catch(() => { + setError("Could not request passkey challenge"); }); setLoading(false); @@ -108,8 +108,8 @@ export default function LoginPasskey({ webAuthN: { credentialAssertionData: data }, } as Checks, authRequestId, - }).catch((error: Error) => { - setError(error.message); + }).catch(() => { + setError("Could not verify passkey"); }); setLoading(false); diff --git a/apps/login/src/ui/PasswordForm.tsx b/apps/login/src/ui/PasswordForm.tsx index 083fcc66fe..9c76561f25 100644 --- a/apps/login/src/ui/PasswordForm.tsx +++ b/apps/login/src/ui/PasswordForm.tsx @@ -57,9 +57,8 @@ export default function PasswordForm({ password: { password: values.password }, }), authRequestId, - }).catch((error: Error) => { - setError(error.message ?? "Could not verify password"); - setLoading(false); + }).catch(() => { + setError("Could not verify password"); }); if (response && "error" in response && response.error) { @@ -79,9 +78,7 @@ export default function PasswordForm({ const response = await resetPassword({ loginName, organization, - }).catch((error: Error) => { - console.error(error); - setLoading(false); + }).catch(() => { setError("Could not reset password"); }); diff --git a/apps/login/src/ui/RegisterPasskey.tsx b/apps/login/src/ui/RegisterPasskey.tsx index db96474c01..afa6c3651b 100644 --- a/apps/login/src/ui/RegisterPasskey.tsx +++ b/apps/login/src/ui/RegisterPasskey.tsx @@ -47,9 +47,8 @@ export default function RegisterPasskey({ passkeyName, publicKeyCredential, sessionId, - }).catch((error: Error) => { - setError(error.message); - setLoading(false); + }).catch(() => { + setError("Could not verify Passkey"); }); setLoading(false); @@ -60,9 +59,8 @@ export default function RegisterPasskey({ setLoading(true); const resp = await registerPasskeyLink({ sessionId, - }).catch((error: Error) => { - setError(error.message ?? "Could not register passkey"); - setLoading(false); + }).catch(() => { + setError("Could not register passkey"); }); setLoading(false); diff --git a/apps/login/src/ui/RegisterU2F.tsx b/apps/login/src/ui/RegisterU2F.tsx index 2e7f295a08..1eaa314dfa 100644 --- a/apps/login/src/ui/RegisterU2F.tsx +++ b/apps/login/src/ui/RegisterU2F.tsx @@ -44,9 +44,7 @@ export default function RegisterU2F({ passkeyName, publicKeyCredential, sessionId, - }).catch((error: Error) => { - console.error(error); - setLoading(false); + }).catch(() => { setError("An error on verifying passkey occurred"); }); @@ -64,18 +62,17 @@ export default function RegisterU2F({ setLoading(true); const response = await addU2F({ sessionId, - }).catch((error: Error) => { - console.error(error); - setLoading(false); + }).catch(() => { setError("An error on registering passkey"); }); + setLoading(false); + if (response && "error" in response && response?.error) { setError(response?.error); } if (!response || !("u2fId" in response)) { - setLoading(false); setError("An error on registering passkey"); return; } diff --git a/apps/login/src/ui/SetPasswordForm.tsx b/apps/login/src/ui/SetPasswordForm.tsx index 3091340ab3..84dce18a31 100644 --- a/apps/login/src/ui/SetPasswordForm.tsx +++ b/apps/login/src/ui/SetPasswordForm.tsx @@ -65,8 +65,7 @@ export default function SetPasswordForm({ organization: organization, authRequestId: authRequestId, password: values.password, - }).catch((error: Error) => { - console.error(error); + }).catch(() => { setError("Could not register user"); }); diff --git a/apps/login/src/ui/SignInWithIDP.tsx b/apps/login/src/ui/SignInWithIDP.tsx index f324147a40..c279ee3604 100644 --- a/apps/login/src/ui/SignInWithIDP.tsx +++ b/apps/login/src/ui/SignInWithIDP.tsx @@ -53,8 +53,8 @@ export function SignInWithIDP({ `${host}/idp/${provider}/success?` + new URLSearchParams(params), failureUrl: `${host}/idp/${provider}/failure?` + new URLSearchParams(params), - }).catch((error: Error) => { - setError(error.message ?? "Could not start IDP flow"); + }).catch(() => { + setError("Could not start IDP flow"); }); setLoading(false); diff --git a/apps/login/src/ui/UsernameForm.tsx b/apps/login/src/ui/UsernameForm.tsx index e08744acab..852d05f06b 100644 --- a/apps/login/src/ui/UsernameForm.tsx +++ b/apps/login/src/ui/UsernameForm.tsx @@ -50,17 +50,16 @@ export default function UsernameForm({ loginName: values.loginName, organization, authRequestId, - }).catch((error: Error) => { - console.error(error); + }).catch(() => { setError("An internal error occurred"); }); + setLoading(false); + if (res?.error) { setError(res.error); } - setLoading(false); - return res; } diff --git a/apps/login/src/ui/VerifyEmailForm.tsx b/apps/login/src/ui/VerifyEmailForm.tsx index ce2589661d..7a55989e83 100644 --- a/apps/login/src/ui/VerifyEmailForm.tsx +++ b/apps/login/src/ui/VerifyEmailForm.tsx @@ -60,9 +60,8 @@ export default function VerifyEmailForm({ setLoading(true); const response = await resendVerifyEmail({ userId, - }).catch((error: Error) => { - setLoading(false); - setError(error.message); + }).catch(() => { + setError("Could not resend email"); }); setLoading(false); @@ -74,9 +73,8 @@ export default function VerifyEmailForm({ const verifyResponse = await verifyUserByEmail({ code: value.code, userId, - }).catch((error: Error) => { - setLoading(false); - setError(error.message ?? "Could not verify email"); + }).catch(() => { + setError("Could not verify email"); }); setLoading(false); From 26d13ae7f806dde09d177c36a0dd0c381795ad63 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 20 Sep 2024 10:22:19 +0200 Subject: [PATCH 227/640] logs --- apps/login/src/app/(login)/otp/[method]/set/page.tsx | 2 -- apps/login/src/ui/LoginPasskey.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index 9cb12a07c1..ee657c5737 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -47,13 +47,11 @@ export default async function Page({ } else if (method === "sms") { // does not work await addOTPSMS(session.factors.user.id).catch((error) => { - console.error(error); error = new Error("Could not add OTP via SMS"); }); } else if (method === "email") { // works await addOTPEmail(session.factors.user.id).catch((error) => { - console.error(error); error = new Error("Could not add OTP via Email"); }); } else { diff --git a/apps/login/src/ui/LoginPasskey.tsx b/apps/login/src/ui/LoginPasskey.tsx index 06d422fb46..77cb47a221 100644 --- a/apps/login/src/ui/LoginPasskey.tsx +++ b/apps/login/src/ui/LoginPasskey.tsx @@ -189,9 +189,9 @@ export default function LoginPasskey({ }); }) .catch((error) => { + // we log this error to the console, as it is not a critical error console.error(error); setLoading(false); - // setError(error); return null; }); } From 36d91f23ac8a977f859944c666f14d7ab01fe771 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 20 Sep 2024 11:00:42 +0200 Subject: [PATCH 228/640] fix fallback --- README.md | 2 +- apps/login/src/lib/zitadel.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 54ecedc8f2..32794d3bce 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ZITADEL TypeScript with Turborepo and Changesets +# ZITADEL TypeScript with Turborepo This repository contains all TypeScript and JavaScript packages and applications you need to create your own ZITADEL Login UI. diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 4f542f533b..957749c113 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -36,8 +36,10 @@ import { unstable_cache } from "next/cache"; import { PROVIDER_MAPPING } from "./idp"; const SESSION_LIFETIME_S = 3600; // TODO load from oidc settings -const CACHE_REVALIDATION_INTERVAL_IN_SECONDS = - Number(process.env.CACHE_REVALIDATION_INTERVAL_IN_SECONDS) ?? 3600; +const CACHE_REVALIDATION_INTERVAL_IN_SECONDS = process.env + .CACHE_REVALIDATION_INTERVAL_IN_SECONDS + ? Number(process.env.CACHE_REVALIDATION_INTERVAL_IN_SECONDS) + : 3600; const transport = createServerTransport( process.env.ZITADEL_SERVICE_USER_TOKEN!, From 0f75ed23011e86a4a8b2a6507f863cb5143d21e0 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 20 Sep 2024 17:40:53 +0200 Subject: [PATCH 229/640] readme --- README.md | 2 ++ apps/login/screenshots/collage.png | Bin 0 -> 288519 bytes 2 files changed, 2 insertions(+) create mode 100644 apps/login/screenshots/collage.png diff --git a/README.md b/README.md index 32794d3bce..1434027df2 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ You can read the [contribution guide](/CONTRIBUTING.md) on how to contribute. Questions can be raised in our [Discord channel](https://discord.gg/erh5Brh7jE) or as a [GitHub issue](https://github.com/zitadel/typescript/issues). +collage of login screens + ## Developing Your Own ZITADEL Login UI We think the easiest path of getting up and running, is the following: diff --git a/apps/login/screenshots/collage.png b/apps/login/screenshots/collage.png new file mode 100644 index 0000000000000000000000000000000000000000..9d5a9c35c866d41201d5f2e5e7e7635875671cce GIT binary patch literal 288519 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 {identityProviders && ( - + > )}

    S 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ⅈ{@;$lRGQ!5NUL+6h>T+_t+K#g= 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�^D|6-W4!=P9aa>)@?sv09_i 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%0DD1=NS{F% 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 Date: Fri, 20 Sep 2024 17:46:32 +0200 Subject: [PATCH 230/640] readme --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1434027df2..819d583fb8 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ This repository contains all TypeScript and JavaScript packages and applications you need to create your own ZITADEL Login UI. +collage of login screens + **⚠️ This repo and packages are in alpha state and subject to change ⚠️** The scope of functionality of this repo and packages is under active development. @@ -13,8 +15,6 @@ You can read the [contribution guide](/CONTRIBUTING.md) on how to contribute. Questions can be raised in our [Discord channel](https://discord.gg/erh5Brh7jE) or as a [GitHub issue](https://github.com/zitadel/typescript/issues). -collage of login screens - ## Developing Your Own ZITADEL Login UI We think the easiest path of getting up and running, is the following: @@ -73,6 +73,7 @@ You can already use the current state, and extend it with your needs. - [x] Domain Discovery - [x] Branding - OIDC Standard + - [x] Authorization Code Flow with PKCE - [x] AuthRequest `hintUserId` - [x] AuthRequest `loginHint` @@ -90,6 +91,47 @@ You can already use the current state, and extend it with your needs. - [x] `urn:zitadel:iam:org:domain:primary:{domain}` - [ ] AuthRequest UI locales + > Back navigation or retrys are not displayed. The flows for reauthentication after registering Passkeys, OTP or U2F methods are not displayed either as these will be omitted in future UX. + +```mermaid + flowchart TD + A[Start] --> register + A[Start] --> accounts + A[Start] --> loginname + loginname -- signInWithIDP --> idp-success + loginname -- signInWithIDP --> idp-failure + idp-success --> B[signedin] + loginname --> password + loginname -- hasPasskey --> passkey + loginname -- allowRegister --> register + passkey-add --passwordAllowed --> password + passkey -- hasPassword --> password + passkey --> B[signedin] + password -- hasMFA --> mfa + password -- allowPasskeys --> passkey-add + mfa --> otp + otp --> B[signedin] + mfa--> u2f + u2f -->B[signedin] + register --> passkey-add + register --> password-set + password-set --> B[signedin] + passkey-add --> B[signedin] + password --> B[signedin] + password-- forceMFA -->mfaset + mfaset --> u2fset + mfaset --> otpset + u2fset --> B[signedin] + otpset --> B[signedin] + accounts--> loginname + password -- not verified yet -->verify + register-- withpassword -->verify + passkey-- notVerified --> verify + verify --> B[signedin] +``` + +You can find a more detailed documentation of the different pages [here](./apps/login/readme.md). + ## Tooling - [TypeScript](https://www.typescriptlang.org/) for static type checking From d59080ccb0a25fb8060834c06692aad5bc5f1f35 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 20 Sep 2024 17:48:01 +0200 Subject: [PATCH 231/640] readme --- README.md | 6 +++++- apps/login/readme.md | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 819d583fb8..fc42a0467c 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,11 @@ You can already use the current state, and extend it with your needs. - [x] `urn:zitadel:iam:org:domain:primary:{domain}` - [ ] AuthRequest UI locales - > Back navigation or retrys are not displayed. The flows for reauthentication after registering Passkeys, OTP or U2F methods are not displayed either as these will be omitted in future UX. + #### Flow diagram + + This diagram shows the available pages and flows. + + > Note that back navigation or retries are not displayed. ```mermaid flowchart TD diff --git a/apps/login/readme.md b/apps/login/readme.md index dc32499a1d..f95a63619d 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -4,7 +4,9 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a ## Flow Diagram -> Back navigation or retrys are not displayed. The flows for reauthentication after registering Passkeys, OTP or U2F methods are not displayed either as these will be omitted in future UX. +This diagram shows the available pages and flows. + +> Note that back navigation or retries are not displayed. ```mermaid flowchart TD From d4c437a8ed1f877c1f09e4b7681c001c0ffed463 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 20 Sep 2024 17:48:33 +0200 Subject: [PATCH 232/640] header --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fc42a0467c..6916ed2bd0 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ the features will be extended. This list should show the current implementation state, and also what is missing. You can already use the current state, and extend it with your needs. +#### Features list + - [x] Local User Registration (with Password) - [x] User Registration and Login with external Provider - [x] Google From 4d690dd0e9a0f2fe0ff5e7be9083b2320b12effa Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Thu, 26 Sep 2024 22:50:55 -0400 Subject: [PATCH 233/640] chore: follow adrs --- apps/login/src/app/(login)/accounts/page.tsx | 4 ++-- apps/login/src/app/(login)/error.tsx | 8 +++---- .../(login)/idp/[provider]/failure/page.tsx | 2 +- .../(login)/idp/[provider]/success/page.tsx | 6 ++--- apps/login/src/app/(login)/idp/page.tsx | 8 +++---- apps/login/src/app/(login)/loginname/page.tsx | 10 ++++---- .../app/(login)/me/change-password/page.tsx | 8 +++---- apps/login/src/app/(login)/mfa/page.tsx | 10 ++++---- apps/login/src/app/(login)/mfa/set/page.tsx | 10 ++++---- .../src/app/(login)/otp/[method]/page.tsx | 8 +++---- .../src/app/(login)/otp/[method]/set/page.tsx | 16 ++++++------- apps/login/src/app/(login)/passkey/page.tsx | 8 +++---- .../src/app/(login)/passkey/set/page.tsx | 8 +++---- apps/login/src/app/(login)/password/page.tsx | 8 +++---- apps/login/src/app/(login)/register/page.tsx | 6 ++--- apps/login/src/app/(login)/signedin/page.tsx | 6 ++--- apps/login/src/app/(login)/u2f/page.tsx | 8 +++---- apps/login/src/app/(login)/u2f/set/page.tsx | 10 ++++---- apps/login/src/app/(login)/verify/page.tsx | 6 ++--- apps/login/src/app/layout.tsx | 12 ++++++---- apps/login/src/app/login/route.ts | 17 +++++++------- .../address-bar.tsx} | 6 ++--- .../{ui/Alert.tsx => components/alert.tsx} | 7 +++--- .../auth-methods.tsx} | 4 ++-- .../authentication-method-radio.tsx} | 2 +- .../{ui/Avatar.tsx => components/avatar.tsx} | 2 +- .../back-button.tsx} | 4 ++-- .../Boundary.tsx => components/boundary.tsx} | 8 +++---- .../{ui/Button.tsx => components/button.tsx} | 2 +- .../change-password-form.tsx} | 18 +++++++-------- .../Checkbox.tsx => components/checkbox.tsx} | 0 .../choose-second-factor-to-setup.tsx} | 4 ++-- .../choose-second-factor.tsx} | 4 ++-- .../copy-to-clipboard.tsx} | 2 +- .../default-tags.tsx} | 0 .../dynamic-theme.tsx} | 10 ++++---- .../external-link.tsx} | 3 ++- .../global-nav.tsx} | 6 ++--- .../idp-signin.tsx} | 6 ++--- .../src/{ui => components}/idps/classes.tsx | 4 ++-- .../idps/sign-in-with-apple.tsx} | 0 .../idps/sign-in-with-azure-ad.tsx} | 4 ++-- .../idps/sign-in-with-generic.tsx} | 0 .../idps/sign-in-with-github.tsx} | 0 .../idps/sign-in-with-gitlab.test.tsx} | 2 +- .../idps/sign-in-with-gitlab.tsx} | 0 .../idps/sign-in-with-google.test.tsx} | 2 +- .../idps/sign-in-with-google.tsx} | 0 .../{ui/Input.tsx => components/input.tsx} | 2 +- .../layout-providers.tsx} | 3 ++- .../LoginOTP.tsx => components/login-otp.tsx} | 12 +++++----- .../login-passkey.tsx} | 12 +++++----- .../src/{ui/Logo.tsx => components/logo.tsx} | 0 .../mobile-nav-toggle.tsx} | 23 ++++++++++++------- .../password-complexity.test.tsx} | 2 +- .../password-complexity.tsx} | 4 ++-- .../password-form.tsx} | 12 +++++----- .../privacy-policy-checkboxes.tsx} | 2 +- .../register-form-without-password.tsx} | 19 +++++++-------- .../register-passkey.tsx} | 12 +++++----- .../register-u2f.tsx} | 12 +++++----- .../self-service-menu.tsx} | 2 +- .../session-item.tsx} | 4 ++-- .../sessions-list.tsx} | 6 ++--- .../set-password-form.tsx} | 18 +++++++-------- .../sign-in-with-idp.tsx} | 22 +++++++++--------- .../skeleton-card.tsx} | 2 +- .../Spinner.tsx => components/spinner.tsx} | 0 .../state-badge.tsx} | 2 +- .../TabGroup.tsx => components/tab-group.tsx} | 2 +- .../src/{ui/Tab.tsx => components/tab.tsx} | 4 ++-- .../theme-provider.tsx} | 2 +- .../theme-wrapper.tsx} | 6 ++--- .../{ui/Theme.tsx => components/theme.tsx} | 4 +--- .../totp-register.tsx} | 12 +++++----- .../user-avatar.tsx} | 4 ++-- .../username-form.tsx} | 12 +++++----- .../verify-email-form.tsx} | 10 ++++---- .../zitadel-logo-dark.tsx} | 0 .../zitadel-logo-light.tsx} | 0 .../zitadel-logo.tsx} | 0 apps/login/src/{utils => helpers}/base64.ts | 0 apps/login/src/{utils => helpers}/colors.ts | 0 .../src/{utils => helpers}/validators.ts | 0 packages/eslint-config-zitadel/index.js | 5 ---- 85 files changed, 257 insertions(+), 254 deletions(-) rename apps/login/src/{ui/AddressBar.tsx => components/address-bar.tsx} (93%) rename apps/login/src/{ui/Alert.tsx => components/alert.tsx} (88%) rename apps/login/src/{ui/AuthMethods.tsx => components/auth-methods.tsx} (98%) rename apps/login/src/{ui/AuthenticationMethodRadio.tsx => components/authentication-method-radio.tsx} (98%) rename apps/login/src/{ui/Avatar.tsx => components/avatar.tsx} (97%) rename apps/login/src/{ui/BackButton.tsx => components/back-button.tsx} (73%) rename apps/login/src/{ui/Boundary.tsx => components/boundary.tsx} (95%) rename apps/login/src/{ui/Button.tsx => components/button.tsx} (99%) rename apps/login/src/{ui/ChangePasswordForm.tsx => components/change-password-form.tsx} (92%) rename apps/login/src/{ui/Checkbox.tsx => components/checkbox.tsx} (100%) rename apps/login/src/{ui/ChooseSecondFactorToSetup.tsx => components/choose-second-factor-to-setup.tsx} (95%) rename apps/login/src/{ui/ChooseSecondFactor.tsx => components/choose-second-factor.tsx} (93%) rename apps/login/src/{ui/CopyToClipboard.tsx => components/copy-to-clipboard.tsx} (93%) rename apps/login/src/{ui/DefaultTags.tsx => components/default-tags.tsx} (100%) rename apps/login/src/{ui/DynamicTheme.tsx => components/dynamic-theme.tsx} (85%) rename apps/login/src/{ui/ExternalLink.tsx => components/external-link.tsx} (87%) rename apps/login/src/{ui/GlobalNav.tsx => components/global-nav.tsx} (96%) rename apps/login/src/{ui/IdpSignin.tsx => components/idp-signin.tsx} (94%) rename apps/login/src/{ui => components}/idps/classes.tsx (86%) rename apps/login/src/{ui/idps/SignInWithApple.tsx => components/idps/sign-in-with-apple.tsx} (100%) rename apps/login/src/{ui/idps/SignInWithAzureAD.tsx => components/idps/sign-in-with-azure-ad.tsx} (91%) rename apps/login/src/{ui/idps/SignInWithGeneric.tsx => components/idps/sign-in-with-generic.tsx} (100%) rename apps/login/src/{ui/idps/SignInWithGithub.tsx => components/idps/sign-in-with-github.tsx} (100%) rename apps/login/src/{ui/idps/SignInWithGitlab.test.tsx => components/idps/sign-in-with-gitlab.test.tsx} (92%) rename apps/login/src/{ui/idps/SignInWithGitlab.tsx => components/idps/sign-in-with-gitlab.tsx} (100%) rename apps/login/src/{ui/idps/SignInWithGoogle.test.tsx => components/idps/sign-in-with-google.test.tsx} (92%) rename apps/login/src/{ui/idps/SignInWithGoogle.tsx => components/idps/sign-in-with-google.tsx} (100%) rename apps/login/src/{ui/Input.tsx => components/input.tsx} (99%) rename apps/login/src/{ui/LayoutProviders.tsx => components/layout-providers.tsx} (84%) rename apps/login/src/{ui/LoginOTP.tsx => components/login-otp.tsx} (96%) rename apps/login/src/{ui/LoginPasskey.tsx => components/login-passkey.tsx} (96%) rename apps/login/src/{ui/Logo.tsx => components/logo.tsx} (100%) rename apps/login/src/{ui/MobileNavToggle.tsx => components/mobile-nav-toggle.tsx} (75%) rename apps/login/src/{ui/PasswordComplexity.test.tsx => components/password-complexity.test.tsx} (97%) rename apps/login/src/{ui/PasswordComplexity.tsx => components/password-complexity.tsx} (97%) rename apps/login/src/{ui/PasswordForm.tsx => components/password-form.tsx} (96%) rename apps/login/src/{ui/PrivacyPolicyCheckboxes.tsx => components/privacy-policy-checkboxes.tsx} (98%) rename apps/login/src/{ui/RegisterFormWithoutPassword.tsx => components/register-form-without-password.tsx} (92%) rename apps/login/src/{ui/RegisterPasskey.tsx => components/register-passkey.tsx} (94%) rename apps/login/src/{ui/RegisterU2F.tsx => components/register-u2f.tsx} (95%) rename apps/login/src/{ui/SelfServiceMenu.tsx => components/self-service-menu.tsx} (92%) rename apps/login/src/{ui/SessionItem.tsx => components/session-item.tsx} (98%) rename apps/login/src/{ui/SessionsList.tsx => components/sessions-list.tsx} (84%) rename apps/login/src/{ui/SetPasswordForm.tsx => components/set-password-form.tsx} (94%) rename apps/login/src/{ui/SignInWithIDP.tsx => components/sign-in-with-idp.tsx} (90%) rename apps/login/src/{ui/SkeletonCard.tsx => components/skeleton-card.tsx} (95%) rename apps/login/src/{ui/Spinner.tsx => components/spinner.tsx} (100%) rename apps/login/src/{ui/StateBadge.tsx => components/state-badge.tsx} (98%) rename apps/login/src/{ui/TabGroup.tsx => components/tab-group.tsx} (89%) rename apps/login/src/{ui/Tab.tsx => components/tab.tsx} (90%) rename apps/login/src/{ui/ThemeProvider.tsx => components/theme-provider.tsx} (78%) rename apps/login/src/{ui/ThemeWrapper.tsx => components/theme-wrapper.tsx} (77%) rename apps/login/src/{ui/Theme.tsx => components/theme.tsx} (96%) rename apps/login/src/{ui/TOTPRegister.tsx => components/totp-register.tsx} (94%) rename apps/login/src/{ui/UserAvatar.tsx => components/user-avatar.tsx} (95%) rename apps/login/src/{ui/UsernameForm.tsx => components/username-form.tsx} (93%) rename apps/login/src/{ui/VerifyEmailForm.tsx => components/verify-email-form.tsx} (94%) rename apps/login/src/{ui/ZitadelLogoDark.tsx => components/zitadel-logo-dark.tsx} (100%) rename apps/login/src/{ui/ZitadelLogoLight.tsx => components/zitadel-logo-light.tsx} (100%) rename apps/login/src/{ui/ZitadelLogo.tsx => components/zitadel-logo.tsx} (100%) rename apps/login/src/{utils => helpers}/base64.ts (100%) rename apps/login/src/{utils => helpers}/colors.ts (100%) rename apps/login/src/{utils => helpers}/validators.ts (100%) diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index 8ba2e32d89..fc50d2846b 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -1,7 +1,7 @@ +import { DynamicTheme } from "@/components/dynamic-theme"; +import { SessionsList } from "@/components/sessions-list"; import { getAllSessionCookieIds } from "@/lib/cookies"; import { getBrandingSettings, listSessions } from "@/lib/zitadel"; -import DynamicTheme from "@/ui/DynamicTheme"; -import SessionsList from "@/ui/SessionsList"; import { UserPlusIcon } from "@heroicons/react/24/outline"; import Link from "next/link"; diff --git a/apps/login/src/app/(login)/error.tsx b/apps/login/src/app/(login)/error.tsx index 47b2e505ce..0053f6ed71 100644 --- a/apps/login/src/app/(login)/error.tsx +++ b/apps/login/src/app/(login)/error.tsx @@ -1,11 +1,11 @@ "use client"; -import { Boundary } from "@/ui/Boundary"; -import { Button } from "@/ui/Button"; -import React from "react"; +import { Boundary } from "@/components/boundary"; +import { Button } from "@/components/button"; +import { useEffect } from "react"; export default function Error({ error, reset }: any) { - React.useEffect(() => { + useEffect(() => { console.log("logging error:", error); }, [error]); diff --git a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx index 2be2c54b01..237feebecc 100644 --- a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -1,5 +1,5 @@ +import { DynamicTheme } from "@/components/dynamic-theme"; import { getBrandingSettings } from "@/lib/zitadel"; -import DynamicTheme from "@/ui/DynamicTheme"; import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; // This configuration shows the given name in the respective IDP button as fallback diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index e8c4f9a2cc..1c8039def6 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -1,3 +1,6 @@ +import { Alert, AlertType } from "@/components/alert"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { IdpSignin } from "@/components/idp-signin"; import { idpTypeToIdentityProviderType, PROVIDER_MAPPING } from "@/lib/idp"; import { addIDPLink, @@ -7,9 +10,6 @@ import { listUsers, retrieveIDPIntent, } from "@/lib/zitadel"; -import Alert, { AlertType } from "@/ui/Alert"; -import DynamicTheme from "@/ui/DynamicTheme"; -import IdpSignin from "@/ui/IdpSignin"; import { AutoLinkingOption } from "@zitadel/proto/zitadel/idp/v2/idp_pb"; export default async function Page({ diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index a41fad781d..8a2d65f380 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -1,6 +1,6 @@ +import { DynamicTheme } from "@/components/dynamic-theme"; +import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { getBrandingSettings, settingsService } from "@/lib/zitadel"; -import DynamicTheme from "@/ui/DynamicTheme"; -import { SignInWithIDP } from "@/ui/SignInWithIDP"; import { makeReqCtx } from "@zitadel/client/v2"; function getIdentityProviders(orgId?: string) { @@ -36,12 +36,12 @@ export default async function Page({

    diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 7c87474cd1..e1847cbcf8 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -1,12 +1,12 @@ +import { DynamicTheme } from "@/components/dynamic-theme"; +import { SignInWithIdp } from "@/components/sign-in-with-idp"; +import { UsernameForm } from "@/components/username-form"; import { getBrandingSettings, getLegalAndSupportSettings, getLoginSettings, settingsService, } from "@/lib/zitadel"; -import DynamicTheme from "@/ui/DynamicTheme"; -import { SignInWithIDP } from "@/ui/SignInWithIDP"; -import UsernameForm from "@/ui/UsernameForm"; import { makeReqCtx } from "@zitadel/client/v2"; function getIdentityProviders(orgId?: string) { @@ -52,12 +52,12 @@ export default async function Page({ allowRegister={!!loginSettings?.allowRegister} > {legal && identityProviders && process.env.ZITADEL_API_URL && ( - + > )}
    diff --git a/apps/login/src/app/(login)/me/change-password/page.tsx b/apps/login/src/app/(login)/me/change-password/page.tsx index febd9e0b99..d87f184ac9 100644 --- a/apps/login/src/app/(login)/me/change-password/page.tsx +++ b/apps/login/src/app/(login)/me/change-password/page.tsx @@ -1,13 +1,13 @@ +import { Alert } from "@/components/alert"; +import { ChangePasswordForm } from "@/components/change-password-form"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { getBrandingSettings, getPasswordComplexitySettings, getSession, } from "@/lib/zitadel"; -import Alert from "@/ui/Alert"; -import ChangePasswordForm from "@/ui/ChangePasswordForm"; -import DynamicTheme from "@/ui/DynamicTheme"; -import UserAvatar from "@/ui/UserAvatar"; export default async function Page({ searchParams, diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index f469fbf38b..4186311fd3 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -1,3 +1,8 @@ +import { Alert } from "@/components/alert"; +import { BackButton } from "@/components/back-button"; +import { ChooseSecondFactor } from "@/components/choose-second-factor"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { loadMostRecentSession } from "@/lib/session"; import { @@ -5,11 +10,6 @@ import { getSession, listAuthenticationMethodTypes, } from "@/lib/zitadel"; -import Alert from "@/ui/Alert"; -import BackButton from "@/ui/BackButton"; -import ChooseSecondFactor from "@/ui/ChooseSecondFactor"; -import DynamicTheme from "@/ui/DynamicTheme"; -import UserAvatar from "@/ui/UserAvatar"; export default async function Page({ searchParams, diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index 1e1c13fc9a..5696c724bd 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -1,3 +1,8 @@ +import { Alert } from "@/components/alert"; +import { BackButton } from "@/components/back-button"; +import { ChooseSecondFactorToSetup } from "@/components/choose-second-factor-to-setup"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { loadMostRecentSession } from "@/lib/session"; import { @@ -7,11 +12,6 @@ import { getUserByID, listAuthenticationMethodTypes, } from "@/lib/zitadel"; -import Alert from "@/ui/Alert"; -import BackButton from "@/ui/BackButton"; -import ChooseSecondFactorToSetup from "@/ui/ChooseSecondFactorToSetup"; -import DynamicTheme from "@/ui/DynamicTheme"; -import UserAvatar from "@/ui/UserAvatar"; import { Timestamp, timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 55af60f66b..db1efa2a00 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -1,9 +1,9 @@ +import { Alert } from "@/components/alert"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { LoginOTP } from "@/components/login-otp"; +import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; -import Alert from "@/ui/Alert"; -import DynamicTheme from "@/ui/DynamicTheme"; -import LoginOTP from "@/ui/LoginOTP"; -import UserAvatar from "@/ui/UserAvatar"; export default async function Page({ searchParams, diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index ee657c5737..3e1b4dca8b 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -1,3 +1,9 @@ +import { Alert } from "@/components/alert"; +import { BackButton } from "@/components/back-button"; +import { Button, ButtonVariants } from "@/components/button"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { TotpRegister } from "@/components/totp-register"; +import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; import { addOTPEmail, @@ -5,12 +11,6 @@ import { getBrandingSettings, registerTOTP, } from "@/lib/zitadel"; -import Alert from "@/ui/Alert"; -import BackButton from "@/ui/BackButton"; -import { Button, ButtonVariants } from "@/ui/Button"; -import DynamicTheme from "@/ui/DynamicTheme"; -import TOTPRegister from "@/ui/TOTPRegister"; -import UserAvatar from "@/ui/UserAvatar"; import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import Link from "next/link"; import { redirect } from "next/navigation"; @@ -129,7 +129,7 @@ export default async function Page({ Scan the QR Code or navigate to the URL manually.

    - + >
    {" "} ) : ( diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index eec5389f70..6cb3e43b84 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -1,10 +1,10 @@ +import { Alert } from "@/components/alert"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { LoginPasskey } from "@/components/login-passkey"; +import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; -import Alert from "@/ui/Alert"; -import DynamicTheme from "@/ui/DynamicTheme"; -import LoginPasskey from "@/ui/LoginPasskey"; -import UserAvatar from "@/ui/UserAvatar"; const title = "Authenticate with a passkey"; const description = diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx index 15aaa6436a..140f1d74e2 100644 --- a/apps/login/src/app/(login)/passkey/set/page.tsx +++ b/apps/login/src/app/(login)/passkey/set/page.tsx @@ -1,9 +1,9 @@ +import { Alert, AlertType } from "@/components/alert"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { RegisterPasskey } from "@/components/register-passkey"; +import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; -import Alert, { AlertType } from "@/ui/Alert"; -import DynamicTheme from "@/ui/DynamicTheme"; -import RegisterPasskey from "@/ui/RegisterPasskey"; -import UserAvatar from "@/ui/UserAvatar"; export default async function Page({ searchParams, diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 81e6ba240a..5da2bd6016 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -1,9 +1,9 @@ +import { Alert } from "@/components/alert"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { PasswordForm } from "@/components/password-form"; +import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; -import Alert from "@/ui/Alert"; -import DynamicTheme from "@/ui/DynamicTheme"; -import PasswordForm from "@/ui/PasswordForm"; -import UserAvatar from "@/ui/UserAvatar"; import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; export default async function Page({ diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 700173e9dc..ab8a8eddfb 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -1,11 +1,11 @@ +import { DynamicTheme } from "@/components/dynamic-theme"; +import { RegisterFormWithoutPassword } from "@/components/register-form-without-password"; +import { SetPasswordForm } from "@/components/set-password-form"; import { getBrandingSettings, getLegalAndSupportSettings, getPasswordComplexitySettings, } from "@/lib/zitadel"; -import DynamicTheme from "@/ui/DynamicTheme"; -import RegisterFormWithoutPassword from "@/ui/RegisterFormWithoutPassword"; -import SetPasswordForm from "@/ui/SetPasswordForm"; export default async function Page({ searchParams, diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index 13715c2c02..da65d60ecc 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -1,8 +1,8 @@ +import { DynamicTheme } from "@/components/dynamic-theme"; +import { SelfServiceMenu } from "@/components/self-service-menu"; +import { UserAvatar } from "@/components/user-avatar"; import { getMostRecentCookieWithLoginname } from "@/lib/cookies"; import { createCallback, getBrandingSettings, getSession } from "@/lib/zitadel"; -import DynamicTheme from "@/ui/DynamicTheme"; -import SelfServiceMenu from "@/ui/SelfServiceMenu"; -import UserAvatar from "@/ui/UserAvatar"; import { create } from "@zitadel/client"; import { CreateCallbackRequestSchema, diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index 0390f1c50f..77a1e6e9a2 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -1,10 +1,10 @@ +import { Alert } from "@/components/alert"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { LoginPasskey } from "@/components/login-passkey"; +import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; -import Alert from "@/ui/Alert"; -import DynamicTheme from "@/ui/DynamicTheme"; -import LoginPasskey from "@/ui/LoginPasskey"; -import UserAvatar from "@/ui/UserAvatar"; export default async function Page({ searchParams, diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index ccd99dcb1b..97120424e1 100644 --- a/apps/login/src/app/(login)/u2f/set/page.tsx +++ b/apps/login/src/app/(login)/u2f/set/page.tsx @@ -1,9 +1,9 @@ +import { Alert } from "@/components/alert"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { RegisterU2f } from "@/components/register-u2f"; +import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; -import Alert from "@/ui/Alert"; -import DynamicTheme from "@/ui/DynamicTheme"; -import RegisterU2F from "@/ui/RegisterU2F"; -import UserAvatar from "@/ui/UserAvatar"; export default async function Page({ searchParams, @@ -48,7 +48,7 @@ export default async function Page({ )} {sessionFactors?.id && ( - { const response = await listSessions( ids.filter((id: string | undefined) => !!id), diff --git a/apps/login/src/ui/AddressBar.tsx b/apps/login/src/components/address-bar.tsx similarity index 93% rename from apps/login/src/ui/AddressBar.tsx rename to apps/login/src/components/address-bar.tsx index e749bcb67a..7e7bda6bd0 100644 --- a/apps/login/src/ui/AddressBar.tsx +++ b/apps/login/src/components/address-bar.tsx @@ -1,7 +1,7 @@ "use client"; import { usePathname } from "next/navigation"; -import React from "react"; +import { Fragment } from "react"; type Props = { domain: string; @@ -39,7 +39,7 @@ export function AddressBar({ domain }: Props) { .filter((s) => !!s) .map((segment) => { return ( - + / - + ); })} diff --git a/apps/login/src/ui/Alert.tsx b/apps/login/src/components/alert.tsx similarity index 88% rename from apps/login/src/ui/Alert.tsx rename to apps/login/src/components/alert.tsx index b6a093bab0..417e67934e 100644 --- a/apps/login/src/ui/Alert.tsx +++ b/apps/login/src/components/alert.tsx @@ -2,10 +2,11 @@ import { ExclamationTriangleIcon, InformationCircleIcon, } from "@heroicons/react/24/outline"; -import clsx from "clsx"; +import { clsx } from "clsx"; +import { ReactNode } from "react"; type Props = { - children: React.ReactNode; + children: ReactNode; type?: AlertType; }; @@ -21,7 +22,7 @@ const red = const neutral = "border-divider-light dark:border-divider-dark bg-black/5 text-gray-600 dark:bg-white/10 dark:text-gray-200"; -export default function Alert({ children, type = AlertType.ALERT }: Props) { +export function Alert({ children, type = AlertType.ALERT }: Props) { return (
    clsx( diff --git a/apps/login/src/ui/AuthenticationMethodRadio.tsx b/apps/login/src/components/authentication-method-radio.tsx similarity index 98% rename from apps/login/src/ui/AuthenticationMethodRadio.tsx rename to apps/login/src/components/authentication-method-radio.tsx index 1eb12ce668..d5c52068ec 100644 --- a/apps/login/src/ui/AuthenticationMethodRadio.tsx +++ b/apps/login/src/components/authentication-method-radio.tsx @@ -13,7 +13,7 @@ export const methods = [ }, ]; -export default function AuthenticationMethodRadio({ +export function AuthenticationMethodRadio({ selected, selectionChanged, }: { diff --git a/apps/login/src/ui/Avatar.tsx b/apps/login/src/components/avatar.tsx similarity index 97% rename from apps/login/src/ui/Avatar.tsx rename to apps/login/src/components/avatar.tsx index e42e6017af..3f340e09b7 100644 --- a/apps/login/src/ui/Avatar.tsx +++ b/apps/login/src/components/avatar.tsx @@ -1,6 +1,6 @@ "use client"; -import { ColorShade, getColorHash } from "@/utils/colors"; +import { ColorShade, getColorHash } from "@/helpers/colors"; import { useTheme } from "next-themes"; import Image from "next/image"; diff --git a/apps/login/src/ui/BackButton.tsx b/apps/login/src/components/back-button.tsx similarity index 73% rename from apps/login/src/ui/BackButton.tsx rename to apps/login/src/components/back-button.tsx index b2f04ad584..5697877bd5 100644 --- a/apps/login/src/ui/BackButton.tsx +++ b/apps/login/src/components/back-button.tsx @@ -1,9 +1,9 @@ "use client"; import { useRouter } from "next/navigation"; -import { Button, ButtonVariants } from "./Button"; +import { Button, ButtonVariants } from "./button"; -export default function BackButton() { +export function BackButton() { const router = useRouter(); return ( - ), -); - -SignInWithApple.displayName = "SignInWithApple"; + + ); +}); diff --git a/apps/login/src/components/idps/sign-in-with-azure-ad.tsx b/apps/login/src/components/idps/sign-in-with-azure-ad.tsx index 51d8a47db8..933604d23e 100644 --- a/apps/login/src/components/idps/sign-in-with-azure-ad.tsx +++ b/apps/login/src/components/idps/sign-in-with-azure-ad.tsx @@ -1,19 +1,15 @@ "use client"; -import { ReactNode, forwardRef } from "react"; -import { IdpButtonClasses, SignInWithIdentityProviderProps } from "./classes"; +import { forwardRef } from "react"; +import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; export const SignInWithAzureAd = forwardRef< HTMLButtonElement, SignInWithIdentityProviderProps ->( - ({ children, className = "", name = "", ...props }, ref): ReactNode => ( - - ), -); - -SignInWithAzureAd.displayName = "SignInWithAzureAD"; + + ); +}); diff --git a/apps/login/src/components/idps/sign-in-with-generic.tsx b/apps/login/src/components/idps/sign-in-with-generic.tsx index 1ebc2d9489..ab8f2f99be 100644 --- a/apps/login/src/components/idps/sign-in-with-generic.tsx +++ b/apps/login/src/components/idps/sign-in-with-generic.tsx @@ -1,25 +1,21 @@ "use client"; -import { ReactNode, forwardRef } from "react"; -import { IdpButtonClasses, SignInWithIdentityProviderProps } from "./classes"; +import { forwardRef } from "react"; +import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; export const SignInWithGeneric = forwardRef< HTMLButtonElement, SignInWithIdentityProviderProps ->( - ( - { children, className = "h-[50px] pl-20", name = "", ...props }, - ref, - ): ReactNode => ( - - ), -); - -SignInWithGeneric.displayName = "SignInWithGeneric"; +>(function SignInWithGeneric(props, ref) { + const { + children, + name = "", + className = "h-[50px] pl-20", + ...restProps + } = props; + return ( + + {children ? children : {name}} + + ); +}); diff --git a/apps/login/src/components/idps/sign-in-with-github.tsx b/apps/login/src/components/idps/sign-in-with-github.tsx index c0d1130b60..11b9078616 100644 --- a/apps/login/src/components/idps/sign-in-with-github.tsx +++ b/apps/login/src/components/idps/sign-in-with-github.tsx @@ -1,27 +1,21 @@ "use client"; -import { ReactNode, forwardRef } from "react"; -import { IdpButtonClasses, SignInWithIdentityProviderProps } from "./classes"; +import { forwardRef } from "react"; +import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; export const SignInWithGithub = forwardRef< HTMLButtonElement, SignInWithIdentityProviderProps ->( - ({ children, className = "", name = "", ...props }, ref): ReactNode => ( - - ), -); - -SignInWithGithub.displayName = "SignInWithGithub"; + + ); +}); diff --git a/apps/login/src/components/idps/sign-in-with-gitlab.tsx b/apps/login/src/components/idps/sign-in-with-gitlab.tsx index 4fcd5bf049..c685cea2ab 100644 --- a/apps/login/src/components/idps/sign-in-with-gitlab.tsx +++ b/apps/login/src/components/idps/sign-in-with-gitlab.tsx @@ -1,19 +1,15 @@ "use client"; -import { ReactNode, forwardRef } from "react"; -import { IdpButtonClasses, SignInWithIdentityProviderProps } from "./classes"; +import { forwardRef } from "react"; +import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; export const SignInWithGitlab = forwardRef< HTMLButtonElement, SignInWithIdentityProviderProps ->( - ({ children, className = "", name = "", ...props }, ref): ReactNode => ( - - ), -); - -SignInWithGitlab.displayName = "SignInWithGitlab"; + + ); +}); diff --git a/apps/login/src/components/idps/sign-in-with-google.tsx b/apps/login/src/components/idps/sign-in-with-google.tsx index 94caa3759a..136e85e62d 100644 --- a/apps/login/src/components/idps/sign-in-with-google.tsx +++ b/apps/login/src/components/idps/sign-in-with-google.tsx @@ -1,19 +1,15 @@ "use client"; -import { ReactNode, forwardRef } from "react"; -import { IdpButtonClasses, SignInWithIdentityProviderProps } from "./classes"; +import { forwardRef } from "react"; +import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; export const SignInWithGoogle = forwardRef< HTMLButtonElement, SignInWithIdentityProviderProps ->( - ({ children, className = "", name = "", ...props }, ref): ReactNode => ( - - ), -); - -SignInWithGoogle.displayName = "SignInWithGoogle"; + + ); +}); From a90220a843b8186986754345bc8f8eb72366e8dd Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 2 Oct 2024 22:35:52 +0200 Subject: [PATCH 235/640] update next --- apps/login/next-env.d.ts | 2 +- apps/login/package.json | 2 +- pnpm-lock.yaml | 147 +++++++++++++++++++++++++-------------- 3 files changed, 95 insertions(+), 56 deletions(-) diff --git a/apps/login/next-env.d.ts b/apps/login/next-env.d.ts index 4f11a03dc6..40c3d68096 100755 --- a/apps/login/next-env.d.ts +++ b/apps/login/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/apps/login/package.json b/apps/login/package.json index 071fd9bd01..0670d5bf49 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -43,7 +43,7 @@ "clsx": "1.2.1", "copy-to-clipboard": "^3.3.3", "moment": "^2.29.4", - "next": "14.2.5", + "next": "14.2.10", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", "qrcode.react": "^3.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f748c0973f..f6435a13f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -61,7 +61,7 @@ importers: version: 0.5.7(tailwindcss@3.4.12) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) + version: 1.3.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -81,11 +81,11 @@ importers: specifier: ^2.29.4 version: 2.30.1 next: - specifier: 14.2.5 - version: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + specifier: 14.2.10 + version: 14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.2.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nice-grpc: specifier: 2.0.1 version: 2.0.1 @@ -304,6 +304,10 @@ packages: resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.25.7': + resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.25.4': resolution: {integrity: sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==} engines: {node: '>=6.9.0'} @@ -346,6 +350,10 @@ packages: resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.25.7': + resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.24.8': resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} engines: {node: '>=6.9.0'} @@ -358,6 +366,10 @@ packages: resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} + '@babel/highlight@7.25.7': + resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.25.6': resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} engines: {node: '>=6.0.0'} @@ -379,6 +391,10 @@ packages: resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.25.7': + resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==} + engines: {node: '>=6.9.0'} + '@babel/template@7.25.0': resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} engines: {node: '>=6.9.0'} @@ -898,62 +914,62 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true - '@next/env@14.2.5': - resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} + '@next/env@14.2.10': + resolution: {integrity: sha512-dZIu93Bf5LUtluBXIv4woQw2cZVZ2DJTjax5/5DOs3lzEOeKLy7GxRSr4caK9/SCPdaW6bCgpye6+n4Dh9oJPw==} '@next/eslint-plugin-next@14.2.7': resolution: {integrity: sha512-+7xh142AdhZGjY9/L0iFo7mqRBMJHe+q+uOL+hto1Lfo9DeWCGcR6no4StlFbVSVcA6fQLKEX6y6qhMsSKbgNQ==} - '@next/swc-darwin-arm64@14.2.5': - resolution: {integrity: sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==} + '@next/swc-darwin-arm64@14.2.10': + resolution: {integrity: sha512-V3z10NV+cvMAfxQUMhKgfQnPbjw+Ew3cnr64b0lr8MDiBJs3eLnM6RpGC46nhfMZsiXgQngCJKWGTC/yDcgrDQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.5': - resolution: {integrity: sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==} + '@next/swc-darwin-x64@14.2.10': + resolution: {integrity: sha512-Y0TC+FXbFUQ2MQgimJ/7Ina2mXIKhE7F+GUe1SgnzRmwFY3hX2z8nyVCxE82I2RicspdkZnSWMn4oTjIKz4uzA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.5': - resolution: {integrity: sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==} + '@next/swc-linux-arm64-gnu@14.2.10': + resolution: {integrity: sha512-ZfQ7yOy5zyskSj9rFpa0Yd7gkrBnJTkYVSya95hX3zeBG9E55Z6OTNPn1j2BTFWvOVVj65C3T+qsjOyVI9DQpA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.5': - resolution: {integrity: sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==} + '@next/swc-linux-arm64-musl@14.2.10': + resolution: {integrity: sha512-n2i5o3y2jpBfXFRxDREr342BGIQCJbdAUi/K4q6Env3aSx8erM9VuKXHw5KNROK9ejFSPf0LhoSkU/ZiNdacpQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.5': - resolution: {integrity: sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==} + '@next/swc-linux-x64-gnu@14.2.10': + resolution: {integrity: sha512-GXvajAWh2woTT0GKEDlkVhFNxhJS/XdDmrVHrPOA83pLzlGPQnixqxD8u3bBB9oATBKB//5e4vpACnx5Vaxdqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.5': - resolution: {integrity: sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==} + '@next/swc-linux-x64-musl@14.2.10': + resolution: {integrity: sha512-opFFN5B0SnO+HTz4Wq4HaylXGFV+iHrVxd3YvREUX9K+xfc4ePbRrxqOuPOFjtSuiVouwe6uLeDtabjEIbkmDA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.5': - resolution: {integrity: sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==} + '@next/swc-win32-arm64-msvc@14.2.10': + resolution: {integrity: sha512-9NUzZuR8WiXTvv+EiU/MXdcQ1XUvFixbLIMNQiVHuzs7ZIFrJDLJDaOF1KaqttoTujpcxljM/RNAOmw1GhPPQQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.5': - resolution: {integrity: sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==} + '@next/swc-win32-ia32-msvc@14.2.10': + resolution: {integrity: sha512-fr3aEbSd1GeW3YUMBkWAu4hcdjZ6g4NBl1uku4gAn661tcxd1bHs1THWYzdsbTRLcCKLjrDZlNp6j2HTfrw+Bg==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@next/swc-win32-x64-msvc@14.2.5': - resolution: {integrity: sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==} + '@next/swc-win32-x64-msvc@14.2.10': + resolution: {integrity: sha512-UjeVoRGKNL2zfbcQ6fscmgjBAS/inHBh63mjIlfPg/NG8Yn2ztqylXt5qilYb6hoHIwaU2ogHknHWWmahJjgZQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1579,6 +1595,9 @@ packages: caniuse-lite@1.0.30001654: resolution: {integrity: sha512-wLJc602fW0OdrUR+PqsBUH3dgrjDcT+mWs/Kw86zPvgjiqOiI2TXMkBFK4KihYzZclmJxrFwgYhZDSEogFai/g==} + caniuse-lite@1.0.30001666: + resolution: {integrity: sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==} + case-anything@2.1.13: resolution: {integrity: sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==} engines: {node: '>=12.13'} @@ -3090,8 +3109,8 @@ packages: react: '*' react-dom: '*' - next@14.2.5: - resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} + next@14.2.10: + resolution: {integrity: sha512-sDDExXnh33cY3RkS9JuFEKaS4HmlWmDKP1VJioucCG6z5KuA008DPsDZOzi8UfqEk3Ii+2NCQSJrfbEWtZZfww==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -4520,6 +4539,11 @@ snapshots: '@babel/highlight': 7.24.7 picocolors: 1.1.0 + '@babel/code-frame@7.25.7': + dependencies: + '@babel/highlight': 7.25.7 + picocolors: 1.1.0 + '@babel/compat-data@7.25.4': {} '@babel/core@7.25.2': @@ -4587,6 +4611,8 @@ snapshots: '@babel/helper-validator-identifier@7.24.7': {} + '@babel/helper-validator-identifier@7.25.7': {} + '@babel/helper-validator-option@7.24.8': {} '@babel/helpers@7.25.6': @@ -4601,6 +4627,13 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.0 + '@babel/highlight@7.25.7': + dependencies: + '@babel/helper-validator-identifier': 7.25.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.1.0 + '@babel/parser@7.25.6': dependencies: '@babel/types': 7.25.6 @@ -4619,6 +4652,10 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@babel/runtime@7.25.7': + dependencies: + regenerator-runtime: 0.14.1 + '@babel/template@7.25.0': dependencies: '@babel/code-frame': 7.24.7 @@ -5128,37 +5165,37 @@ snapshots: - encoding - supports-color - '@next/env@14.2.5': {} + '@next/env@14.2.10': {} '@next/eslint-plugin-next@14.2.7': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@14.2.5': + '@next/swc-darwin-arm64@14.2.10': optional: true - '@next/swc-darwin-x64@14.2.5': + '@next/swc-darwin-x64@14.2.10': optional: true - '@next/swc-linux-arm64-gnu@14.2.5': + '@next/swc-linux-arm64-gnu@14.2.10': optional: true - '@next/swc-linux-arm64-musl@14.2.5': + '@next/swc-linux-arm64-musl@14.2.10': optional: true - '@next/swc-linux-x64-gnu@14.2.5': + '@next/swc-linux-x64-gnu@14.2.10': optional: true - '@next/swc-linux-x64-musl@14.2.5': + '@next/swc-linux-x64-musl@14.2.10': optional: true - '@next/swc-win32-arm64-msvc@14.2.5': + '@next/swc-win32-arm64-msvc@14.2.10': optional: true - '@next/swc-win32-ia32-msvc@14.2.5': + '@next/swc-win32-ia32-msvc@14.2.10': optional: true - '@next/swc-win32-x64-msvc@14.2.5': + '@next/swc-win32-x64-msvc@14.2.10': optional: true '@nodelib/fs.scandir@2.1.5': @@ -5286,8 +5323,8 @@ snapshots: '@testing-library/dom@10.4.0': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/runtime': 7.25.6 + '@babel/code-frame': 7.25.7 + '@babel/runtime': 7.25.7 '@types/aria-query': 5.0.4 aria-query: 5.3.0 chalk: 4.1.2 @@ -5422,11 +5459,11 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/analytics@1.3.1(next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1)': + '@vercel/analytics@1.3.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next: 14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) react: 18.3.1 '@vercel/git-hooks@1.0.0': {} @@ -5779,6 +5816,8 @@ snapshots: caniuse-lite@1.0.30001654: {} + caniuse-lite@1.0.30001666: {} + case-anything@2.1.13: {} caseless@0.12.0: {} @@ -7540,33 +7579,33 @@ snapshots: natural-compare@1.4.0: {} - next-themes@0.2.1(next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-themes@0.2.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next: 14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1): + next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1): dependencies: - '@next/env': 14.2.5 + '@next/env': 14.2.10 '@swc/helpers': 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001654 + caniuse-lite: 1.0.30001666 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.5 - '@next/swc-darwin-x64': 14.2.5 - '@next/swc-linux-arm64-gnu': 14.2.5 - '@next/swc-linux-arm64-musl': 14.2.5 - '@next/swc-linux-x64-gnu': 14.2.5 - '@next/swc-linux-x64-musl': 14.2.5 - '@next/swc-win32-arm64-msvc': 14.2.5 - '@next/swc-win32-ia32-msvc': 14.2.5 - '@next/swc-win32-x64-msvc': 14.2.5 + '@next/swc-darwin-arm64': 14.2.10 + '@next/swc-darwin-x64': 14.2.10 + '@next/swc-linux-arm64-gnu': 14.2.10 + '@next/swc-linux-arm64-musl': 14.2.10 + '@next/swc-linux-x64-gnu': 14.2.10 + '@next/swc-linux-x64-musl': 14.2.10 + '@next/swc-win32-arm64-msvc': 14.2.10 + '@next/swc-win32-ia32-msvc': 14.2.10 + '@next/swc-win32-x64-msvc': 14.2.10 sass: 1.79.1 transitivePeerDependencies: - '@babel/core' From a5af0cae4d643dc29815803d848e3bda8da57cce Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 8 Oct 2024 09:31:56 +0200 Subject: [PATCH 236/640] i18n --- apps/login/i18nConfig.js | 7 ++ apps/login/locales/en/loginname.json | 0 apps/login/package.json | 4 + .../(login)/{ => [locale]}/accounts/page.tsx | 0 .../src/app/(login)/{ => [locale]}/error.tsx | 0 .../idp/[provider]/failure/page.tsx | 0 .../idp/[provider]/success/page.tsx | 0 .../app/(login)/{ => [locale]}/idp/page.tsx | 0 .../(login)/{ => [locale]}/loginname/page.tsx | 0 .../me/change-password/page.tsx | 0 .../app/(login)/{ => [locale]}/mfa/page.tsx | 0 .../(login)/{ => [locale]}/mfa/set/page.tsx | 0 .../{ => [locale]}/otp/[method]/page.tsx | 0 .../{ => [locale]}/otp/[method]/set/page.tsx | 0 .../(login)/{ => [locale]}/overview/page.tsx | 0 .../(login)/{ => [locale]}/passkey/page.tsx | 0 .../{ => [locale]}/passkey/set/page.tsx | 0 .../(login)/{ => [locale]}/password/page.tsx | 0 .../(login)/{ => [locale]}/register/page.tsx | 0 .../(login)/{ => [locale]}/signedin/page.tsx | 0 .../app/(login)/{ => [locale]}/u2f/page.tsx | 0 .../(login)/{ => [locale]}/u2f/set/page.tsx | 0 .../(login)/{ => [locale]}/verify/page.tsx | 0 apps/login/src/app/i18n.js | 40 +++++++++ apps/login/src/app/layout.tsx | 82 ++++++++++------- .../src/components/translations-provider.tsx | 26 ++++++ apps/login/src/middleware.ts | 1 + pnpm-lock.yaml | 87 ++++++++++++++++++- 28 files changed, 216 insertions(+), 31 deletions(-) create mode 100644 apps/login/i18nConfig.js create mode 100644 apps/login/locales/en/loginname.json rename apps/login/src/app/(login)/{ => [locale]}/accounts/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/error.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/idp/[provider]/failure/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/idp/[provider]/success/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/idp/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/loginname/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/me/change-password/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/mfa/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/mfa/set/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/otp/[method]/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/otp/[method]/set/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/overview/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/passkey/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/passkey/set/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/password/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/register/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/signedin/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/u2f/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/u2f/set/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/verify/page.tsx (100%) create mode 100644 apps/login/src/app/i18n.js create mode 100644 apps/login/src/components/translations-provider.tsx diff --git a/apps/login/i18nConfig.js b/apps/login/i18nConfig.js new file mode 100644 index 0000000000..f47cf77f79 --- /dev/null +++ b/apps/login/i18nConfig.js @@ -0,0 +1,7 @@ +const i18nConfig = { + locales: ["en", "de", "it"], + defaultLocale: "en", + prefixDefault: false, +}; + +module.exports = i18nConfig; diff --git a/apps/login/locales/en/loginname.json b/apps/login/locales/en/loginname.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/login/package.json b/apps/login/package.json index 0670d5bf49..208ab3b8ba 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -42,14 +42,18 @@ "@zitadel/proto": "workspace:*", "clsx": "1.2.1", "copy-to-clipboard": "^3.3.3", + "i18next": "^23.15.2", + "i18next-resources-to-backend": "^1.2.1", "moment": "^2.29.4", "next": "14.2.10", + "next-i18n-router": "^5.5.1", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", "qrcode.react": "^3.1.0", "react": "^18.3.1", "react-dom": "18.3.1", "react-hook-form": "7.39.5", + "react-i18next": "^15.0.2", "swr": "^2.2.0", "tinycolor2": "1.4.2" }, diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/[locale]/accounts/page.tsx similarity index 100% rename from apps/login/src/app/(login)/accounts/page.tsx rename to apps/login/src/app/(login)/[locale]/accounts/page.tsx diff --git a/apps/login/src/app/(login)/error.tsx b/apps/login/src/app/(login)/[locale]/error.tsx similarity index 100% rename from apps/login/src/app/(login)/error.tsx rename to apps/login/src/app/(login)/[locale]/error.tsx diff --git a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx b/apps/login/src/app/(login)/[locale]/idp/[provider]/failure/page.tsx similarity index 100% rename from apps/login/src/app/(login)/idp/[provider]/failure/page.tsx rename to apps/login/src/app/(login)/[locale]/idp/[provider]/failure/page.tsx diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/[locale]/idp/[provider]/success/page.tsx similarity index 100% rename from apps/login/src/app/(login)/idp/[provider]/success/page.tsx rename to apps/login/src/app/(login)/[locale]/idp/[provider]/success/page.tsx diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/[locale]/idp/page.tsx similarity index 100% rename from apps/login/src/app/(login)/idp/page.tsx rename to apps/login/src/app/(login)/[locale]/idp/page.tsx diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/[locale]/loginname/page.tsx similarity index 100% rename from apps/login/src/app/(login)/loginname/page.tsx rename to apps/login/src/app/(login)/[locale]/loginname/page.tsx diff --git a/apps/login/src/app/(login)/me/change-password/page.tsx b/apps/login/src/app/(login)/[locale]/me/change-password/page.tsx similarity index 100% rename from apps/login/src/app/(login)/me/change-password/page.tsx rename to apps/login/src/app/(login)/[locale]/me/change-password/page.tsx diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/[locale]/mfa/page.tsx similarity index 100% rename from apps/login/src/app/(login)/mfa/page.tsx rename to apps/login/src/app/(login)/[locale]/mfa/page.tsx diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/[locale]/mfa/set/page.tsx similarity index 100% rename from apps/login/src/app/(login)/mfa/set/page.tsx rename to apps/login/src/app/(login)/[locale]/mfa/set/page.tsx diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/[locale]/otp/[method]/page.tsx similarity index 100% rename from apps/login/src/app/(login)/otp/[method]/page.tsx rename to apps/login/src/app/(login)/[locale]/otp/[method]/page.tsx diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/[locale]/otp/[method]/set/page.tsx similarity index 100% rename from apps/login/src/app/(login)/otp/[method]/set/page.tsx rename to apps/login/src/app/(login)/[locale]/otp/[method]/set/page.tsx diff --git a/apps/login/src/app/(login)/overview/page.tsx b/apps/login/src/app/(login)/[locale]/overview/page.tsx similarity index 100% rename from apps/login/src/app/(login)/overview/page.tsx rename to apps/login/src/app/(login)/[locale]/overview/page.tsx diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/[locale]/passkey/page.tsx similarity index 100% rename from apps/login/src/app/(login)/passkey/page.tsx rename to apps/login/src/app/(login)/[locale]/passkey/page.tsx diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/[locale]/passkey/set/page.tsx similarity index 100% rename from apps/login/src/app/(login)/passkey/set/page.tsx rename to apps/login/src/app/(login)/[locale]/passkey/set/page.tsx diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/[locale]/password/page.tsx similarity index 100% rename from apps/login/src/app/(login)/password/page.tsx rename to apps/login/src/app/(login)/[locale]/password/page.tsx diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/[locale]/register/page.tsx similarity index 100% rename from apps/login/src/app/(login)/register/page.tsx rename to apps/login/src/app/(login)/[locale]/register/page.tsx diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/[locale]/signedin/page.tsx similarity index 100% rename from apps/login/src/app/(login)/signedin/page.tsx rename to apps/login/src/app/(login)/[locale]/signedin/page.tsx diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/[locale]/u2f/page.tsx similarity index 100% rename from apps/login/src/app/(login)/u2f/page.tsx rename to apps/login/src/app/(login)/[locale]/u2f/page.tsx diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/[locale]/u2f/set/page.tsx similarity index 100% rename from apps/login/src/app/(login)/u2f/set/page.tsx rename to apps/login/src/app/(login)/[locale]/u2f/set/page.tsx diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/[locale]/verify/page.tsx similarity index 100% rename from apps/login/src/app/(login)/verify/page.tsx rename to apps/login/src/app/(login)/[locale]/verify/page.tsx diff --git a/apps/login/src/app/i18n.js b/apps/login/src/app/i18n.js new file mode 100644 index 0000000000..b6dae438b7 --- /dev/null +++ b/apps/login/src/app/i18n.js @@ -0,0 +1,40 @@ +import { createInstance } from "i18next"; +import { initReactI18next } from "react-i18next/initReactI18next"; +import resourcesToBackend from "i18next-resources-to-backend"; +import i18nConfig from "i18nConfig"; + +export default async function initTranslations( + locale, + namespaces, + i18nInstance, + resources +) { + i18nInstance = i18nInstance || createInstance(); + + i18nInstance.use(initReactI18next); + + if (!resources) { + i18nInstance.use( + resourcesToBackend((language, namespace) => + import(`/locales/${language}/${namespace}.json`) + ) + ); + } + + await i18nInstance.init({ + lng: locale, + resources, + fallbackLng: i18nConfig.defaultLocale, + supportedLngs: i18nConfig.locales, + defaultNS: namespaces[0], + fallbackNS: namespaces[0], + ns: namespaces, + preload: resources ? [] : i18nConfig.locales, + }); + + return { + i18n: i18nInstance, + resources: i18nInstance.services.resourceStore.data, + t: i18nInstance.t, + }; +} diff --git a/apps/login/src/app/layout.tsx b/apps/login/src/app/layout.tsx index 701409fd0f..f8f0fab6a7 100644 --- a/apps/login/src/app/layout.tsx +++ b/apps/login/src/app/layout.tsx @@ -4,9 +4,13 @@ import { AddressBar } from "@/components/address-bar"; import { GlobalNav } from "@/components/global-nav"; import { Theme } from "@/components/theme"; import { ThemeProvider } from "@/components/theme-provider"; +import { TranslationsProvider } from "@/components/translations-provider"; import { Analytics } from "@vercel/analytics/react"; +import i18nConfig from "i18nConfig"; +import { dir } from "i18next"; import { Lato } from "next/font/google"; import { ReactNode } from "react"; +import initTranslations from "./i18n"; const lato = Lato({ weight: ["400", "700", "900"], @@ -15,11 +19,20 @@ const lato = Lato({ export const revalidate = 60; // revalidate every minute +export function generateStaticParams() { + return i18nConfig.locales.map((locale) => ({ locale })); +} + export default async function RootLayout({ children, + params: { locale }, }: { children: ReactNode; + params: { locale: string }; }) { + const i18nNamespaces = ["common", "footer"]; + const { t, resources } = await initTranslations(locale, i18nNamespaces); + // later only shown with dev mode enabled const showNav = process.env.DEBUG === "true"; @@ -27,45 +40,56 @@ export default async function RootLayout({ domain = domain ? domain.replace("https://", "") : "acme.com"; return ( - + -
    - {showNav ? ( - - ) : ( -
    - -
    - )} -
    -
    - {showNav && ( -
    -
    - -
    -
    - )} + {showNav ? ( + + ) : ( +
    + +
    + )} - {children} +
    +
    + {showNav && ( +
    +
    + +
    +
    + )} + + {children} +
    -
    - + + diff --git a/apps/login/src/components/translations-provider.tsx b/apps/login/src/components/translations-provider.tsx new file mode 100644 index 0000000000..d4692a5e6d --- /dev/null +++ b/apps/login/src/components/translations-provider.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { createInstance } from "i18next"; +import React from "react"; +import { I18nextProvider } from "react-i18next"; +import initTranslations from "../app/i18n"; + +type Props = { + locale: string; + children: React.ReactNode; + namespaces: string[]; + resources: Record>; +}; + +export function TranslationsProvider({ + children, + locale, + namespaces, + resources, +}: Props) { + const i18n = createInstance(); + + initTranslations(locale, namespaces, i18n, resources); + + return {children}; +} diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 3b9f879e30..ee3a66eaf0 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -32,6 +32,7 @@ export function middleware(request: NextRequest) { responseHeaders.set("Access-Control-Allow-Headers", "*"); request.nextUrl.href = `${INSTANCE}${request.nextUrl.pathname}${request.nextUrl.search}`; + return NextResponse.rewrite(request.nextUrl, { request: { headers: requestHeaders, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6435a13f2..ecfa984f27 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,12 +77,21 @@ importers: copy-to-clipboard: specifier: ^3.3.3 version: 3.3.3 + i18next: + specifier: ^23.15.2 + version: 23.15.2 + i18next-resources-to-backend: + specifier: ^1.2.1 + version: 1.2.1 moment: specifier: ^2.29.4 version: 2.30.1 next: specifier: 14.2.10 version: 14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next-i18n-router: + specifier: ^5.5.1 + version: 5.5.1 next-themes: specifier: ^0.2.1 version: 0.2.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -101,6 +110,9 @@ importers: react-hook-form: specifier: 7.39.5 version: 7.39.5(react@18.3.1) + react-i18next: + specifier: ^15.0.2 + version: 15.0.2(i18next@23.15.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) swr: specifier: ^2.2.0 version: 2.2.5(react@18.3.1) @@ -839,6 +851,9 @@ packages: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} + '@formatjs/intl-localematcher@0.5.4': + resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==} + '@grpc/grpc-js@1.11.1': resolution: {integrity: sha512-gyt/WayZrVPH2w/UTLansS7F9Nwld472JxxaETamrM8HNlsa+jSLNyKAZmhxI2Me4c3mQHFiS1wWHDY1g1Kthw==} engines: {node: '>=12.10.0'} @@ -2141,6 +2156,7 @@ packages: eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@9.6.1: @@ -2491,6 +2507,9 @@ packages: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -2522,6 +2541,12 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + i18next-resources-to-backend@1.2.1: + resolution: {integrity: sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==} + + i18next@23.15.2: + resolution: {integrity: sha512-zcPSWzCvw6uKnuYHIqs4W7hTuB9e3AFcSdZgvCWoPXIZsBjBd4djN2/2uOHIB+1DFFkQnMBXvhNg7J3WyCuywQ==} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -3102,6 +3127,13 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + next-i18n-router@5.5.1: + resolution: {integrity: sha512-uJGYUAQS33LbRT3Jx+kurR/E79iPQo1jWZUYmc+614UkPt58k2XYyGloSvHR74b21i4K/d6eksdBj6T2WojjdA==} + next-themes@0.2.1: resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} peerDependencies: @@ -3603,6 +3635,19 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 + react-i18next@15.0.2: + resolution: {integrity: sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==} + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4375,6 +4420,10 @@ packages: jsdom: optional: true + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -5065,6 +5114,10 @@ snapshots: '@fastify/busboy@2.1.1': {} + '@formatjs/intl-localematcher@0.5.4': + dependencies: + tslib: 2.7.0 + '@grpc/grpc-js@1.11.1': dependencies: '@grpc/proto-loader': 0.7.13 @@ -5136,14 +5189,14 @@ snapshots: '@manypkg/find-root@1.1.0': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.25.7 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.25.7 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -6988,6 +7041,10 @@ snapshots: dependencies: whatwg-encoding: 3.1.1 + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 @@ -7023,6 +7080,14 @@ snapshots: human-signals@5.0.0: {} + i18next-resources-to-backend@1.2.1: + dependencies: + '@babel/runtime': 7.25.7 + + i18next@23.15.2: + dependencies: + '@babel/runtime': 7.25.7 + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -7579,6 +7644,13 @@ snapshots: natural-compare@1.4.0: {} + negotiator@0.6.3: {} + + next-i18n-router@5.5.1: + dependencies: + '@formatjs/intl-localematcher': 0.5.4 + negotiator: 0.6.3 + next-themes@0.2.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: next: 14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) @@ -7990,6 +8062,15 @@ snapshots: dependencies: react: 18.3.1 + react-i18next@15.0.2(i18next@23.15.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.25.7 + html-parse-stringify: 3.0.1 + i18next: 23.15.2 + react: 18.3.1 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + react-is@16.13.1: {} react-is@17.0.2: {} @@ -8834,6 +8915,8 @@ snapshots: - supports-color - terser + void-elements@3.1.0: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 From 5dc1d8b0bc26fc9bd905c86e88ae68c49d1d982b Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 8 Oct 2024 14:32:06 +0200 Subject: [PATCH 237/640] i18n setup --- apps/login/i18nConfig.js | 4 +- apps/login/locales/en/loginname.json | 4 + apps/login/package.json | 4 +- .../{(login) => }/[locale]/accounts/page.tsx | 0 .../src/app/{(login) => }/[locale]/error.tsx | 0 .../[locale]/idp/[provider]/failure/page.tsx | 0 .../[locale]/idp/[provider]/success/page.tsx | 0 .../app/{(login) => }/[locale]/idp/page.tsx | 0 apps/login/src/app/{ => [locale]}/layout.tsx | 7 +- .../{(login) => }/[locale]/loginname/page.tsx | 9 +- .../[locale]/me/change-password/page.tsx | 0 .../app/{(login) => }/[locale]/mfa/page.tsx | 0 .../{(login) => }/[locale]/mfa/set/page.tsx | 0 .../[locale]/otp/[method]/page.tsx | 0 .../[locale]/otp/[method]/set/page.tsx | 0 .../{(login) => }/[locale]/overview/page.tsx | 0 apps/login/src/app/{ => [locale]}/page.tsx | 0 .../{(login) => }/[locale]/passkey/page.tsx | 0 .../[locale]/passkey/set/page.tsx | 0 .../{(login) => }/[locale]/password/page.tsx | 0 .../{(login) => }/[locale]/register/page.tsx | 0 .../{(login) => }/[locale]/signedin/page.tsx | 0 .../app/{(login) => }/[locale]/u2f/page.tsx | 0 .../{(login) => }/[locale]/u2f/set/page.tsx | 0 .../{(login) => }/[locale]/verify/page.tsx | 0 .../src/components/language-switcher.tsx | 133 ++++++++++++++++ apps/login/src/en/loginname.json | 0 apps/login/src/middleware.ts | 61 ++++---- pnpm-lock.yaml | 144 +++++++++++------- 29 files changed, 272 insertions(+), 94 deletions(-) rename apps/login/src/app/{(login) => }/[locale]/accounts/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/error.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/idp/[provider]/failure/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/idp/[provider]/success/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/idp/page.tsx (100%) rename apps/login/src/app/{ => [locale]}/layout.tsx (94%) rename apps/login/src/app/{(login) => }/[locale]/loginname/page.tsx (89%) rename apps/login/src/app/{(login) => }/[locale]/me/change-password/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/mfa/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/mfa/set/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/otp/[method]/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/otp/[method]/set/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/overview/page.tsx (100%) rename apps/login/src/app/{ => [locale]}/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/passkey/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/passkey/set/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/password/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/register/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/signedin/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/u2f/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/u2f/set/page.tsx (100%) rename apps/login/src/app/{(login) => }/[locale]/verify/page.tsx (100%) create mode 100644 apps/login/src/components/language-switcher.tsx create mode 100644 apps/login/src/en/loginname.json diff --git a/apps/login/i18nConfig.js b/apps/login/i18nConfig.js index f47cf77f79..0397dfec56 100644 --- a/apps/login/i18nConfig.js +++ b/apps/login/i18nConfig.js @@ -1,7 +1,7 @@ const i18nConfig = { - locales: ["en", "de", "it"], + locales: ["en"], defaultLocale: "en", prefixDefault: false, }; -module.exports = i18nConfig; +export default i18nConfig; diff --git a/apps/login/locales/en/loginname.json b/apps/login/locales/en/loginname.json index e69de29bb2..11de53292f 100644 --- a/apps/login/locales/en/loginname.json +++ b/apps/login/locales/en/loginname.json @@ -0,0 +1,4 @@ +{ + "title": "Welcome back!!", + "description": "Enter your login data." +} diff --git a/apps/login/package.json b/apps/login/package.json index 208ab3b8ba..03c5f181d5 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -45,7 +45,7 @@ "i18next": "^23.15.2", "i18next-resources-to-backend": "^1.2.1", "moment": "^2.29.4", - "next": "14.2.10", + "next": "14.2.14", "next-i18n-router": "^5.5.1", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", @@ -85,7 +85,7 @@ "prettier-plugin-tailwindcss": "0.6.6", "sass": "^1.79.1", "start-server-and-test": "^2.0.8", - "tailwindcss": "3.4.12", + "tailwindcss": "3.4.13", "ts-proto": "^2.2.0", "typescript": "^5.6.2", "zitadel-tailwind-config": "workspace:*" diff --git a/apps/login/src/app/(login)/[locale]/accounts/page.tsx b/apps/login/src/app/[locale]/accounts/page.tsx similarity index 100% rename from apps/login/src/app/(login)/[locale]/accounts/page.tsx rename to apps/login/src/app/[locale]/accounts/page.tsx diff --git a/apps/login/src/app/(login)/[locale]/error.tsx b/apps/login/src/app/[locale]/error.tsx similarity index 100% rename from apps/login/src/app/(login)/[locale]/error.tsx rename to apps/login/src/app/[locale]/error.tsx diff --git a/apps/login/src/app/(login)/[locale]/idp/[provider]/failure/page.tsx b/apps/login/src/app/[locale]/idp/[provider]/failure/page.tsx similarity index 100% rename from apps/login/src/app/(login)/[locale]/idp/[provider]/failure/page.tsx rename to apps/login/src/app/[locale]/idp/[provider]/failure/page.tsx diff --git a/apps/login/src/app/(login)/[locale]/idp/[provider]/success/page.tsx b/apps/login/src/app/[locale]/idp/[provider]/success/page.tsx similarity index 100% rename from apps/login/src/app/(login)/[locale]/idp/[provider]/success/page.tsx rename to apps/login/src/app/[locale]/idp/[provider]/success/page.tsx diff --git a/apps/login/src/app/(login)/[locale]/idp/page.tsx b/apps/login/src/app/[locale]/idp/page.tsx similarity index 100% rename from apps/login/src/app/(login)/[locale]/idp/page.tsx rename to apps/login/src/app/[locale]/idp/page.tsx diff --git a/apps/login/src/app/layout.tsx b/apps/login/src/app/[locale]/layout.tsx similarity index 94% rename from apps/login/src/app/layout.tsx rename to apps/login/src/app/[locale]/layout.tsx index f8f0fab6a7..90efe8d705 100644 --- a/apps/login/src/app/layout.tsx +++ b/apps/login/src/app/[locale]/layout.tsx @@ -10,14 +10,14 @@ import i18nConfig from "i18nConfig"; import { dir } from "i18next"; import { Lato } from "next/font/google"; import { ReactNode } from "react"; -import initTranslations from "./i18n"; +import initTranslations from "../i18n"; const lato = Lato({ weight: ["400", "700", "900"], subsets: ["latin"], }); -export const revalidate = 60; // revalidate every minute +// export const revalidate = 60; // revalidate every minute export function generateStaticParams() { return i18nConfig.locales.map((locale) => ({ locale })); @@ -30,7 +30,8 @@ export default async function RootLayout({ children: ReactNode; params: { locale: string }; }) { - const i18nNamespaces = ["common", "footer"]; + const i18nNamespaces = ["loginname"]; + console.log("layout:", locale); const { t, resources } = await initTranslations(locale, i18nNamespaces); // later only shown with dev mode enabled diff --git a/apps/login/src/app/(login)/[locale]/loginname/page.tsx b/apps/login/src/app/[locale]/loginname/page.tsx similarity index 89% rename from apps/login/src/app/(login)/[locale]/loginname/page.tsx rename to apps/login/src/app/[locale]/loginname/page.tsx index e1847cbcf8..bd4f6a5a07 100644 --- a/apps/login/src/app/(login)/[locale]/loginname/page.tsx +++ b/apps/login/src/app/[locale]/loginname/page.tsx @@ -1,3 +1,4 @@ +import initTranslations from "@/app/i18n"; import { DynamicTheme } from "@/components/dynamic-theme"; import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { UsernameForm } from "@/components/username-form"; @@ -19,9 +20,13 @@ function getIdentityProviders(orgId?: string) { export default async function Page({ searchParams, + params: { locale }, }: { searchParams: Record; + params: { locale: string }; }) { + const { t } = await initTranslations(locale, ["loginname"]); + const loginName = searchParams?.loginName; const authRequestId = searchParams?.authRequestId; const organization = searchParams?.organization; @@ -41,8 +46,8 @@ export default async function Page({ return (
    -

    Welcome back!

    -

    Enter your login data.

    +

    {t("title")}

    +

    {t("description")}

    l.code === currentLocale) || LANGS[0], + ); + + const router = useRouter(); + const currentPathname = usePathname(); + + const handleChange = (language: Lang) => { + setSelected(language); + const newLocale = language.code; + // set cookie for next-i18n-router + const days = 30; + const date = new Date(); + date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); + const expires = date.toUTCString(); + document.cookie = `NEXT_LOCALE=${newLocale};expires=${expires};path=/`; + + // redirect to the new locale path + if ( + currentLocale === i18nConfig.defaultLocale && + !i18nConfig.prefixDefault + ) { + router.push("/" + newLocale + currentPathname); + } else { + router.push( + currentPathname.replace(`/${currentLocale}`, `/${newLocale}`), + ); + } + + router.refresh(); + }; + + return ( +
    + +
    + + {selected.name} + + + + + + {LANGS.map((lang, index) => ( + + `relative cursor-default select-none py-2 pl-10 pr-4 ${ + active ? "bg-amber-100 text-amber-900" : "text-gray-900" + }` + } + value={lang} + > + {({ selected }) => ( + <> + + {lang.name} + + {selected ? ( + + + ) : null} + + )} + + ))} + + +
    +
    +
    + ); +} diff --git a/apps/login/src/en/loginname.json b/apps/login/src/en/loginname.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index ee3a66eaf0..a3722fe910 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -1,42 +1,45 @@ +import { i18nRouter } from "next-i18n-router"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; - -export const config = { - matcher: [ - "/.well-known/:path*", - "/oauth/:path*", - "/oidc/:path*", - "/idps/callback/:path*", - ], -}; +import i18nConfig from "../i18nConfig"; const INSTANCE = process.env.ZITADEL_API_URL; const SERVICE_USER_ID = process.env.ZITADEL_SERVICE_USER_ID as string; export function middleware(request: NextRequest) { - const requestHeaders = new Headers(request.headers); - requestHeaders.set("x-zitadel-login-client", SERVICE_USER_ID); + // OIDC specific routes + if ( + request.nextUrl.pathname.startsWith("/.well-known") || + request.nextUrl.pathname.startsWith("/oauth") || + request.nextUrl.pathname.startsWith("/oidc") || + request.nextUrl.pathname.startsWith("/idps/callback") + ) { + const requestHeaders = new Headers(request.headers); + requestHeaders.set("x-zitadel-login-client", SERVICE_USER_ID); - // this is a workaround for the next.js server not forwarding the host header - // requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`); - requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`); + // this is a workaround for the next.js server not forwarding the host header + // requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`); + requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`); - // this is a workaround for the next.js server not forwarding the host header - requestHeaders.set( - "x-zitadel-instance-host", - `${INSTANCE}`.replace("https://", ""), - ); + // this is a workaround for the next.js server not forwarding the host header + requestHeaders.set( + "x-zitadel-instance-host", + `${INSTANCE}`.replace("https://", ""), + ); - const responseHeaders = new Headers(); - responseHeaders.set("Access-Control-Allow-Origin", "*"); - responseHeaders.set("Access-Control-Allow-Headers", "*"); + const responseHeaders = new Headers(); + responseHeaders.set("Access-Control-Allow-Origin", "*"); + responseHeaders.set("Access-Control-Allow-Headers", "*"); - request.nextUrl.href = `${INSTANCE}${request.nextUrl.pathname}${request.nextUrl.search}`; + request.nextUrl.href = `${INSTANCE}${request.nextUrl.pathname}${request.nextUrl.search}`; - return NextResponse.rewrite(request.nextUrl, { - request: { - headers: requestHeaders, - }, - headers: responseHeaders, - }); + return NextResponse.rewrite(request.nextUrl, { + request: { + headers: requestHeaders, + }, + headers: responseHeaders, + }); + } + + return i18nRouter(request, i18nConfig); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ecfa984f27..7e2cfb61c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,10 +58,10 @@ importers: version: 2.1.3(react@18.3.1) '@tailwindcss/forms': specifier: 0.5.7 - version: 0.5.7(tailwindcss@3.4.12) + version: 0.5.7(tailwindcss@3.4.13) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) + version: 1.3.1(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -87,14 +87,14 @@ importers: specifier: ^2.29.4 version: 2.30.1 next: - specifier: 14.2.10 - version: 14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + specifier: 14.2.14 + version: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) next-i18n-router: specifier: ^5.5.1 version: 5.5.1 next-themes: specifier: ^0.2.1 - version: 0.2.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.2.1(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nice-grpc: specifier: 2.0.1 version: 2.0.1 @@ -202,8 +202,8 @@ importers: specifier: ^2.0.8 version: 2.0.8 tailwindcss: - specifier: 3.4.12 - version: 3.4.12 + specifier: 3.4.13 + version: 3.4.13 ts-proto: specifier: ^2.2.0 version: 2.2.0 @@ -929,62 +929,62 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true - '@next/env@14.2.10': - resolution: {integrity: sha512-dZIu93Bf5LUtluBXIv4woQw2cZVZ2DJTjax5/5DOs3lzEOeKLy7GxRSr4caK9/SCPdaW6bCgpye6+n4Dh9oJPw==} + '@next/env@14.2.14': + resolution: {integrity: sha512-/0hWQfiaD5//LvGNgc8PjvyqV50vGK0cADYzaoOOGN8fxzBn3iAiaq3S0tCRnFBldq0LVveLcxCTi41ZoYgAgg==} '@next/eslint-plugin-next@14.2.7': resolution: {integrity: sha512-+7xh142AdhZGjY9/L0iFo7mqRBMJHe+q+uOL+hto1Lfo9DeWCGcR6no4StlFbVSVcA6fQLKEX6y6qhMsSKbgNQ==} - '@next/swc-darwin-arm64@14.2.10': - resolution: {integrity: sha512-V3z10NV+cvMAfxQUMhKgfQnPbjw+Ew3cnr64b0lr8MDiBJs3eLnM6RpGC46nhfMZsiXgQngCJKWGTC/yDcgrDQ==} + '@next/swc-darwin-arm64@14.2.14': + resolution: {integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.10': - resolution: {integrity: sha512-Y0TC+FXbFUQ2MQgimJ/7Ina2mXIKhE7F+GUe1SgnzRmwFY3hX2z8nyVCxE82I2RicspdkZnSWMn4oTjIKz4uzA==} + '@next/swc-darwin-x64@14.2.14': + resolution: {integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.10': - resolution: {integrity: sha512-ZfQ7yOy5zyskSj9rFpa0Yd7gkrBnJTkYVSya95hX3zeBG9E55Z6OTNPn1j2BTFWvOVVj65C3T+qsjOyVI9DQpA==} + '@next/swc-linux-arm64-gnu@14.2.14': + resolution: {integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.10': - resolution: {integrity: sha512-n2i5o3y2jpBfXFRxDREr342BGIQCJbdAUi/K4q6Env3aSx8erM9VuKXHw5KNROK9ejFSPf0LhoSkU/ZiNdacpQ==} + '@next/swc-linux-arm64-musl@14.2.14': + resolution: {integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.10': - resolution: {integrity: sha512-GXvajAWh2woTT0GKEDlkVhFNxhJS/XdDmrVHrPOA83pLzlGPQnixqxD8u3bBB9oATBKB//5e4vpACnx5Vaxdqg==} + '@next/swc-linux-x64-gnu@14.2.14': + resolution: {integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.10': - resolution: {integrity: sha512-opFFN5B0SnO+HTz4Wq4HaylXGFV+iHrVxd3YvREUX9K+xfc4ePbRrxqOuPOFjtSuiVouwe6uLeDtabjEIbkmDA==} + '@next/swc-linux-x64-musl@14.2.14': + resolution: {integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.10': - resolution: {integrity: sha512-9NUzZuR8WiXTvv+EiU/MXdcQ1XUvFixbLIMNQiVHuzs7ZIFrJDLJDaOF1KaqttoTujpcxljM/RNAOmw1GhPPQQ==} + '@next/swc-win32-arm64-msvc@14.2.14': + resolution: {integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.10': - resolution: {integrity: sha512-fr3aEbSd1GeW3YUMBkWAu4hcdjZ6g4NBl1uku4gAn661tcxd1bHs1THWYzdsbTRLcCKLjrDZlNp6j2HTfrw+Bg==} + '@next/swc-win32-ia32-msvc@14.2.14': + resolution: {integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@next/swc-win32-x64-msvc@14.2.10': - resolution: {integrity: sha512-UjeVoRGKNL2zfbcQ6fscmgjBAS/inHBh63mjIlfPg/NG8Yn2ztqylXt5qilYb6hoHIwaU2ogHknHWWmahJjgZQ==} + '@next/swc-win32-x64-msvc@14.2.14': + resolution: {integrity: sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -3141,8 +3141,8 @@ packages: react: '*' react-dom: '*' - next@14.2.10: - resolution: {integrity: sha512-sDDExXnh33cY3RkS9JuFEKaS4HmlWmDKP1VJioucCG6z5KuA008DPsDZOzi8UfqEk3Ii+2NCQSJrfbEWtZZfww==} + next@14.2.14: + resolution: {integrity: sha512-Q1coZG17MW0Ly5x76shJ4dkC23woLAhhnDnw+DfTc7EpZSGuWrlsZ3bZaO8t6u1Yu8FVfhkqJE+U8GC7E0GLPQ==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -4059,6 +4059,11 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + tailwindcss@3.4.13: + resolution: {integrity: sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==} + engines: {node: '>=14.0.0'} + hasBin: true + tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -5218,37 +5223,37 @@ snapshots: - encoding - supports-color - '@next/env@14.2.10': {} + '@next/env@14.2.14': {} '@next/eslint-plugin-next@14.2.7': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@14.2.10': + '@next/swc-darwin-arm64@14.2.14': optional: true - '@next/swc-darwin-x64@14.2.10': + '@next/swc-darwin-x64@14.2.14': optional: true - '@next/swc-linux-arm64-gnu@14.2.10': + '@next/swc-linux-arm64-gnu@14.2.14': optional: true - '@next/swc-linux-arm64-musl@14.2.10': + '@next/swc-linux-arm64-musl@14.2.14': optional: true - '@next/swc-linux-x64-gnu@14.2.10': + '@next/swc-linux-x64-gnu@14.2.14': optional: true - '@next/swc-linux-x64-musl@14.2.10': + '@next/swc-linux-x64-musl@14.2.14': optional: true - '@next/swc-win32-arm64-msvc@14.2.10': + '@next/swc-win32-arm64-msvc@14.2.14': optional: true - '@next/swc-win32-ia32-msvc@14.2.10': + '@next/swc-win32-ia32-msvc@14.2.14': optional: true - '@next/swc-win32-x64-msvc@14.2.10': + '@next/swc-win32-x64-msvc@14.2.14': optional: true '@nodelib/fs.scandir@2.1.5': @@ -5361,10 +5366,10 @@ snapshots: mini-svg-data-uri: 1.4.4 tailwindcss: 3.4.12 - '@tailwindcss/forms@0.5.7(tailwindcss@3.4.12)': + '@tailwindcss/forms@0.5.7(tailwindcss@3.4.13)': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.12 + tailwindcss: 3.4.13 '@tanstack/react-virtual@3.10.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -5512,11 +5517,11 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/analytics@1.3.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1)': + '@vercel/analytics@1.3.1(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) react: 18.3.1 '@vercel/git-hooks@1.0.0': {} @@ -7651,15 +7656,15 @@ snapshots: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next-themes@0.2.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-themes@0.2.1(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1): + next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1): dependencies: - '@next/env': 14.2.10 + '@next/env': 14.2.14 '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001666 @@ -7669,15 +7674,15 @@ snapshots: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.10 - '@next/swc-darwin-x64': 14.2.10 - '@next/swc-linux-arm64-gnu': 14.2.10 - '@next/swc-linux-arm64-musl': 14.2.10 - '@next/swc-linux-x64-gnu': 14.2.10 - '@next/swc-linux-x64-musl': 14.2.10 - '@next/swc-win32-arm64-msvc': 14.2.10 - '@next/swc-win32-ia32-msvc': 14.2.10 - '@next/swc-win32-x64-msvc': 14.2.10 + '@next/swc-darwin-arm64': 14.2.14 + '@next/swc-darwin-x64': 14.2.14 + '@next/swc-linux-arm64-gnu': 14.2.14 + '@next/swc-linux-arm64-musl': 14.2.14 + '@next/swc-linux-x64-gnu': 14.2.14 + '@next/swc-linux-x64-musl': 14.2.14 + '@next/swc-win32-arm64-msvc': 14.2.14 + '@next/swc-win32-ia32-msvc': 14.2.14 + '@next/swc-win32-x64-msvc': 14.2.14 sass: 1.79.1 transitivePeerDependencies: - '@babel/core' @@ -8564,6 +8569,33 @@ snapshots: transitivePeerDependencies: - ts-node + tailwindcss@3.4.13: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.2 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.6 + lilconfig: 2.1.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.0 + postcss: 8.4.47 + postcss-import: 15.1.0(postcss@8.4.47) + postcss-js: 4.0.1(postcss@8.4.47) + postcss-load-config: 4.0.2(postcss@8.4.47) + postcss-nested: 6.2.0(postcss@8.4.47) + postcss-selector-parser: 6.1.2 + resolve: 1.22.8 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + tapable@2.2.1: {} tar@6.2.1: From fc03ecdc60dcab4cd8f676845acc3cca755692cb Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 8 Oct 2024 14:35:46 +0200 Subject: [PATCH 238/640] fix middlware config --- apps/login/locales/en/loginname.json | 2 +- apps/login/src/middleware.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/login/locales/en/loginname.json b/apps/login/locales/en/loginname.json index 11de53292f..6a7d1d3b20 100644 --- a/apps/login/locales/en/loginname.json +++ b/apps/login/locales/en/loginname.json @@ -1,4 +1,4 @@ { - "title": "Welcome back!!", + "title": "Welcome back!", "description": "Enter your login data." } diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index a3722fe910..bf36545968 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -6,6 +6,10 @@ import i18nConfig from "../i18nConfig"; const INSTANCE = process.env.ZITADEL_API_URL; const SERVICE_USER_ID = process.env.ZITADEL_SERVICE_USER_ID as string; +export const config = { + matcher: "/((?!api|static|.*\\..*|_next).*)", +}; + export function middleware(request: NextRequest) { // OIDC specific routes if ( From e6a54311242959eb3db4fe99a49ef955997d7ba0 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 8 Oct 2024 15:14:29 +0200 Subject: [PATCH 239/640] implement language switcher --- apps/login/i18nConfig.js | 2 +- apps/login/locales/de/loginname.json | 4 + apps/login/locales/it/loginname.json | 4 + apps/login/package.json | 2 +- apps/login/src/app/[locale]/layout.tsx | 4 +- .../src/components/language-switcher.tsx | 35 +++-- apps/login/src/components/theme.tsx | 2 +- pnpm-lock.yaml | 146 +++++++++++++++++- 8 files changed, 175 insertions(+), 24 deletions(-) create mode 100644 apps/login/locales/de/loginname.json create mode 100644 apps/login/locales/it/loginname.json diff --git a/apps/login/i18nConfig.js b/apps/login/i18nConfig.js index 0397dfec56..d17db506c9 100644 --- a/apps/login/i18nConfig.js +++ b/apps/login/i18nConfig.js @@ -1,5 +1,5 @@ const i18nConfig = { - locales: ["en"], + locales: ["en", "de", "it"], defaultLocale: "en", prefixDefault: false, }; diff --git a/apps/login/locales/de/loginname.json b/apps/login/locales/de/loginname.json new file mode 100644 index 0000000000..939b98fd9d --- /dev/null +++ b/apps/login/locales/de/loginname.json @@ -0,0 +1,4 @@ +{ + "title": "Willkommen zurück!", + "description": "Geben Sie Ihre Anmeldedaten ein." +} diff --git a/apps/login/locales/it/loginname.json b/apps/login/locales/it/loginname.json new file mode 100644 index 0000000000..6a7d1d3b20 --- /dev/null +++ b/apps/login/locales/it/loginname.json @@ -0,0 +1,4 @@ +{ + "title": "Welcome back!", + "description": "Enter your login data." +} diff --git a/apps/login/package.json b/apps/login/package.json index 03c5f181d5..5fcfb4c0ca 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -33,7 +33,7 @@ "*": "prettier --write --ignore-unknown" }, "dependencies": { - "@headlessui/react": "^1.7.18", + "@headlessui/react": "^2.1.9", "@heroicons/react": "2.1.3", "@tailwindcss/forms": "0.5.7", "@vercel/analytics": "^1.2.2", diff --git a/apps/login/src/app/[locale]/layout.tsx b/apps/login/src/app/[locale]/layout.tsx index 90efe8d705..1be167024d 100644 --- a/apps/login/src/app/[locale]/layout.tsx +++ b/apps/login/src/app/[locale]/layout.tsx @@ -2,6 +2,7 @@ import "@/styles/globals.scss"; import { AddressBar } from "@/components/address-bar"; import { GlobalNav } from "@/components/global-nav"; +import { LanguageSwitcher } from "@/components/language-switcher"; import { Theme } from "@/components/theme"; import { ThemeProvider } from "@/components/theme-provider"; import { TranslationsProvider } from "@/components/translations-provider"; @@ -65,7 +66,8 @@ export default async function RootLayout({ {showNav ? ( ) : ( -
    +
    +
    )} diff --git a/apps/login/src/components/language-switcher.tsx b/apps/login/src/components/language-switcher.tsx index 0cf5518567..10ec8b762e 100644 --- a/apps/login/src/components/language-switcher.tsx +++ b/apps/login/src/components/language-switcher.tsx @@ -1,6 +1,12 @@ "use client"; -import { Listbox, Transition } from "@headlessui/react"; +import { + Listbox, + ListboxButton, + ListboxOption, + ListboxOptions, + Transition, +} from "@headlessui/react"; import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/24/outline"; import { usePathname, useRouter } from "next/navigation"; import { Fragment, useState } from "react"; @@ -39,7 +45,7 @@ type Props = { locale: string; }; -export default function LanguageSwitcher({ locale }: Props) { +export function LanguageSwitcher({ locale }: Props) { const { i18n } = useTranslation(); const currentLocale = locale || i18n.language || i18nConfig.defaultLocale; @@ -77,10 +83,10 @@ export default function LanguageSwitcher({ locale }: Props) { }; return ( -
    +
    -
    - +
    + {selected.name} - + - + {LANGS.map((lang, index) => ( - `relative cursor-default select-none py-2 pl-10 pr-4 ${ - active ? "bg-amber-100 text-amber-900" : "text-gray-900" + active + ? "bg-background-light-300 dark:bg-background-dark-300" + : "" }` } value={lang} @@ -116,15 +127,15 @@ export default function LanguageSwitcher({ locale }: Props) { {lang.name} {selected ? ( - + ) : null} )} - + ))} - +
    diff --git a/apps/login/src/components/theme.tsx b/apps/login/src/components/theme.tsx index 3d70f5ef5b..86d39476ff 100644 --- a/apps/login/src/components/theme.tsx +++ b/apps/login/src/components/theme.tsx @@ -21,7 +21,7 @@ export function Theme() { return (
    )}
    diff --git a/apps/login/src/i18n/request.ts b/apps/login/src/i18n/request.ts index 6cb19f91e7..123f52e2a7 100644 --- a/apps/login/src/i18n/request.ts +++ b/apps/login/src/i18n/request.ts @@ -2,11 +2,10 @@ import { getRequestConfig } from "next-intl/server"; import { cookies } from "next/headers"; export default getRequestConfig(async () => { - // Provide a static locale, fetch a user setting, - // read from `cookies()`, `headers()`, etc. - const cookiesList = cookies(); - const locale = cookiesList.get("NEXT_LOCALE")?.value ?? "en"; + const locale: string = cookiesList.get("NEXT_LOCALE")?.value ?? "en"; + + console.log("i18nRequest", locale); return { locale, diff --git a/apps/login/src/lib/cookies.ts b/apps/login/src/lib/cookies.ts index f9c8debf1c..6ff84c2c77 100644 --- a/apps/login/src/lib/cookies.ts +++ b/apps/login/src/lib/cookies.ts @@ -31,7 +31,7 @@ export async function setLanguageCookie(language: string) { // @ts-ignore return cookiesList.set({ name: "NEXT_LOCALE", - value: JSON.stringify(language), + value: language, httpOnly: true, path: "/", }); From faf38c7f6f48c1b35bfe8e14fe84f40d836a6813 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 9 Oct 2024 10:40:15 +0200 Subject: [PATCH 243/640] implement fallback language --- apps/login/package.json | 1 + apps/login/src/app/(login)/layout.tsx | 2 +- apps/login/src/i18n/request.ts | 8 +++++++- pnpm-lock.yaml | 9 +++++++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/login/package.json b/apps/login/package.json index d1cf31d7d1..9617300a2e 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -42,6 +42,7 @@ "@zitadel/proto": "workspace:*", "clsx": "1.2.1", "copy-to-clipboard": "^3.3.3", + "deepmerge": "^4.3.1", "moment": "^2.29.4", "next": "14.2.14", "next-intl": "^3.20.0", diff --git a/apps/login/src/app/(login)/layout.tsx b/apps/login/src/app/(login)/layout.tsx index e3f54f235b..700de5561a 100644 --- a/apps/login/src/app/(login)/layout.tsx +++ b/apps/login/src/app/(login)/layout.tsx @@ -54,7 +54,7 @@ export default async function RootLayout({ ) : (
    - + {locale && }
    )} diff --git a/apps/login/src/i18n/request.ts b/apps/login/src/i18n/request.ts index 123f52e2a7..5571eb2229 100644 --- a/apps/login/src/i18n/request.ts +++ b/apps/login/src/i18n/request.ts @@ -1,14 +1,20 @@ +import deepmerge from "deepmerge"; import { getRequestConfig } from "next-intl/server"; import { cookies } from "next/headers"; export default getRequestConfig(async () => { + const fallback = "en"; const cookiesList = cookies(); const locale: string = cookiesList.get("NEXT_LOCALE")?.value ?? "en"; + const userMessages = (await import(`../../locales/${locale}.json`)).default; + const fallbackMessages = (await import(`../../locales/${fallback}.json`)) + .default; + console.log("i18nRequest", locale); return { locale, - messages: (await import(`../../locales/${locale}.json`)).default, + messages: deepmerge(fallbackMessages, userMessages), }; }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45eadb617c..1a2f4ff98a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,9 @@ importers: copy-to-clipboard: specifier: ^3.3.3 version: 3.3.3 + deepmerge: + specifier: ^4.3.1 + version: 4.3.1 moment: specifier: ^2.29.4 version: 2.30.1 @@ -1934,6 +1937,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -6317,6 +6324,8 @@ snapshots: deep-is@0.1.4: {} + deepmerge@4.3.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.0 From 93b2ebf75ece89e975edaadca38882540481aa7e Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 9 Oct 2024 10:47:18 +0200 Subject: [PATCH 244/640] fix server action response --- apps/login/src/app/(login)/layout.tsx | 2 +- apps/login/src/components/language-switcher.tsx | 11 ++++------- apps/login/src/lib/cookies.ts | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/apps/login/src/app/(login)/layout.tsx b/apps/login/src/app/(login)/layout.tsx index 700de5561a..50ea287a1c 100644 --- a/apps/login/src/app/(login)/layout.tsx +++ b/apps/login/src/app/(login)/layout.tsx @@ -54,7 +54,7 @@ export default async function RootLayout({ ) : (
    - {locale && } +
    )} diff --git a/apps/login/src/components/language-switcher.tsx b/apps/login/src/components/language-switcher.tsx index 92f097998f..dd2fa8b74b 100644 --- a/apps/login/src/components/language-switcher.tsx +++ b/apps/login/src/components/language-switcher.tsx @@ -9,6 +9,7 @@ import { Transition, } from "@headlessui/react"; import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/24/outline"; +import { useLocale } from "next-intl"; import { useRouter } from "next/navigation"; import { Fragment, useState } from "react"; @@ -40,12 +41,8 @@ const LANGS: Lang[] = [ }, ]; -type Props = { - locale: string; -}; - -export function LanguageSwitcher({ locale }: Props) { - const currentLocale = locale || "en"; +export function LanguageSwitcher() { + const currentLocale = useLocale(); const [selected, setSelected] = useState( LANGS.find((l) => l.code === currentLocale) || LANGS[0], @@ -83,7 +80,7 @@ export function LanguageSwitcher({ locale }: Props) { > {LANGS.map((lang, index) => ( (sessions: SessionCookie[]) { export async function setLanguageCookie(language: string) { const cookiesList = cookies(); - // @ts-ignore - return cookiesList.set({ + + await cookiesList.set({ name: "NEXT_LOCALE", value: language, httpOnly: true, From 12df4eec8889d862314091696a874cdc57a199e0 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 9 Oct 2024 14:03:01 +0200 Subject: [PATCH 245/640] try to fix unit tests, idp, password, accounts translations --- apps/login/locales/en.json | 44 ++- apps/login/src/app/(login)/accounts/page.tsx | 8 +- .../(login)/idp/[provider]/success/page.tsx | 263 ++++++++---------- apps/login/src/app/(login)/idp/page.tsx | 8 +- apps/login/src/app/(login)/password/page.tsx | 13 +- apps/login/src/components/back-button.tsx | 4 +- .../idps/sign-in-with-gitlab.test.tsx | 17 +- .../idps/sign-in-with-google.test.tsx | 18 +- apps/login/src/components/password-form.tsx | 7 +- apps/login/src/components/sessions-list.tsx | 4 +- apps/login/src/components/username-form.tsx | 2 +- 11 files changed, 214 insertions(+), 174 deletions(-) diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index b7f739055d..fc7808e44f 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -1,14 +1,54 @@ { + "common": { + "back": "Back" + }, + "accounts": { + "title": "Accounts", + "description": "Select the account you want to use.", + "addAnother": "Add another account", + "noResults": "No accounts found" + }, "loginname": { "title": "Welcome back!", "description": "Enter your login data.", - "registerButton": "Register new user" + "register": "Register new user" + }, + "password": { + "title": "Password", + "description": "Enter your password.", + "resetPassword": "Reset Password", + "submit": "Continue" }, "idp": { + "title": "Sign in with SSO", + "description": "Select one of the following providers to sign in", "signInWithApple": "Sign in with Apple", "signInWithGoogle": "Sign in with Google", "signInWithAzureAD": "Sign in with AzureAD", "signInWithGithub": "Sign in with GitHub", - "signInWithGitlab": "Sign in with GitLab" + "signInWithGitlab": "Sign in with GitLab", + "loginSuccess": { + "title": "Login successful", + "description": "You have successfully been loggedIn!" + }, + "linkingSuccess": { + "title": "Account linked", + "description": "You have successfully linked your account!" + }, + "registerSuccess": { + "title": "Registration successful", + "description": "You have successfully registered!" + }, + "loginError": { + "title": "Login failed", + "description": "An error occurred while trying to login." + }, + "linkingError": { + "title": "Account linking failed", + "description": "An error occurred while trying to link your account." + } + }, + "error": { + "unknownContext": "Could not get the context of the user. Make sure to enter the username first or provide a loginName as searchParam." } } diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index fc50d2846b..e412dc6d49 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -3,6 +3,7 @@ import { SessionsList } from "@/components/sessions-list"; import { getAllSessionCookieIds } from "@/lib/cookies"; import { getBrandingSettings, listSessions } from "@/lib/zitadel"; import { UserPlusIcon } from "@heroicons/react/24/outline"; +import { useTranslations } from "next-intl"; import Link from "next/link"; async function loadSessions() { @@ -24,6 +25,7 @@ export default async function Page({ }: { searchParams: Record; }) { + const t = useTranslations("accounts"); const authRequestId = searchParams?.authRequestId; const organization = searchParams?.organization; @@ -34,8 +36,8 @@ export default async function Page({ return (
    -

    Accounts

    -

    Use your ZITADEL Account

    +

    {t("title")}

    +

    {t("description")}

    @@ -53,7 +55,7 @@ export default async function Page({
    - Add another account + {t("addAnother")}
    diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 1c8039def6..644ed135ab 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -11,7 +11,24 @@ import { retrieveIDPIntent, } from "@/lib/zitadel"; import { AutoLinkingOption } from "@zitadel/proto/zitadel/idp/v2/idp_pb"; +import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; +import { getLocale, getTranslations } from "next-intl/server"; +async function loginFailed(branding?: BrandingSettings) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "idp" }); + + return ( + +
    +

    {t("loginError.title")}

    +
    + {{t("loginError.title")}} +
    +
    +
    + ); +} export default async function Page({ searchParams, params, @@ -19,166 +36,96 @@ export default async function Page({ searchParams: Record; params: { provider: string }; }) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "idp" }); const { id, token, authRequestId, organization } = searchParams; const { provider } = params; const branding = await getBrandingSettings(organization); - if (provider && id && token) { - return retrieveIDPIntent(id, token) - .then(async (resp) => { - const { idpInformation, userId } = resp; - if (userId) { - // TODO: update user if idp.options.isAutoUpdate is true + if (!provider || !id || !token) { + return loginFailed(branding); + } - return ( - -
    -

    Login successful

    -
    You have successfully been loggedIn!
    + const intent = await retrieveIDPIntent(id, token); - -
    -
    - ); - } + const { idpInformation, userId } = intent; - if (idpInformation) { - const idp = await getIDPByID(idpInformation.idpId); - const options = idp?.config?.options; + if (userId && id && token) { + // TODO: update user if idp.options.isAutoUpdate is true - if (!idp) { - throw new Error("IDP not found"); - } + return ( + +
    +

    {t("loginSuccess.title")}

    +
    {t("loginSuccess.description")}
    - const providerType = idpTypeToIdentityProviderType(idp.type); + +
    +
    + ); + } - // search for potential user via username, then link - if (options?.isLinkingAllowed) { - let foundUser; - const email = - PROVIDER_MAPPING[providerType](idpInformation).email?.email; + if (!idpInformation) { + return loginFailed(branding); + } - if (options.autoLinking === AutoLinkingOption.EMAIL && email) { - foundUser = await listUsers({ email }).then((response) => { - return response.result ? response.result[0] : null; - }); - } else if (options.autoLinking === AutoLinkingOption.USERNAME) { - foundUser = await listUsers( - options.autoLinking === AutoLinkingOption.USERNAME - ? { userName: idpInformation.userName } - : { email }, - ).then((response) => { - return response.result ? response.result[0] : null; - }); - } else { - foundUser = await listUsers({ - userName: idpInformation.userName, - email, - }).then((response) => { - return response.result ? response.result[0] : null; - }); - } + const idp = await getIDPByID(idpInformation.idpId); + const options = idp?.config?.options; - if (foundUser) { - const idpLink = await addIDPLink( - { - id: idpInformation.idpId, - userId: idpInformation.userId, - userName: idpInformation.userName, - }, - foundUser.userId, - ).catch((error) => { - return ( - -
    -

    Linking failed

    -
    - { - - {JSON.stringify(error.message)} - - } -
    -
    -
    - ); - }); + if (!idp) { + throw new Error("IDP not found"); + } - if (idpLink) { - return ( - // TODO: possibily login user now - -
    -

    Account successfully linked

    -
    Your account has successfully been linked!
    -
    -
    - ); - } - } - } + const providerType = idpTypeToIdentityProviderType(idp.type); - if (options?.isCreationAllowed && options.isAutoCreation) { - const newUser = await createUser(providerType, idpInformation); + // search for potential user via username, then link + if (options?.isLinkingAllowed) { + let foundUser; + const email = PROVIDER_MAPPING[providerType](idpInformation).email?.email; - if (newUser) { - return ( - -
    -

    Register successful

    -
    You have successfully been registered!
    -
    -
    - ); - } - } + if (options.autoLinking === AutoLinkingOption.EMAIL && email) { + foundUser = await listUsers({ email }).then((response) => { + return response.result ? response.result[0] : null; + }); + } else if (options.autoLinking === AutoLinkingOption.USERNAME) { + foundUser = await listUsers( + options.autoLinking === AutoLinkingOption.USERNAME + ? { userName: idpInformation.userName } + : { email }, + ).then((response) => { + return response.result ? response.result[0] : null; + }); + } else { + foundUser = await listUsers({ + userName: idpInformation.userName, + email, + }).then((response) => { + return response.result ? response.result[0] : null; + }); + } - // return login failed if no linking or creation is allowed and no user was found - return ( - -
    -

    Login failed

    -
    - { - - User could not be logged in - - } -
    -
    -
    - ); - } else { - return ( - -
    -

    Login failed

    -
    - { - - Could not get user information - - } -
    -
    -
    - ); - } - }) - .catch((error) => { + if (foundUser) { + const idpLink = await addIDPLink( + { + id: idpInformation.idpId, + userId: idpInformation.userId, + userName: idpInformation.userName, + }, + foundUser.userId, + ).catch((error) => { return (
    -

    An error occurred

    +

    {t("linkingError.title")}

    { - {JSON.stringify(error.message)} + {t("linkingError.description")} }
    @@ -186,16 +133,36 @@ export default async function Page({ ); }); - } else { - return ( - -
    -
    -

    Register

    -

    No id and token received!

    -
    -
    -
    - ); + + if (idpLink) { + return ( + // TODO: possibily login user now + +
    +

    {t("linkingSuccess.title")}

    +
    {t("linkingSuccess.description")}
    +
    +
    + ); + } + } } + + if (options?.isCreationAllowed && options.isAutoCreation) { + const newUser = await createUser(providerType, idpInformation); + + if (newUser) { + return ( + +
    +

    {t("registerSuccess.title")}

    +
    {t("registerSuccess.description")}
    +
    +
    + ); + } + } + + // return login failed if no linking or creation is allowed and no user was found + return loginFailed; } diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index 8a2d65f380..addbd26294 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -2,6 +2,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { getBrandingSettings, settingsService } from "@/lib/zitadel"; import { makeReqCtx } from "@zitadel/client/v2"; +import { useTranslations } from "next-intl"; function getIdentityProviders(orgId?: string) { return settingsService @@ -16,6 +17,7 @@ export default async function Page({ }: { searchParams: Record; }) { + const t = useTranslations("idp"); const authRequestId = searchParams?.authRequestId; const organization = searchParams?.organization; @@ -30,10 +32,8 @@ export default async function Page({ return (
    -

    Sign in with SSO

    -

    - Select one of the following providers to sign in -

    +

    {t("title")}

    +

    {t("description")}

    {identityProviders && ( ; }) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "password" }); + const { loginName, organization, authRequestId, alt } = searchParams; // also allow no session to be found (ignoreUnkownUsername) @@ -31,17 +35,14 @@ export default async function Page({ return (
    -

    {sessionFactors?.factors?.user?.displayName ?? "Password"}

    -

    Enter your password.

    +

    {sessionFactors?.factors?.user?.displayName ?? t("title")}

    +

    {t("description")}

    {/* show error only if usernames should be shown to be unknown */} {(!sessionFactors || !loginName) && !loginSettings?.ignoreUnknownUsernames && (
    - - Could not get the context of the user. Make sure to enter the - username first or provide a loginName as searchParam. - + {t("error:unknownContext")}
    )} diff --git a/apps/login/src/components/back-button.tsx b/apps/login/src/components/back-button.tsx index 5697877bd5..fe348af9c4 100644 --- a/apps/login/src/components/back-button.tsx +++ b/apps/login/src/components/back-button.tsx @@ -1,9 +1,11 @@ "use client"; +import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { Button, ButtonVariants } from "./button"; export function BackButton() { + const t = useTranslations("common"); const router = useRouter(); return ( ); } diff --git a/apps/login/src/components/idps/sign-in-with-gitlab.test.tsx b/apps/login/src/components/idps/sign-in-with-gitlab.test.tsx index 302251ace2..e35562ad17 100644 --- a/apps/login/src/components/idps/sign-in-with-gitlab.test.tsx +++ b/apps/login/src/components/idps/sign-in-with-gitlab.test.tsx @@ -1,24 +1,35 @@ import { afterEach, describe, expect, test } from "vitest"; import { cleanup, render, screen } from "@testing-library/react"; +import { NextIntlClientProvider } from "next-intl"; import { SignInWithGitlab } from "./sign-in-with-gitlab"; afterEach(cleanup); -describe("", () => { +describe("", async () => { + const messages = await getMessages({ locale: "en" }); + test("renders without crashing", () => { const { container } = render(); expect(container.firstChild).toBeDefined(); }); test("displays the default text", () => { - render(); + render( + + + , + ); const signInText = screen.getByText(/Sign in with Gitlab/i); expect(signInText).toBeInTheDocument(); }); test("displays the given text", () => { - render(); + render( + + + , + ); const signInText = screen.getByText(/Gitlab/i); expect(signInText).toBeInTheDocument(); }); diff --git a/apps/login/src/components/idps/sign-in-with-google.test.tsx b/apps/login/src/components/idps/sign-in-with-google.test.tsx index b67dc004e2..9d1056046f 100644 --- a/apps/login/src/components/idps/sign-in-with-google.test.tsx +++ b/apps/login/src/components/idps/sign-in-with-google.test.tsx @@ -1,24 +1,36 @@ import { afterEach, describe, expect, test } from "vitest"; import { cleanup, render, screen } from "@testing-library/react"; +import { NextIntlClientProvider } from "next-intl"; +import { getMessages } from "next-intl/server"; import { SignInWithGoogle } from "./sign-in-with-google"; afterEach(cleanup); -describe("", () => { +describe("", async () => { + const messages = await getMessages({ locale: "en" }); + test("renders without crashing", () => { const { container } = render(); expect(container.firstChild).toBeDefined(); }); test("displays the default text", () => { - render(); + render( + + + , + ); const signInText = screen.getByText(/Sign in with Google/i); expect(signInText).toBeInTheDocument(); }); test("displays the given text", () => { - render(); + render( + + + , + ); const signInText = screen.getByText(/Google/i); expect(signInText).toBeInTheDocument(); }); diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index 0aacd0a6a1..c0b5058249 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -5,6 +5,7 @@ import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -35,6 +36,8 @@ export function PasswordForm({ promptPasswordless, isAlternative, }: Props) { + const t = useTranslations("password"); + const { register, handleSubmit, formState } = useForm({ mode: "onBlur", }); @@ -240,7 +243,7 @@ export function PasswordForm({ type="button" disabled={loading} > - Reset Password + {t("resetPassword")} )} @@ -277,7 +280,7 @@ export function PasswordForm({ onClick={handleSubmit(submitPasswordAndContinue)} > {loading && } - continue + {t("submit")}
    diff --git a/apps/login/src/components/sessions-list.tsx b/apps/login/src/components/sessions-list.tsx index a1a971fc9c..85c67e4b02 100644 --- a/apps/login/src/components/sessions-list.tsx +++ b/apps/login/src/components/sessions-list.tsx @@ -1,6 +1,7 @@ "use client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { useTranslations } from "next-intl"; import { useState } from "react"; import { Alert } from "./alert"; import { SessionItem } from "./session-item"; @@ -11,6 +12,7 @@ type Props = { }; export function SessionsList({ sessions, authRequestId }: Props) { + const t = useTranslations("accounts"); const [list, setList] = useState(sessions); return sessions ? (
    @@ -30,6 +32,6 @@ export function SessionsList({ sessions, authRequestId }: Props) { })}
    ) : ( - No Sessions available! + {t("noResults")} ); } diff --git a/apps/login/src/components/username-form.tsx b/apps/login/src/components/username-form.tsx index b67cb5f5cd..80780a8ede 100644 --- a/apps/login/src/components/username-form.tsx +++ b/apps/login/src/components/username-form.tsx @@ -98,7 +98,7 @@ export function UsernameForm({ type="button" disabled={loading} > - {t("registerButton")} + {t("register")} )}
    From b94d60dd86a811522331ebaeaaa980da164da16e Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 9 Oct 2024 15:39:41 +0200 Subject: [PATCH 246/640] unit --- .../src/components/idps/sign-in-with-gitlab.test.tsx | 11 ++++++++--- .../src/components/idps/sign-in-with-google.test.tsx | 6 +++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/login/src/components/idps/sign-in-with-gitlab.test.tsx b/apps/login/src/components/idps/sign-in-with-gitlab.test.tsx index e35562ad17..03b0ebddc4 100644 --- a/apps/login/src/components/idps/sign-in-with-gitlab.test.tsx +++ b/apps/login/src/components/idps/sign-in-with-gitlab.test.tsx @@ -1,16 +1,21 @@ import { afterEach, describe, expect, test } from "vitest"; import { cleanup, render, screen } from "@testing-library/react"; -import { NextIntlClientProvider } from "next-intl"; +import { NextIntlClientProvider, useMessages } from "next-intl"; + import { SignInWithGitlab } from "./sign-in-with-gitlab"; afterEach(cleanup); describe("", async () => { - const messages = await getMessages({ locale: "en" }); + const messages = useMessages(); test("renders without crashing", () => { - const { container } = render(); + const { container } = render( + + + , + ); expect(container.firstChild).toBeDefined(); }); diff --git a/apps/login/src/components/idps/sign-in-with-google.test.tsx b/apps/login/src/components/idps/sign-in-with-google.test.tsx index 9d1056046f..f1a20c46e8 100644 --- a/apps/login/src/components/idps/sign-in-with-google.test.tsx +++ b/apps/login/src/components/idps/sign-in-with-google.test.tsx @@ -11,7 +11,11 @@ describe("", async () => { const messages = await getMessages({ locale: "en" }); test("renders without crashing", () => { - const { container } = render(); + const { container } = render( + + + , + ); expect(container.firstChild).toBeDefined(); }); From b2e8384902c699d229496c679a1ef21886d71c90 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 9 Oct 2024 15:48:09 +0200 Subject: [PATCH 247/640] mfa, idp i18n --- apps/login/locales/en.json | 14 ++++++- apps/login/src/app/(login)/accounts/page.tsx | 6 ++- .../(login)/idp/[provider]/failure/page.tsx | 38 ++++++------------- apps/login/src/app/(login)/idp/page.tsx | 6 ++- apps/login/src/app/(login)/mfa/page.tsx | 12 ++++-- apps/login/src/app/(login)/mfa/set/page.tsx | 17 ++++----- 6 files changed, 48 insertions(+), 45 deletions(-) diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index fc7808e44f..e4650e02ca 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -48,7 +48,19 @@ "description": "An error occurred while trying to link your account." } }, + "mfa": { + "verify": { + "title": "Verify your identity", + "description": "Choose one of the following factors.", + "noResults": "No second factors available to setup." + }, + "set": { + "title": "Set up 2-Factor", + "description": "Choose one of the following second factors." + } + }, "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.", + "sessionExpired": "You need to have a valid session in order to continue." } } diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index e412dc6d49..d8813fada0 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -3,7 +3,7 @@ import { SessionsList } from "@/components/sessions-list"; import { getAllSessionCookieIds } from "@/lib/cookies"; import { getBrandingSettings, listSessions } from "@/lib/zitadel"; import { UserPlusIcon } from "@heroicons/react/24/outline"; -import { useTranslations } from "next-intl"; +import { getLocale, getTranslations } from "next-intl/server"; import Link from "next/link"; async function loadSessions() { @@ -25,7 +25,9 @@ export default async function Page({ }: { searchParams: Record; }) { - const t = useTranslations("accounts"); + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "accounts" }); + const authRequestId = searchParams?.authRequestId; const organization = searchParams?.organization; diff --git a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx index 237feebecc..42dcba25fa 100644 --- a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -1,6 +1,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { getBrandingSettings } from "@/lib/zitadel"; import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { getLocale, getTranslations } from "next-intl/server"; // This configuration shows the given name in the respective IDP button as fallback const PROVIDER_NAME_MAPPING: { @@ -18,36 +19,19 @@ export default async function Page({ searchParams: Record; params: { provider: string }; }) { - const { id, token, authRequestId, organization } = searchParams; - const { provider } = params; + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "idp" }); + + const { organization } = searchParams; const branding = await getBrandingSettings(organization); - if (provider) { - return ( - -
    -

    Login failure

    -
    - An error signing in with{" "} - {PROVIDER_NAME_MAPPING[provider] - ? PROVIDER_NAME_MAPPING[provider] - : provider}{" "} - happened! -
    - - {/* - {} - */} -
    -
    - ); - } else { - return ( + return ( +
    -

    Register

    -

    No id and token received!

    +

    {t("loginError.title")}

    +
    {t("loginError.description")}
    - ); - } +
    + ); } diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index addbd26294..ee43d5582c 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -2,7 +2,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { getBrandingSettings, settingsService } from "@/lib/zitadel"; import { makeReqCtx } from "@zitadel/client/v2"; -import { useTranslations } from "next-intl"; +import { getLocale, getTranslations } from "next-intl/server"; function getIdentityProviders(orgId?: string) { return settingsService @@ -17,7 +17,9 @@ export default async function Page({ }: { searchParams: Record; }) { - const t = useTranslations("idp"); + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "idp" }); + const authRequestId = searchParams?.authRequestId; const organization = searchParams?.organization; diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index 4186311fd3..1caad7b8fe 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -10,12 +10,16 @@ import { getSession, listAuthenticationMethodTypes, } from "@/lib/zitadel"; +import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ searchParams, }: { searchParams: Record; }) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "mfa" }); + const { loginName, authRequestId, organization, sessionId } = searchParams; const sessionFactors = sessionId @@ -67,9 +71,9 @@ export default async function Page({ return (
    -

    Verify 2-Factor

    +

    {t("verify.title")}

    -

    Choose one of the following second factors.

    +

    {t("verify.description")}

    {sessionFactors && ( Provide your active session as loginName param + {t("error.unknownContext")} )} {sessionFactors ? ( @@ -93,7 +97,7 @@ export default async function Page({ userMethods={sessionFactors.authMethods ?? []} > ) : ( - No second factors available to setup. + {t("verify.noResults")} )}
    diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index 5696c724bd..5e2227b1a2 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -14,6 +14,7 @@ import { } from "@/lib/zitadel"; import { Timestamp, timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { getLocale, getTranslations } from "next-intl/server"; function isSessionValid(session: Partial): { valid: boolean; @@ -36,6 +37,9 @@ export default async function Page({ }: { searchParams: Record; }) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "mfa" }); + const { loginName, checkAfter, @@ -104,9 +108,9 @@ export default async function Page({ return (
    -

    Set up 2-Factor

    +

    {t("set.title")}

    -

    Choose one of the following second factors.

    +

    {t("set.description")}

    {sessionWithData && ( Provide your active session as loginName param + {t("error.unknownContext")} )} - {!valid && ( - - You need to have a valid session in order to set a second factor! - {/* TODO: show reauth button */} - - )} + {!valid && {t("error.sessionExpired")}} {isSessionValid(sessionWithData).valid && loginSettings && From b5e81d426f998fa0a6dd2a16a8c9581a1ad5f38f Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 9 Oct 2024 17:03:03 +0200 Subject: [PATCH 248/640] mfa, otp i18n --- apps/login/locales/en.json | 19 +++++++++++++++++++ apps/login/src/app/(login)/mfa/page.tsx | 2 +- apps/login/src/app/(login)/mfa/set/page.tsx | 2 +- .../src/app/(login)/otp/[method]/page.tsx | 17 +++++++++-------- .../src/app/(login)/otp/[method]/set/page.tsx | 17 ++++++++--------- apps/login/src/components/login-otp.tsx | 9 ++++++--- apps/login/src/components/totp-register.tsx | 5 ++++- 7 files changed, 48 insertions(+), 23 deletions(-) diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index e4650e02ca..b4a9162c71 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -59,6 +59,25 @@ "description": "Choose one of the following second factors." } }, + "otp": { + "verify": { + "title": "Verify 2-Factor", + "totpDescription": "Enter the code from your authenticator app.", + "smsDescription": "Enter the code you received via SMS.", + "emailDescription": "Enter the code you received via email.", + "noCodeReceived": "Didn't receive a code?", + "resendCode": "Resend code", + "submit": "Continue" + }, + "set": { + "title": "Set up 2-Factor", + "totpDescription": "Scan the QR code with your authenticator app.", + "smsDescription": "Enter your phone number to receive a code via SMS.", + "emailDescription": "Enter your email address to receive a code via email.", + "totpRegisterDescription": "Scan the QR Code or navigate to the URL manually.", + "submit": "Continue" + } + }, "error": { "unknownContext": "Could not get the context of the user. Make sure to enter the username first or provide a loginName as searchParam.", "sessionExpired": "You need to have a valid session in order to continue." diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index 1caad7b8fe..38b3e57cf9 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -85,7 +85,7 @@ export default async function Page({ )} {!(loginName || sessionId) && ( - {t("error.unknownContext")} + {t("error:unknownContext")} )} {sessionFactors ? ( diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index 5e2227b1a2..5af145350a 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -122,7 +122,7 @@ export default async function Page({ )} {!(loginName || sessionId) && ( - {t("error.unknownContext")} + {t("error:unknownContext")} )} {!valid && {t("error.sessionExpired")}} diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index db1efa2a00..d1071dbcad 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -4,6 +4,7 @@ import { LoginOTP } from "@/components/login-otp"; import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; +import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ searchParams, @@ -12,6 +13,9 @@ export default async function Page({ searchParams: Record; params: Record; }) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "otp" }); + const { loginName, authRequestId, sessionId, organization, code, submit } = searchParams; @@ -27,23 +31,20 @@ export default async function Page({ return (
    -

    Verify 2-Factor

    +

    {t("verify.title")}

    {method === "time-based" && ( -

    Enter the code from your authenticator app.

    +

    {t("verify.totpDescription")}

    )} {method === "sms" && ( -

    Enter the code you got on your phone.

    +

    {t("verify.smsDescription")}

    )} {method === "email" && ( -

    Enter the code you got via your email.

    +

    {t("verify.emailDescription")}

    )} {!session && (
    - - Could not get the context of the user. Make sure to enter the - username first or provide a loginName as searchParam. - + {t("error:unknownContext")}
    )} diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index 3e1b4dca8b..7a9c28cbbd 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -12,6 +12,7 @@ import { registerTOTP, } from "@/lib/zitadel"; import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { getLocale, getTranslations } from "next-intl/server"; import Link from "next/link"; import { redirect } from "next/navigation"; @@ -22,6 +23,9 @@ export default async function Page({ searchParams: Record; params: Record; }) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "otp" }); + const { loginName, organization, sessionId, authRequestId, checkAfter } = searchParams; const { method } = params; @@ -98,13 +102,10 @@ export default async function Page({ return (
    -

    Register 2-factor

    +

    {t("set.title")}

    {!session && (
    - - Could not get the context of the user. Make sure to enter the - username first or provide a loginName as searchParam. - + {t("error:unknownContext")}
    )} @@ -125,9 +126,7 @@ export default async function Page({ {totpResponse && "uri" in totpResponse && "secret" in totpResponse ? ( <> -

    - Scan the QR Code or navigate to the URL manually. -

    +

    {t("set.totpRegisterDescription")}

    - continue + {t("set.submit")}
    diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 5f11390739..e131e2446b 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -4,6 +4,7 @@ import { updateSession } from "@/lib/server/session"; import { create } from "@zitadel/client"; import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; @@ -35,6 +36,8 @@ export function LoginOTP({ method, code, }: Props) { + const t = useTranslations("otp"); + const [error, setError] = useState(""); const [loading, setLoading] = useState(false); @@ -191,7 +194,7 @@ export function LoginOTP({
    - Did not get the Code? + {t("noCodeReceived")}
    @@ -241,7 +244,7 @@ export function LoginOTP({ })} > {loading && } - continue + {t("submit")}
    diff --git a/apps/login/src/components/totp-register.tsx b/apps/login/src/components/totp-register.tsx index 7bcb697b22..450cb5bfa7 100644 --- a/apps/login/src/components/totp-register.tsx +++ b/apps/login/src/components/totp-register.tsx @@ -1,5 +1,6 @@ "use client"; import { verifyTOTP } from "@/lib/server-actions"; +import { useTranslations } from "next-intl"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { QRCodeSVG } from "qrcode.react"; @@ -33,6 +34,8 @@ export function TotpRegister({ organization, checkAfter, }: Props) { + const t = useTranslations("otp"); + const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const router = useRouter(); @@ -138,7 +141,7 @@ export function TotpRegister({ onClick={handleSubmit(continueWithCode)} > {loading && } - continue + {t("set.submit")}
    From b8be02a2784adc0bdd67577aa9f281b8e54233db Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 10 Oct 2024 16:15:10 +0200 Subject: [PATCH 249/640] i18n, cleanup layout --- apps/login/locales/en.json | 54 ++++++++ apps/login/src/app/(login)/layout.tsx | 43 +------ apps/login/src/app/(login)/overview/page.tsx | 45 ------- apps/login/src/app/(login)/passkey/page.tsx | 14 +- .../src/app/(login)/passkey/set/page.tsx | 25 ++-- apps/login/src/app/(login)/register/page.tsx | 12 +- apps/login/src/app/(login)/signedin/page.tsx | 10 +- apps/login/src/app/(login)/u2f/page.tsx | 12 +- apps/login/src/app/(login)/u2f/set/page.tsx | 17 +-- apps/login/src/app/(login)/verify/page.tsx | 17 ++- apps/login/src/components/global-nav.tsx | 120 ------------------ apps/login/src/components/login-passkey.tsx | 7 +- .../components/privacy-policy-checkboxes.tsx | 10 +- .../register-form-without-password.tsx | 7 +- .../login/src/components/register-passkey.tsx | 7 +- apps/login/src/components/register-u2f.tsx | 5 +- .../src/components/set-password-form.tsx | 5 +- .../src/components/verify-email-form.tsx | 7 +- 18 files changed, 148 insertions(+), 269 deletions(-) delete mode 100644 apps/login/src/app/(login)/overview/page.tsx delete mode 100644 apps/login/src/components/global-nav.tsx diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index b4a9162c71..5d6f7e57d6 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -78,6 +78,60 @@ "submit": "Continue" } }, + "passkey": { + "verify": { + "title": "Authenticate with a passkey", + "description": "Your device will ask for your fingerprint, face, or screen lock", + "usePassword": "Use password", + "submit": "Continue" + }, + "set": { + "title": "Setup a passkey", + "description": "Your device will ask for your fingerprint, face, or screen lock", + "info": { + "description": "A passkey is an authentication method on a device like your fingerprint, Apple FaceID or similar. ", + "link": "Passwordless Authentication" + }, + "skip": "Skip", + "submit": "Continue" + } + }, + "u2f": { + "verify": { + "title": "Verify 2-Factor", + "description": "Verify your account with your device." + }, + "set": { + "title": "Set up 2-Factor", + "description": "Set up a device as a second factor.", + "submit": "Continue" + } + }, + "register": { + "title": "Register", + "description": "Create your ZITADEL account.", + "selectMethod": "Select the method you would like to authenticate", + "agreeTo": "To register you must agree to the terms and conditions", + "termsOfService": "Terms of Service", + "privacyPolicy": "Privacy Policy", + "submit": "Continue", + "password": { + "title": "Set Password", + "description": "Set the password for your account", + "submit": "Continue" + } + }, + "signedin": { + "title": "Welcome {user}!", + "description": "You are signed in." + }, + "verify": { + "title": "Verify user", + "description": "Enter the Code provided in the verification email.", + "userIdMissing": "No userId provided!", + "resendCode": "Resend code", + "submit": "Continue" + }, "error": { "unknownContext": "Could not get the context of the user. Make sure to enter the username first or provide a loginName as searchParam.", "sessionExpired": "You need to have a valid session in order to continue." diff --git a/apps/login/src/app/(login)/layout.tsx b/apps/login/src/app/(login)/layout.tsx index 50ea287a1c..e218bbcfae 100644 --- a/apps/login/src/app/(login)/layout.tsx +++ b/apps/login/src/app/(login)/layout.tsx @@ -1,7 +1,5 @@ import "@/styles/globals.scss"; -import { AddressBar } from "@/components/address-bar"; -import { GlobalNav } from "@/components/global-nav"; import { LanguageSwitcher } from "@/components/language-switcher"; import { Theme } from "@/components/theme"; import { ThemeProvider } from "@/components/theme-provider"; @@ -23,16 +21,9 @@ export default async function RootLayout({ }: { children: ReactNode; }) { - const locale = (await getLocale()) ?? "en"; - + const locale = await getLocale(); const messages = await getMessages(); - // later only shown with dev mode enabled - const showNav = process.env.DEBUG === "true"; - - let domain = process.env.ZITADEL_API_URL; - domain = domain ? domain.replace("https://", "") : "acme.com"; - return (
    - {showNav ? ( - - ) : ( -
    - - -
    - )} +
    + + +
    -
    +
    - {showNav && ( -
    -
    - -
    -
    - )} - {children}
    diff --git a/apps/login/src/app/(login)/overview/page.tsx b/apps/login/src/app/(login)/overview/page.tsx deleted file mode 100644 index 4cb2227bc9..0000000000 --- a/apps/login/src/app/(login)/overview/page.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { demos } from "@/lib/demos"; -import Link from "next/link"; - -export default function Page() { - return ( -
    -
    -
    -

    Pages

    - -
    - {demos.map((section) => { - return ( -
    -
    - {section.name} -
    -
    - {section.items.map((item) => { - return ( - -
    {item.name}
    - - {item.description ? ( -
    - {item.description} -
    - ) : null} - - ); - })} -
    -
    - ); - })} -
    -
    -
    -
    - ); -} diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index 6cb3e43b84..22a195673b 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -5,16 +5,16 @@ import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; - -const title = "Authenticate with a passkey"; -const description = - "Your device will ask for your fingerprint, face, or screen lock"; +import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ searchParams, }: { searchParams: Record; }) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "passkey" }); + const { loginName, altPassword, authRequestId, organization, sessionId } = searchParams; @@ -39,7 +39,7 @@ export default async function Page({ return (
    -

    {title}

    +

    {t("verify.title")}

    {sessionFactors && ( )} -

    {description}

    +

    {t("verify.description")}

    {!(loginName || sessionId) && ( - Provide your active session as loginName param + {t("error:unknownContext")} )} {(loginName || sessionId) && ( diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx index 140f1d74e2..c56f4605b8 100644 --- a/apps/login/src/app/(login)/passkey/set/page.tsx +++ b/apps/login/src/app/(login)/passkey/set/page.tsx @@ -4,12 +4,16 @@ import { RegisterPasskey } from "@/components/register-passkey"; import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; +import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ searchParams, }: { searchParams: Record; }) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "passkey" }); + const { loginName, prompt, organization, authRequestId } = searchParams; const session = await loadMostRecentSession({ @@ -17,19 +21,12 @@ export default async function Page({ organization, }); - const title = !!prompt - ? "Authenticate with a passkey" - : "Use your passkey to confirm it's really you"; - const description = !!prompt - ? "When set up, you will be able to authenticate without a password." - : "Your device will ask for your fingerprint, face, or screen lock"; - const branding = await getBrandingSettings(organization); return (
    -

    {title}

    +

    {t("set.title")}

    {session && ( )} -

    {description}

    +

    {t("set.description")}

    - A passkey is an authentication method on a device like your - fingerprint, Apple FaceID or similar. + {t("set.info.description")} - Passwordless Authentication + {t("set.info.link")} {!session && (
    - - Could not get the context of the user. Make sure to enter the - username first or provide a loginName as searchParam. - + {t("error:unknownContext")}
    )} diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index ab8a8eddfb..8169d205a0 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -6,12 +6,16 @@ import { getLegalAndSupportSettings, getPasswordComplexitySettings, } from "@/lib/zitadel"; +import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ searchParams, }: { searchParams: Record; }) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "register" }); + const { firstname, lastname, email, organization, authRequestId } = searchParams; @@ -30,8 +34,8 @@ export default async function Page({ return setPassword ? (
    -

    Set Password

    -

    Set the password for your account

    +

    {t("password.title")}

    +

    {t("description")}

    {legal && passwordComplexitySettings && (
    -

    Register

    -

    Create your ZITADEL account.

    +

    {t("title")}

    +

    {t("description")}

    {legal && passwordComplexitySettings && (
    -

    {`Welcome ${sessionFactors?.factors?.user?.displayName}`}

    -

    You are signed in.

    +

    + {t("title", { user: sessionFactors?.factors?.user?.displayName })} +

    +

    {t("description")}

    ; }) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "u2f" }); + const { loginName, authRequestId, sessionId, organization } = searchParams; const branding = await getBrandingSettings(organization); @@ -34,7 +38,7 @@ export default async function Page({ return (
    -

    Verify 2-Factor

    +

    {t("verify.title")}

    {sessionFactors && ( )} -

    - Verify your account with your device. -

    +

    {t("verify.description")}

    {!(loginName || sessionId) && ( - Provide your active session as loginName param + {t("error:unknownContext")} )} {(loginName || sessionId) && ( diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index 97120424e1..ca74c80e63 100644 --- a/apps/login/src/app/(login)/u2f/set/page.tsx +++ b/apps/login/src/app/(login)/u2f/set/page.tsx @@ -4,12 +4,16 @@ import { RegisterU2f } from "@/components/register-u2f"; import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; +import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ searchParams, }: { searchParams: Record; }) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "u2f" }); + const { loginName, organization, authRequestId, checkAfter } = searchParams; const sessionFactors = await loadMostRecentSession({ @@ -17,16 +21,12 @@ export default async function Page({ organization, }); - const title = "Use your passkey to confirm it's really you"; - const description = - "Your device will ask for your fingerprint, face, or screen lock"; - const branding = await getBrandingSettings(organization); return (
    -

    {title}

    +

    {t("set.title")}

    {sessionFactors && ( )} -

    {description}

    +

    {t("set.description")}

    {!sessionFactors && (
    - - Could not get the context of the user. Make sure to enter the - username first or provide a loginName as searchParam. - + {t("error:unknownContext")}
    )} diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 746c2719ed..6ee00a30f8 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -3,8 +3,12 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { VerifyEmailForm } from "@/components/verify-email-form"; import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; +import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ searchParams }: { searchParams: any }) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "verify" }); + const { userId, loginName, @@ -22,17 +26,12 @@ export default async function Page({ searchParams }: { searchParams: any }) { return (
    -

    Verify user

    -

    - Enter the Code provided in the verification email. -

    +

    {t("title")}

    +

    {t("description")}

    {!userId && (
    - - Could not get the context of the user. Make sure to provide a - userId as searchParam. - + {t("error:unknownContext")}
    )} @@ -50,7 +49,7 @@ export default async function Page({ searchParams }: { searchParams: any }) { ) : (
    - No userId provided! + {t("userIdMissing")}
    )}
    diff --git a/apps/login/src/components/global-nav.tsx b/apps/login/src/components/global-nav.tsx deleted file mode 100644 index eab2af91a6..0000000000 --- a/apps/login/src/components/global-nav.tsx +++ /dev/null @@ -1,120 +0,0 @@ -"use client"; - -import { ZitadelLogo } from "@/components/zitadel-logo"; -import { demos, type Item } from "@/lib/demos"; -import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/solid"; -import { clsx } from "clsx"; -import Link from "next/link"; -import { usePathname, useSelectedLayoutSegment } from "next/navigation"; -import { useState } from "react"; -import { Theme } from "./theme"; - -export function GlobalNav() { - const [isOpen, setIsOpen] = useState(false); - const close = () => setIsOpen(false); - - return ( -
    -
    - -
    - -
    - -

    - Login -

    - -
    - -
    - - -
    - -
    - - -
    - -
    -
    -
    - ); -} - -function GlobalNavItem({ - item, - close, -}: { - item: Item; - close: () => false | void; -}) { - const segment = useSelectedLayoutSegment(); - const pathname = usePathname(); - - const isActive = `/${item.slug}` === pathname; - - return ( - - {item.name} - - ); -} diff --git a/apps/login/src/components/login-passkey.tsx b/apps/login/src/components/login-passkey.tsx index b1ee32c4bb..cd79f95e5c 100644 --- a/apps/login/src/components/login-passkey.tsx +++ b/apps/login/src/components/login-passkey.tsx @@ -8,6 +8,7 @@ import { UserVerificationRequirement, } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { Alert } from "./alert"; @@ -33,6 +34,8 @@ export function LoginPasskey({ organization, login = true, }: Props) { + const t = useTranslations("passkey"); + const [error, setError] = useState(""); const [loading, setLoading] = useState(false); @@ -232,7 +235,7 @@ export function LoginPasskey({ ); }} > - use password + {t("verify.usePassword")} ) : ( @@ -267,7 +270,7 @@ export function LoginPasskey({ }} > {loading && } - continue + {t("verify.submit")}
    diff --git a/apps/login/src/components/privacy-policy-checkboxes.tsx b/apps/login/src/components/privacy-policy-checkboxes.tsx index 77ae92a90b..fc37e7f171 100644 --- a/apps/login/src/components/privacy-policy-checkboxes.tsx +++ b/apps/login/src/components/privacy-policy-checkboxes.tsx @@ -1,5 +1,6 @@ "use client"; import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; +import { useTranslations } from "next-intl"; import Link from "next/link"; import { useState } from "react"; import { Checkbox } from "./checkbox"; @@ -15,6 +16,7 @@ type AcceptanceState = { }; export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) { + const t = useTranslations("register"); const [acceptanceState, setAcceptanceState] = useState({ tosAccepted: false, privacyPolicyAccepted: false, @@ -23,7 +25,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) { return ( <>

    - To register you must agree to the terms and conditions + {t("agreeTo")} {legal?.helpLink && ( @@ -62,9 +64,8 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {

    - Agree  - Terms of Service + {t("termsOfService")}

    @@ -87,13 +88,12 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {

    - Agree  - Privacy Policy + {t("privacyPolicy")}

    diff --git a/apps/login/src/components/register-form-without-password.tsx b/apps/login/src/components/register-form-without-password.tsx index 517fe06ec5..df1cad2caa 100644 --- a/apps/login/src/components/register-form-without-password.tsx +++ b/apps/login/src/components/register-form-without-password.tsx @@ -2,6 +2,7 @@ import { registerUser, RegisterUserResponse } from "@/lib/server/register"; import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; +import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; @@ -41,6 +42,8 @@ export function RegisterFormWithoutPassword({ organization, authRequestId, }: Props) { + const t = useTranslations("register"); + const { register, handleSubmit, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -160,7 +163,7 @@ export function RegisterFormWithoutPassword({ )}

    - Select the method you would like to authenticate + {t("selectMethod")}

    @@ -187,7 +190,7 @@ export function RegisterFormWithoutPassword({ )} > {loading && } - continue + {t("submit")}
    diff --git a/apps/login/src/components/register-passkey.tsx b/apps/login/src/components/register-passkey.tsx index 18684c23e8..e067b38743 100644 --- a/apps/login/src/components/register-passkey.tsx +++ b/apps/login/src/components/register-passkey.tsx @@ -2,6 +2,7 @@ import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64"; import { registerPasskeyLink, verifyPasskey } from "@/lib/server/passkeys"; +import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -25,6 +26,8 @@ export function RegisterPasskey({ organization, authRequestId, }: Props) { + const t = useTranslations("passkey"); + const { handleSubmit, formState } = useForm({ mode: "onBlur", }); @@ -173,7 +176,7 @@ export function RegisterPasskey({ continueAndLogin(); }} > - skip + {t("set.skip")} ) : ( @@ -188,7 +191,7 @@ export function RegisterPasskey({ onClick={handleSubmit(submitRegisterAndContinue)} > {loading && } - continue + {t("set.submit")}
    diff --git a/apps/login/src/components/register-u2f.tsx b/apps/login/src/components/register-u2f.tsx index 77b54fd855..2f294ea070 100644 --- a/apps/login/src/components/register-u2f.tsx +++ b/apps/login/src/components/register-u2f.tsx @@ -3,6 +3,7 @@ import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64"; import { addU2F, verifyU2F } from "@/lib/server/u2f"; import { RegisterU2FResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { Alert } from "./alert"; @@ -25,6 +26,8 @@ export function RegisterU2f({ authRequestId, checkAfter, }: Props) { + const t = useTranslations("u2f"); + const [error, setError] = useState(""); const [loading, setLoading] = useState(false); @@ -198,7 +201,7 @@ export function RegisterU2f({ onClick={submitRegisterAndContinue} > {loading && } - continue + {t("set.submit")}
    diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index e271719231..507393c1a9 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -8,6 +8,7 @@ import { } from "@/helpers/validators"; import { registerUser, RegisterUserResponse } from "@/lib/server/register"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; +import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; @@ -42,6 +43,8 @@ export function SetPasswordForm({ organization, authRequestId, }: Props) { + const t = useTranslations("register"); + const { register, handleSubmit, watch, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -186,7 +189,7 @@ export function SetPasswordForm({ onClick={handleSubmit(submitRegister)} > {loading && } - continue + {t("password.submit")}
    diff --git a/apps/login/src/components/verify-email-form.tsx b/apps/login/src/components/verify-email-form.tsx index cbc66b1980..77cb9d9771 100644 --- a/apps/login/src/components/verify-email-form.tsx +++ b/apps/login/src/components/verify-email-form.tsx @@ -3,6 +3,7 @@ import { Alert } from "@/components/alert"; import { resendVerifyEmail, verifyUserByEmail } from "@/lib/server/email"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; @@ -35,6 +36,8 @@ export function VerifyEmailForm({ sessionId, loginSettings, }: Props) { + const t = useTranslations("verify"); + const { register, handleSubmit, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -123,7 +126,7 @@ export function VerifyEmailForm({ onClick={() => resendCode()} variant={ButtonVariants.Secondary} > - resend code + {t("resendCode")}
    From 043dad972de6eafef424deaebae4e1d6262cecce Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 10 Oct 2024 16:45:28 +0200 Subject: [PATCH 250/640] de, es, it --- apps/login/locales/de.json | 133 ++++++++++++++++- apps/login/locales/es.json | 139 ++++++++++++++++++ apps/login/locales/it.json | 139 ++++++++++++++++++ .../src/components/language-switcher.tsx | 18 ++- 4 files changed, 421 insertions(+), 8 deletions(-) create mode 100644 apps/login/locales/es.json create mode 100644 apps/login/locales/it.json diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 40b532fa4a..3d09858cae 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -1,10 +1,139 @@ { + "common": { + "back": "Zurück" + }, + "accounts": { + "title": "Konten", + "description": "Wählen Sie das Konto aus, das Sie verwenden möchten.", + "addAnother": "Ein weiteres Konto hinzufügen", + "noResults": "Keine Konten gefunden" + }, "loginname": { "title": "Willkommen zurück!", - "description": "Geben Sie Ihre Anmeldedaten ein." + "description": "Geben Sie Ihre Anmeldedaten ein.", + "register": "Neuen Benutzer registrieren" + }, + "password": { + "title": "Passwort", + "description": "Geben Sie Ihr Passwort ein.", + "resetPassword": "Passwort zurücksetzen", + "submit": "Weiter" }, "idp": { + "title": "Mit SSO anmelden", + "description": "Wählen Sie einen der folgenden Anbieter, um sich anzumelden", "signInWithApple": "Mit Apple anmelden", - "signInWithGoogle": "Mit Google anmelden" + "signInWithGoogle": "Mit Google anmelden", + "signInWithAzureAD": "Mit AzureAD anmelden", + "signInWithGithub": "Mit GitHub anmelden", + "signInWithGitlab": "Mit GitLab anmelden", + "loginSuccess": { + "title": "Anmeldung erfolgreich", + "description": "Sie haben sich erfolgreich angemeldet!" + }, + "linkingSuccess": { + "title": "Konto verknüpft", + "description": "Sie haben Ihr Konto erfolgreich verknüpft!" + }, + "registerSuccess": { + "title": "Registrierung erfolgreich", + "description": "Sie haben sich erfolgreich registriert!" + }, + "loginError": { + "title": "Anmeldung fehlgeschlagen", + "description": "Beim Anmelden ist ein Fehler aufgetreten." + }, + "linkingError": { + "title": "Konto-Verknüpfung fehlgeschlagen", + "description": "Beim Verknüpfen Ihres Kontos ist ein Fehler aufgetreten." + } + }, + "mfa": { + "verify": { + "title": "Bestätigen Sie Ihre Identität", + "description": "Wählen Sie einen der folgenden Faktoren.", + "noResults": "Keine zweiten Faktoren verfügbar, um sie einzurichten." + }, + "set": { + "title": "2-Faktor einrichten", + "description": "Wählen Sie einen der folgenden zweiten Faktoren." + } + }, + "otp": { + "verify": { + "title": "2-Faktor bestätigen", + "totpDescription": "Geben Sie den Code aus Ihrer Authentifizierungs-App ein.", + "smsDescription": "Geben Sie den Code ein, den Sie per SMS erhalten haben.", + "emailDescription": "Geben Sie den Code ein, den Sie per E-Mail erhalten haben.", + "noCodeReceived": "Keinen Code erhalten?", + "resendCode": "Code erneut senden", + "submit": "Weiter" + }, + "set": { + "title": "2-Faktor einrichten", + "totpDescription": "Scannen Sie den QR-Code mit Ihrer Authentifizierungs-App.", + "smsDescription": "Geben Sie Ihre Telefonnummer ein, um einen Code per SMS zu erhalten.", + "emailDescription": "Geben Sie Ihre E-Mail-Adresse ein, um einen Code per E-Mail zu erhalten.", + "totpRegisterDescription": "Scannen Sie den QR-Code oder navigieren Sie manuell zur URL.", + "submit": "Weiter" + } + }, + "passkey": { + "verify": { + "title": "Mit einem Passkey authentifizieren", + "description": "Ihr Gerät wird nach Ihrem Fingerabdruck, Gesicht oder Bildschirmsperre fragen", + "usePassword": "Passwort verwenden", + "submit": "Weiter" + }, + "set": { + "title": "Passkey einrichten", + "description": "Ihr Gerät wird nach Ihrem Fingerabdruck, Gesicht oder Bildschirmsperre fragen", + "info": { + "description": "Ein Passkey ist eine Authentifizierungsmethode auf einem Gerät wie Ihr Fingerabdruck, Apple FaceID oder ähnliches.", + "link": "Passwortlose Authentifizierung" + }, + "skip": "Überspringen", + "submit": "Weiter" + } + }, + "u2f": { + "verify": { + "title": "2-Faktor bestätigen", + "description": "Bestätigen Sie Ihr Konto mit Ihrem Gerät." + }, + "set": { + "title": "2-Faktor einrichten", + "description": "Richten Sie ein Gerät als zweiten Faktor ein.", + "submit": "Weiter" + } + }, + "register": { + "title": "Registrieren", + "description": "Erstellen Sie Ihr ZITADEL-Konto.", + "selectMethod": "Wählen Sie die Methode, mit der Sie sich authentifizieren möchten", + "agreeTo": "Um sich zu registrieren, müssen Sie den Nutzungsbedingungen zustimmen", + "termsOfService": "Nutzungsbedingungen", + "privacyPolicy": "Datenschutzrichtlinie", + "submit": "Weiter", + "password": { + "title": "Passwort festlegen", + "description": "Legen Sie das Passwort für Ihr Konto fest", + "submit": "Weiter" + } + }, + "signedin": { + "title": "Willkommen {user}!", + "description": "Sie sind angemeldet." + }, + "verify": { + "title": "Benutzer verifizieren", + "description": "Geben Sie den Code ein, der in der Bestätigungs-E-Mail angegeben ist.", + "userIdMissing": "Keine Benutzer-ID angegeben!", + "resendCode": "Code erneut senden", + "submit": "Weiter" + }, + "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.", + "sessionExpired": "Sie müssen eine gültige Sitzung haben, um fortzufahren." } } diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json new file mode 100644 index 0000000000..12f042fcd6 --- /dev/null +++ b/apps/login/locales/es.json @@ -0,0 +1,139 @@ +{ + "common": { + "back": "Atrás" + }, + "accounts": { + "title": "Cuentas", + "description": "Selecciona la cuenta que deseas usar.", + "addAnother": "Agregar otra cuenta", + "noResults": "No se encontraron cuentas" + }, + "loginname": { + "title": "¡Bienvenido de nuevo!", + "description": "Introduce tus datos de acceso.", + "register": "Registrar nuevo usuario" + }, + "password": { + "title": "Contraseña", + "description": "Introduce tu contraseña.", + "resetPassword": "Restablecer Contraseña", + "submit": "Continuar" + }, + "idp": { + "title": "Iniciar sesión con SSO", + "description": "Selecciona uno de los siguientes proveedores para iniciar sesión", + "signInWithApple": "Iniciar sesión con Apple", + "signInWithGoogle": "Iniciar sesión con Google", + "signInWithAzureAD": "Iniciar sesión con AzureAD", + "signInWithGithub": "Iniciar sesión con GitHub", + "signInWithGitlab": "Iniciar sesión con GitLab", + "loginSuccess": { + "title": "Inicio de sesión exitoso", + "description": "¡Has iniciado sesión con éxito!" + }, + "linkingSuccess": { + "title": "Cuenta vinculada", + "description": "¡Has vinculado tu cuenta con éxito!" + }, + "registerSuccess": { + "title": "Registro exitoso", + "description": "¡Te has registrado con éxito!" + }, + "loginError": { + "title": "Error de inicio de sesión", + "description": "Ocurrió un error al intentar iniciar sesión." + }, + "linkingError": { + "title": "Error al vincular la cuenta", + "description": "Ocurrió un error al intentar vincular tu cuenta." + } + }, + "mfa": { + "verify": { + "title": "Verifica tu identidad", + "description": "Elige uno de los siguientes factores.", + "noResults": "No hay factores secundarios disponibles para configurar." + }, + "set": { + "title": "Configurar autenticación de 2 factores", + "description": "Elige uno de los siguientes factores secundarios." + } + }, + "otp": { + "verify": { + "title": "Verificar autenticación de 2 factores", + "totpDescription": "Introduce el código de tu aplicación de autenticación.", + "smsDescription": "Introduce el código que recibiste por SMS.", + "emailDescription": "Introduce el código que recibiste por correo electrónico.", + "noCodeReceived": "¿No recibiste un código?", + "resendCode": "Reenviar código", + "submit": "Continuar" + }, + "set": { + "title": "Configurar autenticación de 2 factores", + "totpDescription": "Escanea el código QR con tu aplicación de autenticación.", + "smsDescription": "Introduce tu número de teléfono para recibir un código por SMS.", + "emailDescription": "Introduce tu dirección de correo electrónico para recibir un código por correo electrónico.", + "totpRegisterDescription": "Escanea el código QR o navega manualmente a la URL.", + "submit": "Continuar" + } + }, + "passkey": { + "verify": { + "title": "Autenticar con una clave de acceso", + "description": "Tu dispositivo pedirá tu huella digital, rostro o bloqueo de pantalla", + "usePassword": "Usar contraseña", + "submit": "Continuar" + }, + "set": { + "title": "Configurar una clave de acceso", + "description": "Tu dispositivo pedirá tu huella digital, rostro o bloqueo de pantalla", + "info": { + "description": "Una clave de acceso es un método de autenticación en un dispositivo como tu huella digital, Apple FaceID o similar.", + "link": "Autenticación sin contraseña" + }, + "skip": "Omitir", + "submit": "Continuar" + } + }, + "u2f": { + "verify": { + "title": "Verificar autenticación de 2 factores", + "description": "Verifica tu cuenta con tu dispositivo." + }, + "set": { + "title": "Configurar autenticación de 2 factores", + "description": "Configura un dispositivo como segundo factor.", + "submit": "Continuar" + } + }, + "register": { + "title": "Registrarse", + "description": "Crea tu cuenta ZITADEL.", + "selectMethod": "Selecciona el método con el que deseas autenticarte", + "agreeTo": "Para registrarte debes aceptar los términos y condiciones", + "termsOfService": "Términos de Servicio", + "privacyPolicy": "Política de Privacidad", + "submit": "Continuar", + "password": { + "title": "Establecer Contraseña", + "description": "Establece la contraseña para tu cuenta", + "submit": "Continuar" + } + }, + "signedin": { + "title": "¡Bienvenido {user}!", + "description": "Has iniciado sesión." + }, + "verify": { + "title": "Verificar usuario", + "description": "Introduce el código proporcionado en el correo electrónico de verificación.", + "userIdMissing": "¡No se proporcionó userId!", + "resendCode": "Reenviar código", + "submit": "Continuar" + }, + "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.", + "sessionExpired": "Necesitas tener una sesión válida para continuar." + } +} diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json new file mode 100644 index 0000000000..a43424ff9c --- /dev/null +++ b/apps/login/locales/it.json @@ -0,0 +1,139 @@ +{ + "common": { + "back": "Indietro" + }, + "accounts": { + "title": "Account", + "description": "Seleziona l'account che desideri utilizzare.", + "addAnother": "Aggiungi un altro account", + "noResults": "Nessun account trovato" + }, + "loginname": { + "title": "Bentornato!", + "description": "Inserisci i tuoi dati di accesso.", + "register": "Registrati come nuovo utente" + }, + "password": { + "title": "Password", + "description": "Inserisci la tua password.", + "resetPassword": "Reimposta Password", + "submit": "Continua" + }, + "idp": { + "title": "Accedi con SSO", + "description": "Seleziona uno dei seguenti provider per accedere", + "signInWithApple": "Accedi con Apple", + "signInWithGoogle": "Accedi con Google", + "signInWithAzureAD": "Accedi con AzureAD", + "signInWithGithub": "Accedi con GitHub", + "signInWithGitlab": "Accedi con GitLab", + "loginSuccess": { + "title": "Accesso riuscito", + "description": "Accesso effettuato con successo!" + }, + "linkingSuccess": { + "title": "Account collegato", + "description": "Hai collegato con successo il tuo account!" + }, + "registerSuccess": { + "title": "Registrazione riuscita", + "description": "Registrazione effettuata con successo!" + }, + "loginError": { + "title": "Accesso fallito", + "description": "Si è verificato un errore durante il tentativo di accesso." + }, + "linkingError": { + "title": "Collegamento account fallito", + "description": "Si è verificato un errore durante il tentativo di collegare il tuo account." + } + }, + "mfa": { + "verify": { + "title": "Verifica la tua identità", + "description": "Scegli uno dei seguenti fattori.", + "noResults": "Nessun secondo fattore disponibile per la configurazione." + }, + "set": { + "title": "Configura l'autenticazione a 2 fattori", + "description": "Scegli uno dei seguenti secondi fattori." + } + }, + "otp": { + "verify": { + "title": "Verifica l'autenticazione a 2 fattori", + "totpDescription": "Inserisci il codice dalla tua app di autenticazione.", + "smsDescription": "Inserisci il codice ricevuto via SMS.", + "emailDescription": "Inserisci il codice ricevuto via email.", + "noCodeReceived": "Non hai ricevuto un codice?", + "resendCode": "Invia di nuovo il codice", + "submit": "Continua" + }, + "set": { + "title": "Configura l'autenticazione a 2 fattori", + "totpDescription": "Scansiona il codice QR con la tua app di autenticazione.", + "smsDescription": "Inserisci il tuo numero di telefono per ricevere un codice via SMS.", + "emailDescription": "Inserisci il tuo indirizzo email per ricevere un codice via email.", + "totpRegisterDescription": "Scansiona il codice QR o naviga manualmente all'URL.", + "submit": "Continua" + } + }, + "passkey": { + "verify": { + "title": "Autenticati con una passkey", + "description": "Il tuo dispositivo chiederà la tua impronta digitale, il volto o il blocco schermo", + "usePassword": "Usa password", + "submit": "Continua" + }, + "set": { + "title": "Configura una passkey", + "description": "Il tuo dispositivo chiederà la tua impronta digitale, il volto o il blocco schermo", + "info": { + "description": "Una passkey è un metodo di autenticazione su un dispositivo come la tua impronta digitale, Apple FaceID o simili.", + "link": "Autenticazione senza password" + }, + "skip": "Salta", + "submit": "Continua" + } + }, + "u2f": { + "verify": { + "title": "Verifica l'autenticazione a 2 fattori", + "description": "Verifica il tuo account con il tuo dispositivo." + }, + "set": { + "title": "Configura l'autenticazione a 2 fattori", + "description": "Configura un dispositivo come secondo fattore.", + "submit": "Continua" + } + }, + "register": { + "title": "Registrati", + "description": "Crea il tuo account ZITADEL.", + "selectMethod": "Seleziona il metodo con cui desideri autenticarti", + "agreeTo": "Per registrarti devi accettare i termini e le condizioni", + "termsOfService": "Termini di Servizio", + "privacyPolicy": "Informativa sulla Privacy", + "submit": "Continua", + "password": { + "title": "Imposta Password", + "description": "Imposta la password per il tuo account", + "submit": "Continua" + } + }, + "signedin": { + "title": "Benvenuto {user}!", + "description": "Sei connesso." + }, + "verify": { + "title": "Verifica utente", + "description": "Inserisci il codice fornito nell'email di verifica.", + "userIdMissing": "Nessun userId fornito!", + "resendCode": "Invia di nuovo il codice", + "submit": "Continua" + }, + "error": { + "unknownContext": "Impossibile ottenere il contesto dell'utente. Assicurati di inserire prima il nome utente o di fornire un loginName come parametro di ricerca.", + "sessionExpired": "Devi avere una sessione valida per continuare." + } +} diff --git a/apps/login/src/components/language-switcher.tsx b/apps/login/src/components/language-switcher.tsx index dd2fa8b74b..0c02251a25 100644 --- a/apps/login/src/components/language-switcher.tsx +++ b/apps/login/src/components/language-switcher.tsx @@ -23,21 +23,27 @@ interface Lang { const LANGS: Lang[] = [ { id: 1, + name: "English", + code: "en", + img: "/images/flags/us.png", + }, + { + id: 2, name: "Deutsch", code: "de", img: "/images/flags/de.png", }, { - id: 2, + id: 3, name: "Italiano", code: "it", img: "/images/flags/it.png", }, { - id: 3, - name: "English", - code: "en", - img: "/images/flags/us.png", + id: 4, + name: "Español", + code: "es", + img: "/images/flags/es.png", }, ]; @@ -80,7 +86,7 @@ export function LanguageSwitcher() { > {LANGS.map((lang, index) => ( Date: Thu, 10 Oct 2024 17:05:43 +0200 Subject: [PATCH 251/640] fix redirect to idp if no other method --- apps/login/src/components/password-form.tsx | 43 ++++++++++------- apps/login/src/lib/server/loginname.ts | 53 +++++++++++++++++++-- apps/login/src/lib/zitadel.ts | 9 ++++ 3 files changed, 83 insertions(+), 22 deletions(-) diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index c0b5058249..ca6c9506e0 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -70,6 +70,7 @@ export function PasswordForm({ setLoading(false); + console.log(response); return response; } @@ -111,6 +112,11 @@ export function PasswordForm({ !submitted.authMethods || !submitted.factors?.user?.loginName ) { + console.log( + !submitted, + !submitted?.authMethods, + !submitted?.factors?.user?.loginName, + ); return; } @@ -120,6 +126,7 @@ export function PasswordForm({ m !== AuthenticationMethodType.PASSKEY, ); + console.log(availableSecondFactors); if (availableSecondFactors?.length == 1) { const params = new URLSearchParams({ loginName: submitted.factors?.user.loginName, @@ -206,25 +213,25 @@ export function PasswordForm({ } return router.push(`/login?` + params); - } else { - // without OIDC flow - const params = new URLSearchParams( - authRequestId - ? { - loginName: submitted.factors.user.loginName, - authRequestId, - } - : { - loginName: submitted.factors.user.loginName, - }, - ); - - if (organization) { - params.append("organization", organization); - } - - return router.push(`/signedin?` + params); } + + // without OIDC flow + const params = new URLSearchParams( + authRequestId + ? { + loginName: submitted.factors.user.loginName, + authRequestId, + } + : { + loginName: submitted.factors.user.loginName, + }, + ); + + if (organization) { + params.append("organization", organization); + } + + return router.push(`/signedin?` + params); } return ( diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 60d785b990..44386e70c8 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -5,12 +5,13 @@ import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_ import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; import { redirect } from "next/navigation"; -import { idpTypeToSlug } from "../idp"; +import { idpTypeToIdentityProviderType, idpTypeToSlug } from "../idp"; import { - getActiveIdentityProviders, + getIDPByID, getLoginSettings, getOrgsByDomain, listAuthenticationMethodTypes, + listIDPLinks, listUsers, startIdentityProviderFlow, } from "../zitadel"; @@ -71,6 +72,51 @@ export async function sendLoginname(command: SendLoginnameCommand) { } }; + const redirectUserToIDP = async (userId: string) => { + const identityProviders = await listIDPLinks(userId).then((resp) => { + return resp.result; + }); + + if (identityProviders.length === 1) { + const host = headers().get("host"); + const identityProviderId = identityProviders[0].idpId; + + const idp = await getIDPByID(identityProviderId); + const idpType = idp?.type; + + if (!idp || !idpType) { + throw new Error("Could not find identity provider"); + } + + const identityProviderType = idpTypeToIdentityProviderType(idpType); + const provider = idpTypeToSlug(identityProviderType); + + const params = new URLSearchParams(); + + if (command.authRequestId) { + params.set("authRequestId", command.authRequestId); + } + + if (command.organization) { + params.set("organization", command.organization); + } + + const resp = await startIdentityProviderFlow({ + idpId: idp.id, + urls: { + successUrl: + `${host}/idp/${provider}/success?` + new URLSearchParams(params), + failureUrl: + `${host}/idp/${provider}/failure?` + new URLSearchParams(params), + }, + }); + + if (resp?.nextStep.case === "authUrl") { + return redirect(resp.nextStep.value); + } + } + }; + if (users.details?.totalResult == BigInt(1) && users.result[0].userId) { const userId = users.result[0].userId; @@ -153,8 +199,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } else if ( methods.authMethodTypes.includes(AuthenticationMethodType.IDP) ) { - // TODO: redirect user to idp - await redirectUserToSingleIDPIfAvailable(); + await redirectUserToIDP(userId); } else if ( methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD) ) { diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 957749c113..3b032200e9 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -97,6 +97,15 @@ export async function getLoginSettings(orgId?: string) { )().then((resp) => (resp ? fromJson(LoginSettingsSchema, resp) : undefined)); } +export async function listIDPLinks(userId: string) { + return userService.listIDPLinks( + { + userId, + }, + {}, + ); +} + export async function addOTPEmail(userId: string) { return userService.addOTPEmail( { From 2fb94038747d7d92db390c975fa582a31f505182 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 11 Oct 2024 08:32:04 +0200 Subject: [PATCH 252/640] missing import --- apps/login/src/lib/server/loginname.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 44386e70c8..299d5f9584 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -7,6 +7,7 @@ import { headers } from "next/headers"; import { redirect } from "next/navigation"; import { idpTypeToIdentityProviderType, idpTypeToSlug } from "../idp"; import { + getActiveIdentityProviders, getIDPByID, getLoginSettings, getOrgsByDomain, From 4cd24fbddc71cdbee3cd2689c49c40214d01d7e6 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 11 Oct 2024 08:40:57 +0200 Subject: [PATCH 253/640] fix unit tests --- .../components/idps/sign-in-with-gitlab.test.tsx | 14 +++++++++----- .../components/idps/sign-in-with-google.test.tsx | 13 ++++++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/apps/login/src/components/idps/sign-in-with-gitlab.test.tsx b/apps/login/src/components/idps/sign-in-with-gitlab.test.tsx index 03b0ebddc4..ab5bfda54d 100644 --- a/apps/login/src/components/idps/sign-in-with-gitlab.test.tsx +++ b/apps/login/src/components/idps/sign-in-with-gitlab.test.tsx @@ -1,18 +1,22 @@ import { afterEach, describe, expect, test } from "vitest"; import { cleanup, render, screen } from "@testing-library/react"; -import { NextIntlClientProvider, useMessages } from "next-intl"; +import { NextIntlClientProvider } from "next-intl"; import { SignInWithGitlab } from "./sign-in-with-gitlab"; afterEach(cleanup); describe("", async () => { - const messages = useMessages(); + const messages = { + idp: { + signInWithGitlab: "Sign in with GitLab", + }, + }; test("renders without crashing", () => { const { container } = render( - + , ); @@ -21,7 +25,7 @@ describe("", async () => { test("displays the default text", () => { render( - + , ); @@ -31,7 +35,7 @@ describe("", async () => { test("displays the given text", () => { render( - + , ); diff --git a/apps/login/src/components/idps/sign-in-with-google.test.tsx b/apps/login/src/components/idps/sign-in-with-google.test.tsx index f1a20c46e8..953da21d94 100644 --- a/apps/login/src/components/idps/sign-in-with-google.test.tsx +++ b/apps/login/src/components/idps/sign-in-with-google.test.tsx @@ -2,17 +2,20 @@ import { afterEach, describe, expect, test } from "vitest"; import { cleanup, render, screen } from "@testing-library/react"; import { NextIntlClientProvider } from "next-intl"; -import { getMessages } from "next-intl/server"; import { SignInWithGoogle } from "./sign-in-with-google"; afterEach(cleanup); describe("", async () => { - const messages = await getMessages({ locale: "en" }); + const messages = { + idp: { + signInWithGoogle: "Sign in with Google", + }, + }; test("renders without crashing", () => { const { container } = render( - + , ); @@ -21,7 +24,7 @@ describe("", async () => { test("displays the default text", () => { render( - + , ); @@ -31,7 +34,7 @@ describe("", async () => { test("displays the given text", () => { render( - + , ); From 2a667acfd918b7aa41dd13f16a5f490aef349623 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 11 Oct 2024 09:59:27 +0200 Subject: [PATCH 254/640] cleanup logs, language switcher UI --- .../src/components/language-switcher.tsx | 90 ++++++++----------- apps/login/src/i18n/request.ts | 2 - 2 files changed, 35 insertions(+), 57 deletions(-) diff --git a/apps/login/src/components/language-switcher.tsx b/apps/login/src/components/language-switcher.tsx index 0c02251a25..df394a7e72 100644 --- a/apps/login/src/components/language-switcher.tsx +++ b/apps/login/src/components/language-switcher.tsx @@ -6,12 +6,12 @@ import { ListboxButton, ListboxOption, ListboxOptions, - Transition, } from "@headlessui/react"; -import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/24/outline"; +import { CheckIcon, ChevronDownIcon } from "@heroicons/react/24/outline"; +import clsx from "clsx"; import { useLocale } from "next-intl"; import { useRouter } from "next/navigation"; -import { Fragment, useState } from "react"; +import { useState } from "react"; interface Lang { id: number; @@ -68,59 +68,39 @@ export function LanguageSwitcher() { return (
    -
    - - {selected.name} - - - - - + {selected.name} + -
    + +
    + {lang.name} +
    + + ))} +
    ); diff --git a/apps/login/src/i18n/request.ts b/apps/login/src/i18n/request.ts index 5571eb2229..cfa505263a 100644 --- a/apps/login/src/i18n/request.ts +++ b/apps/login/src/i18n/request.ts @@ -11,8 +11,6 @@ export default getRequestConfig(async () => { const fallbackMessages = (await import(`../../locales/${fallback}.json`)) .default; - console.log("i18nRequest", locale); - return { locale, messages: deepmerge(fallbackMessages, userMessages), From 9f4d07a39cf27ee9fcb4353058f405c121222f2d Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 11 Oct 2024 10:35:55 +0200 Subject: [PATCH 255/640] revert middleware --- apps/login/src/middleware.ts | 59 +++++++++++++++++------------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 58252a50ae..021c2dd31a 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -1,41 +1,38 @@ -import type { NextRequest } from "next/server"; -import { NextResponse } from "next/server"; +export const config = { + matcher: [ + "/.well-known/:path*", + "/oauth/:path*", + "/oidc/:path*", + "/idps/callback/:path*", + ], +}; const INSTANCE = process.env.ZITADEL_API_URL; const SERVICE_USER_ID = process.env.ZITADEL_SERVICE_USER_ID as string; export function middleware(request: NextRequest) { - // OIDC specific routes - if ( - request.nextUrl.pathname.startsWith("/.well-known") || - request.nextUrl.pathname.startsWith("/oauth") || - request.nextUrl.pathname.startsWith("/oidc") || - request.nextUrl.pathname.startsWith("/idps/callback") - ) { - const requestHeaders = new Headers(request.headers); - requestHeaders.set("x-zitadel-login-client", SERVICE_USER_ID); + const requestHeaders = new Headers(request.headers); + requestHeaders.set("x-zitadel-login-client", SERVICE_USER_ID); - // this is a workaround for the next.js server not forwarding the host header - // requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`); - requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`); + // this is a workaround for the next.js server not forwarding the host header + // requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`); + requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`); - // this is a workaround for the next.js server not forwarding the host header - requestHeaders.set( - "x-zitadel-instance-host", - `${INSTANCE}`.replace("https://", ""), - ); + // this is a workaround for the next.js server not forwarding the host header + requestHeaders.set( + "x-zitadel-instance-host", + `${INSTANCE}`.replace("https://", ""), + ); - const responseHeaders = new Headers(); - responseHeaders.set("Access-Control-Allow-Origin", "*"); - responseHeaders.set("Access-Control-Allow-Headers", "*"); + const responseHeaders = new Headers(); + responseHeaders.set("Access-Control-Allow-Origin", "*"); + responseHeaders.set("Access-Control-Allow-Headers", "*"); - request.nextUrl.href = `${INSTANCE}${request.nextUrl.pathname}${request.nextUrl.search}`; - - return NextResponse.rewrite(request.nextUrl, { - request: { - headers: requestHeaders, - }, - headers: responseHeaders, - }); - } + request.nextUrl.href = `${INSTANCE}${request.nextUrl.pathname}${request.nextUrl.search}`; + return NextResponse.rewrite(request.nextUrl, { + request: { + headers: requestHeaders, + }, + headers: responseHeaders, + }); } From 6421883a87cdc03f5e02fb82d68c34e9c9ef0630 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 11 Oct 2024 10:36:19 +0200 Subject: [PATCH 256/640] imports --- apps/login/src/middleware.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 021c2dd31a..28be3cbdbe 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -1,3 +1,5 @@ +import { NextRequest, NextResponse } from "next/server"; + export const config = { matcher: [ "/.well-known/:path*", From f87c2311a03338efb167c6f30fc8635fb1de5fed Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 11 Oct 2024 10:45:22 +0200 Subject: [PATCH 257/640] handle login passkey error --- apps/login/src/components/login-passkey.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/login/src/components/login-passkey.tsx b/apps/login/src/components/login-passkey.tsx index cd79f95e5c..54898ce835 100644 --- a/apps/login/src/components/login-passkey.tsx +++ b/apps/login/src/components/login-passkey.tsx @@ -57,6 +57,7 @@ export function LoginPasskey({ if (!pK) { setError("Could not request passkey challenge"); setLoading(false); + return; } return submitLoginAndContinue(pK) @@ -190,12 +191,6 @@ export function LoginPasskey({ return router.push(`/signedin?` + params); } }); - }) - .catch((error) => { - // we log this error to the console, as it is not a critical error - console.error(error); - setLoading(false); - return null; }); } From 76bb714e3a90be4c9f6b5a30b18771c50dfc2a1b Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 11 Oct 2024 11:16:34 +0200 Subject: [PATCH 258/640] move language config to i18n/request --- .../src/components/language-switcher.tsx | 35 +------------------ apps/login/src/i18n/request.ts | 28 ++++++++++++++- apps/login/src/lib/cookies.ts | 3 +- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/apps/login/src/components/language-switcher.tsx b/apps/login/src/components/language-switcher.tsx index df394a7e72..b8612a8d02 100644 --- a/apps/login/src/components/language-switcher.tsx +++ b/apps/login/src/components/language-switcher.tsx @@ -1,5 +1,6 @@ "use client"; +import { Lang, LANGS } from "@/i18n/request"; import { setLanguageCookie } from "@/lib/cookies"; import { Listbox, @@ -13,40 +14,6 @@ import { useLocale } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; -interface Lang { - id: number; - name: string; - img: string; - code: string; -} - -const LANGS: Lang[] = [ - { - id: 1, - name: "English", - code: "en", - img: "/images/flags/us.png", - }, - { - id: 2, - name: "Deutsch", - code: "de", - img: "/images/flags/de.png", - }, - { - id: 3, - name: "Italiano", - code: "it", - img: "/images/flags/it.png", - }, - { - id: 4, - name: "Español", - code: "es", - img: "/images/flags/es.png", - }, -]; - export function LanguageSwitcher() { const currentLocale = useLocale(); diff --git a/apps/login/src/i18n/request.ts b/apps/login/src/i18n/request.ts index cfa505263a..632bc9e372 100644 --- a/apps/login/src/i18n/request.ts +++ b/apps/login/src/i18n/request.ts @@ -2,10 +2,36 @@ import deepmerge from "deepmerge"; import { getRequestConfig } from "next-intl/server"; import { cookies } from "next/headers"; +export interface Lang { + name: string; + code: string; +} + +export const LANGS: Lang[] = [ + { + name: "English", + code: "en", + }, + { + name: "Deutsch", + code: "de", + }, + { + name: "Italiano", + code: "it", + }, + { + name: "Español", + code: "es", + }, +]; + +export const LANGUAGE_COOKIE_NAME = "NEXT_LOCALE"; + export default getRequestConfig(async () => { const fallback = "en"; const cookiesList = cookies(); - const locale: string = cookiesList.get("NEXT_LOCALE")?.value ?? "en"; + const locale: string = cookiesList.get(LANGUAGE_COOKIE_NAME)?.value ?? "en"; const userMessages = (await import(`../../locales/${locale}.json`)).default; const fallbackMessages = (await import(`../../locales/${fallback}.json`)) diff --git a/apps/login/src/lib/cookies.ts b/apps/login/src/lib/cookies.ts index 30e0a3ee5f..1bad6e1698 100644 --- a/apps/login/src/lib/cookies.ts +++ b/apps/login/src/lib/cookies.ts @@ -1,5 +1,6 @@ "use server"; +import { LANGUAGE_COOKIE_NAME } from "@/i18n/request"; import { cookies } from "next/headers"; export type Cookie = { @@ -30,7 +31,7 @@ export async function setLanguageCookie(language: string) { const cookiesList = cookies(); await cookiesList.set({ - name: "NEXT_LOCALE", + name: LANGUAGE_COOKIE_NAME, value: language, httpOnly: true, path: "/", From 16fe17021c4dfa075fde4ac953dd53feccc0b99b Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 11 Oct 2024 11:19:20 +0200 Subject: [PATCH 259/640] seperate file --- .../src/components/language-switcher.tsx | 2 +- apps/login/src/i18n/request.ts | 27 +------------------ apps/login/src/lib/i18n.ts | 25 +++++++++++++++++ 3 files changed, 27 insertions(+), 27 deletions(-) create mode 100644 apps/login/src/lib/i18n.ts diff --git a/apps/login/src/components/language-switcher.tsx b/apps/login/src/components/language-switcher.tsx index b8612a8d02..c7dcad7ec1 100644 --- a/apps/login/src/components/language-switcher.tsx +++ b/apps/login/src/components/language-switcher.tsx @@ -1,7 +1,7 @@ "use client"; -import { Lang, LANGS } from "@/i18n/request"; import { setLanguageCookie } from "@/lib/cookies"; +import { Lang, LANGS } from "@/lib/i18n"; import { Listbox, ListboxButton, diff --git a/apps/login/src/i18n/request.ts b/apps/login/src/i18n/request.ts index 632bc9e372..ed4ba25054 100644 --- a/apps/login/src/i18n/request.ts +++ b/apps/login/src/i18n/request.ts @@ -1,33 +1,8 @@ +import { LANGUAGE_COOKIE_NAME } from "@/lib/i18n"; import deepmerge from "deepmerge"; import { getRequestConfig } from "next-intl/server"; import { cookies } from "next/headers"; -export interface Lang { - name: string; - code: string; -} - -export const LANGS: Lang[] = [ - { - name: "English", - code: "en", - }, - { - name: "Deutsch", - code: "de", - }, - { - name: "Italiano", - code: "it", - }, - { - name: "Español", - code: "es", - }, -]; - -export const LANGUAGE_COOKIE_NAME = "NEXT_LOCALE"; - export default getRequestConfig(async () => { const fallback = "en"; const cookiesList = cookies(); diff --git a/apps/login/src/lib/i18n.ts b/apps/login/src/lib/i18n.ts new file mode 100644 index 0000000000..aba1d3069c --- /dev/null +++ b/apps/login/src/lib/i18n.ts @@ -0,0 +1,25 @@ +export interface Lang { + name: string; + code: string; +} + +export const LANGS: Lang[] = [ + { + name: "English", + code: "en", + }, + { + name: "Deutsch", + code: "de", + }, + { + name: "Italiano", + code: "it", + }, + { + name: "Español", + code: "es", + }, +]; + +export const LANGUAGE_COOKIE_NAME = "NEXT_LOCALE"; From 2b3a4717d790fff37beac4adacf66057c4c79d6d Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 11 Oct 2024 11:31:27 +0200 Subject: [PATCH 260/640] import --- apps/login/src/lib/cookies.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/lib/cookies.ts b/apps/login/src/lib/cookies.ts index 1bad6e1698..63bbfff0f5 100644 --- a/apps/login/src/lib/cookies.ts +++ b/apps/login/src/lib/cookies.ts @@ -1,7 +1,7 @@ "use server"; -import { LANGUAGE_COOKIE_NAME } from "@/i18n/request"; import { cookies } from "next/headers"; +import { LANGUAGE_COOKIE_NAME } from "./i18n"; export type Cookie = { id: string; From 5256472d29922271f76feafa82b25cd95ff9f93c Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 14 Oct 2024 08:44:38 +0200 Subject: [PATCH 261/640] rm logs --- apps/login/src/components/password-form.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index ca6c9506e0..f3adecbd06 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -70,7 +70,6 @@ export function PasswordForm({ setLoading(false); - console.log(response); return response; } @@ -112,11 +111,6 @@ export function PasswordForm({ !submitted.authMethods || !submitted.factors?.user?.loginName ) { - console.log( - !submitted, - !submitted?.authMethods, - !submitted?.factors?.user?.loginName, - ); return; } @@ -126,7 +120,6 @@ export function PasswordForm({ m !== AuthenticationMethodType.PASSKEY, ); - console.log(availableSecondFactors); if (availableSecondFactors?.length == 1) { const params = new URLSearchParams({ loginName: submitted.factors?.user.loginName, From f3ff6d68efacb8372142419f55f583cc00be4970 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 14 Oct 2024 08:48:24 +0200 Subject: [PATCH 262/640] change translation for expired session --- apps/login/locales/de.json | 2 +- apps/login/locales/en.json | 2 +- apps/login/locales/es.json | 2 +- apps/login/locales/it.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 3d09858cae..35450c661f 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -134,6 +134,6 @@ }, "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.", - "sessionExpired": "Sie müssen eine gültige Sitzung haben, um fortzufahren." + "sessionExpired": "Ihre aktuelle Sitzung ist abgelaufen. Bitte melden Sie sich erneut an." } } diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 5d6f7e57d6..d19b1a36e3 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -134,6 +134,6 @@ }, "error": { "unknownContext": "Could not get the context of the user. Make sure to enter the username first or provide a loginName as searchParam.", - "sessionExpired": "You need to have a valid session in order to continue." + "sessionExpired": "Your current session has expired. Please login again." } } diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index 12f042fcd6..ebd3598e3b 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -134,6 +134,6 @@ }, "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.", - "sessionExpired": "Necesitas tener una sesión válida para continuar." + "sessionExpired": "Tu sesión actual ha expirado. Por favor, inicia sesión de nuevo." } } diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index a43424ff9c..88b2f0b1e1 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -134,6 +134,6 @@ }, "error": { "unknownContext": "Impossibile ottenere il contesto dell'utente. Assicurati di inserire prima il nome utente o di fornire un loginName come parametro di ricerca.", - "sessionExpired": "Devi avere una sessione valida per continuare." + "sessionExpired": "La tua sessione attuale è scaduta. Effettua nuovamente l'accesso." } } From 73abf1f02189a4b139d55162a2cf9fda385c4c2e Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 14 Oct 2024 09:12:06 +0200 Subject: [PATCH 263/640] remove redundant check --- apps/login/src/app/(login)/idp/[provider]/success/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 644ed135ab..c7a5f06ca9 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -51,7 +51,7 @@ export default async function Page({ const { idpInformation, userId } = intent; - if (userId && id && token) { + if (userId) { // TODO: update user if idp.options.isAutoUpdate is true return ( From fc64614d7222866812b841d152358f714a5d302f Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 15 Oct 2024 16:01:08 +0200 Subject: [PATCH 264/640] fix: temporarily remove passkey setup to reduce decision complexity on the password page --- apps/login/src/components/password-form.tsx | 40 +++++++++++---------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index f3adecbd06..f25cbf60fc 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -175,27 +175,31 @@ export function PasswordForm({ // TODO: provide a way to setup passkeys on mfa page? return router.push(`/mfa/set?` + params); - } else if ( - submitted.factors && - !submitted.factors.webAuthN && // if session was not verified with a passkey - promptPasswordless && // if explicitly prompted due policy - !isAlternative // escaped if password was used as an alternative method - ) { - const params = new URLSearchParams({ - loginName: submitted.factors.user.loginName, - prompt: "true", - }); + } + // TODO: implement passkey setup - if (authRequestId) { - params.append("authRequestId", authRequestId); - } + // else if ( + // submitted.factors && + // !submitted.factors.webAuthN && // if session was not verified with a passkey + // promptPasswordless && // if explicitly prompted due policy + // !isAlternative // escaped if password was used as an alternative method + // ) { + // const params = new URLSearchParams({ + // loginName: submitted.factors.user.loginName, + // prompt: "true", + // }); - if (organization) { - params.append("organization", organization); - } + // if (authRequestId) { + // params.append("authRequestId", authRequestId); + // } - return router.push(`/passkey/set?` + params); - } else if (authRequestId && submitted.sessionId) { + // if (organization) { + // params.append("organization", organization); + // } + + // return router.push(`/passkey/set?` + params); + // } + else if (authRequestId && submitted.sessionId) { const params = new URLSearchParams({ sessionId: submitted.sessionId, authRequest: authRequestId, From 7cb23f93cd6fb97c316e1bd4bb0a7c702ef75ffa Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 15 Oct 2024 16:29:16 +0200 Subject: [PATCH 265/640] password set page (initial, recovery) --- apps/login/src/app/(login)/password/page.tsx | 2 + .../src/app/(login)/password/set/page.tsx | 75 +++++++++++++++++++ apps/login/src/lib/server/loginname.ts | 20 +++++ 3 files changed, 97 insertions(+) create mode 100644 apps/login/src/app/(login)/password/set/page.tsx diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index f68073a352..d6c87850eb 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -32,6 +32,8 @@ export default async function Page({ const branding = await getBrandingSettings(organization); const loginSettings = await getLoginSettings(organization); + console.log(sessionFactors); + return (
    diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx new file mode 100644 index 0000000000..d6c87850eb --- /dev/null +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -0,0 +1,75 @@ +import { Alert } from "@/components/alert"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { PasswordForm } from "@/components/password-form"; +import { UserAvatar } from "@/components/user-avatar"; +import { loadMostRecentSession } from "@/lib/session"; +import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; +import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { getLocale, getTranslations } from "next-intl/server"; + +export default async function Page({ + searchParams, +}: { + searchParams: Record; +}) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "password" }); + + const { loginName, organization, authRequestId, alt } = searchParams; + + // also allow no session to be found (ignoreUnkownUsername) + let sessionFactors; + try { + sessionFactors = await loadMostRecentSession({ + loginName, + organization, + }); + } catch (error) { + // ignore error to continue to show the password form + console.warn(error); + } + + const branding = await getBrandingSettings(organization); + const loginSettings = await getLoginSettings(organization); + + console.log(sessionFactors); + + return ( + +
    +

    {sessionFactors?.factors?.user?.displayName ?? t("title")}

    +

    {t("description")}

    + + {/* show error only if usernames should be shown to be unknown */} + {(!sessionFactors || !loginName) && + !loginSettings?.ignoreUnknownUsernames && ( +
    + {t("error:unknownContext")} +
    + )} + + {sessionFactors && ( + + )} + + {loginName && ( + + )} +
    +
    + ); +} diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 299d5f9584..2d67b46f3a 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -2,6 +2,7 @@ import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; import { redirect } from "next/navigation"; @@ -135,6 +136,25 @@ export async function sendLoginname(command: SendLoginnameCommand) { return { error: "Could not create session for user" }; } + if (users.result[0].state === UserState.INITIAL) { + const params = new URLSearchParams({ + loginName: session.factors?.user?.loginName, + }); + + if (command.organization || session.factors?.user?.organizationId) { + params.append( + "organization", + command.organization ?? session.factors?.user?.organizationId, + ); + } + + if (command.authRequestId) { + params.append("authRequestid", command.authRequestId); + } + + return redirect("/password/set?" + params); + } + const methods = await listAuthenticationMethodTypes( session.factors?.user?.id, ); From 20b3c6bfc2fb234dfcbdf3520dffdfaebb6ed135 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 15 Oct 2024 17:27:08 +0200 Subject: [PATCH 266/640] set password form --- .../src/app/(login)/password/set/page.tsx | 9 +- apps/login/src/app/(login)/register/page.tsx | 6 +- .../src/components/set-password-form.tsx | 19 +- .../components/set-register-password-form.tsx | 197 ++++++++++++++++++ apps/login/src/lib/server/password.ts | 22 ++ 5 files changed, 227 insertions(+), 26 deletions(-) create mode 100644 apps/login/src/components/set-register-password-form.tsx diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index d6c87850eb..3be72f46dc 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -1,10 +1,9 @@ import { Alert } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; -import { PasswordForm } from "@/components/password-form"; +import { SetPasswordForm } from "@/components/set-password-form"; import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; -import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ @@ -58,15 +57,11 @@ export default async function Page({ )} {loginName && ( - )}
    diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 8169d205a0..ad84e81b31 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -1,6 +1,6 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { RegisterFormWithoutPassword } from "@/components/register-form-without-password"; -import { SetPasswordForm } from "@/components/set-password-form"; +import { SetRegisterPasswordForm } from "@/components/set-register-password-form"; import { getBrandingSettings, getLegalAndSupportSettings, @@ -38,14 +38,14 @@ export default async function Page({

    {t("description")}

    {legal && passwordComplexitySettings && ( - + > )}
    diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 507393c1a9..e865bfd479 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -6,7 +6,7 @@ import { symbolValidator, upperCaseValidator, } from "@/helpers/validators"; -import { registerUser, RegisterUserResponse } from "@/lib/server/register"; +import { RegisterUserResponse } from "@/lib/server/register"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; @@ -28,18 +28,12 @@ type Inputs = type Props = { passwordComplexitySettings: PasswordComplexitySettings; - email: string; - firstname: string; - lastname: string; organization?: string; authRequestId?: string; }; export function SetPasswordForm({ passwordComplexitySettings, - email, - firstname, - lastname, organization, authRequestId, }: Props) { @@ -47,11 +41,7 @@ export function SetPasswordForm({ const { register, handleSubmit, watch, formState } = useForm({ mode: "onBlur", - defaultValues: { - email: email ?? "", - firstname: firstname ?? "", - lastname: lastname ?? "", - }, + defaultValues: {}, }); const [loading, setLoading] = useState(false); @@ -61,10 +51,7 @@ export function SetPasswordForm({ async function submitRegister(values: Inputs) { setLoading(true); - const response = await registerUser({ - email: email, - firstName: firstname, - lastName: lastname, + const response = await changePassword({ organization: organization, authRequestId: authRequestId, password: values.password, diff --git a/apps/login/src/components/set-register-password-form.tsx b/apps/login/src/components/set-register-password-form.tsx new file mode 100644 index 0000000000..b8f2eaf3c5 --- /dev/null +++ b/apps/login/src/components/set-register-password-form.tsx @@ -0,0 +1,197 @@ +"use client"; + +import { + lowerCaseValidator, + numberValidator, + symbolValidator, + upperCaseValidator, +} from "@/helpers/validators"; +import { registerUser, RegisterUserResponse } from "@/lib/server/register"; +import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; +import { useTranslations } from "next-intl"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { FieldValues, useForm } from "react-hook-form"; +import { Alert } from "./alert"; +import { BackButton } from "./back-button"; +import { Button, ButtonVariants } from "./button"; +import { TextInput } from "./input"; +import { PasswordComplexity } from "./password-complexity"; +import { Spinner } from "./spinner"; + +type Inputs = + | { + password: string; + confirmPassword: string; + } + | FieldValues; + +type Props = { + passwordComplexitySettings: PasswordComplexitySettings; + email: string; + firstname: string; + lastname: string; + organization?: string; + authRequestId?: string; +}; + +export function SetRegisterPasswordForm({ + passwordComplexitySettings, + email, + firstname, + lastname, + organization, + authRequestId, +}: Props) { + const t = useTranslations("register"); + + const { register, handleSubmit, watch, formState } = useForm({ + mode: "onBlur", + defaultValues: { + email: email ?? "", + firstname: firstname ?? "", + lastname: lastname ?? "", + }, + }); + + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + const router = useRouter(); + + async function submitRegister(values: Inputs) { + setLoading(true); + const response = await registerUser({ + email: email, + firstName: firstname, + lastName: lastname, + organization: organization, + authRequestId: authRequestId, + password: values.password, + }).catch(() => { + setError("Could not register user"); + }); + + if (response && "error" in response) { + setError(response.error); + } + + setLoading(false); + + if (!response) { + setError("Could not register user"); + return; + } + + const userResponse = response as RegisterUserResponse; + + const params = new URLSearchParams({ userId: userResponse.userId }); + + if (userResponse.factors?.user?.loginName) { + params.append("loginName", userResponse.factors.user.loginName); + } + if (organization) { + params.append("organization", organization); + } + if (userResponse && userResponse.sessionId) { + params.append("sessionId", userResponse.sessionId); + } + + // skip verification for now as it is an app based flow + // return router.push(`/verify?` + params); + + // check for mfa force to continue with mfa setup + + if (authRequestId && userResponse.sessionId) { + if (authRequestId) { + params.append("authRequest", authRequestId); + } + return router.push(`/login?` + params); + } else { + if (authRequestId) { + params.append("authRequestId", authRequestId); + } + return router.push(`/signedin?` + params); + } + } + + const { errors } = formState; + + const watchPassword = watch("password", ""); + const watchConfirmPassword = watch("confirmPassword", ""); + + const hasMinLength = + passwordComplexitySettings && + watchPassword?.length >= passwordComplexitySettings.minLength; + const hasSymbol = symbolValidator(watchPassword); + const hasNumber = numberValidator(watchPassword); + const hasUppercase = upperCaseValidator(watchPassword); + const hasLowercase = lowerCaseValidator(watchPassword); + + const policyIsValid = + passwordComplexitySettings && + (passwordComplexitySettings.requiresLowercase ? hasLowercase : true) && + (passwordComplexitySettings.requiresNumber ? hasNumber : true) && + (passwordComplexitySettings.requiresUppercase ? hasUppercase : true) && + (passwordComplexitySettings.requiresSymbol ? hasSymbol : true) && + hasMinLength; + + return ( +
    +
    +
    + +
    +
    + +
    +
    + + {passwordComplexitySettings && ( + + )} + + {error && {error}} + +
    + + +
    + + ); +} diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 2a08380658..d5ca8c671e 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -107,3 +107,25 @@ export async function sendPassword(command: UpdateSessionCommand) { authMethods, }; } + +export async function changePassword(command: { + userId: string; + password: string; +}) { + // check for init state + const users = await listUsers({ + loginName: command.loginName, + organizationId: command.organization, + }); + + if ( + !users.details || + users.details.totalResult !== BigInt(1) || + !users.result[0].userId + ) { + return { error: "Could not send Password Reset Link" }; + } + const userId = users.result[0].userId; + + return passwordReset(userId); +} From 4772861262ec15206739959191e402f3a47315b9 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 09:53:30 +0200 Subject: [PATCH 267/640] use postgres --- acceptance/docker-compose.yaml | 25 ++++++++++++++----------- acceptance/machinekey/.kitkeep | 0 acceptance/zitadel.yaml | 18 +++++++++++++++++- 3 files changed, 31 insertions(+), 12 deletions(-) delete mode 100644 acceptance/machinekey/.kitkeep diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index ac2fa512f5..b39a3a4fe9 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -1,5 +1,3 @@ -version: "3.8" - services: zitadel: user: "${ZITADEL_DEV_UID}" @@ -15,17 +13,22 @@ services: condition: "service_healthy" db: - image: "cockroachdb/cockroach:v22.2.2" - command: "start-single-node --insecure --http-addr :9090" + restart: 'always' + image: 'postgres:latest' + environment: + - POSTGRES_USER=zitadel + - PGUSER=zitadel + - POSTGRES_DB=zitadel + - POSTGRES_HOST_AUTH_METHOD=trust + command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c shared_buffers=1GB -c work_mem=16MB -c effective_io_concurrency=100 -c wal_level=minimal -c archive_mode=off -c max_wal_senders=0 healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9090/health?ready=1"] - interval: "10s" - timeout: "30s" + test: ["CMD-SHELL", "pg_isready"] + interval: '10s' + timeout: '30s' retries: 5 - start_period: "20s" + start_period: '20s' ports: - - "26257:26257" - - "9090:9090" + - 5432:5432 wait_for_zitadel: image: curlimages/curl:8.00.1 @@ -33,7 +36,7 @@ services: [ "/bin/sh", "-c", - "i=0; while ! curl http://zitadel:8080/debug/ready && [ $$i -lt 30 ]; do sleep 1; i=$$((i+1)); done; [ $$i -eq 30 ] && exit 1 || exit 0", + "i=0; while ! curl http://zitadel:8080/debug/ready && [ $$i -lt 30 ]; do sleep 1; i=$$((i+1)); done; [ $$i -eq 120 ] && exit 1 || exit 0", ] depends_on: - zitadel diff --git a/acceptance/machinekey/.kitkeep b/acceptance/machinekey/.kitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/acceptance/zitadel.yaml b/acceptance/zitadel.yaml index 2b471db040..07a79bbb6b 100644 --- a/acceptance/zitadel.yaml +++ b/acceptance/zitadel.yaml @@ -9,8 +9,24 @@ FirstInstance: Type: 1 Database: - Cockroach: + EventPushConnRatio: 0.2 # 4 + ProjectionSpoolerConnRatio: 0.3 # 6 + postgres: Host: db + Port: 5432 + Database: zitadel + MaxOpenConns: 20 + MaxIdleConns: 20 + MaxConnLifetime: 1h + MaxConnIdleTime: 5m + User: + Username: zitadel + SSL: + Mode: disable + Admin: + Username: zitadel + SSL: + Mode: disable Logstore: Access: From 94bd6bc3f62cb8f78cfcf012d4739c5a7916189e Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 16 Oct 2024 11:20:23 +0200 Subject: [PATCH 268/640] set password --- apps/login/locales/de.json | 3 +- apps/login/locales/en.json | 3 +- apps/login/locales/es.json | 3 +- apps/login/locales/it.json | 3 +- .../src/app/(login)/password/set/page.tsx | 38 +++++++++++-------- .../src/components/set-password-form.tsx | 3 ++ apps/login/src/lib/server/password.ts | 18 ++++----- apps/login/src/lib/zitadel.ts | 12 ++++++ 8 files changed, 52 insertions(+), 31 deletions(-) diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 35450c661f..a731b2b3c6 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -134,6 +134,7 @@ }, "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.", - "sessionExpired": "Ihre aktuelle Sitzung ist abgelaufen. Bitte melden Sie sich erneut an." + "sessionExpired": "Ihre aktuelle Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.", + "failedLoading": "Daten konnten nicht geladen werden. Bitte versuchen Sie es erneut." } } diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index d19b1a36e3..4eae3c1a4c 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -134,6 +134,7 @@ }, "error": { "unknownContext": "Could not get the context of the user. Make sure to enter the username first or provide a loginName as searchParam.", - "sessionExpired": "Your current session has expired. Please login again." + "sessionExpired": "Your current session has expired. Please login again.", + "failedLoading": "Failed to load data. Please try again." } } diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index ebd3598e3b..4fa10378f2 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -134,6 +134,7 @@ }, "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.", - "sessionExpired": "Tu sesión actual ha expirado. Por favor, inicia sesión de nuevo." + "sessionExpired": "Tu sesión actual ha expirado. Por favor, inicia sesión de nuevo.", + "failedLoading": "No se pudieron cargar los datos. Por favor, inténtalo de nuevo." } } diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index 88b2f0b1e1..8f7d4c3344 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -134,6 +134,7 @@ }, "error": { "unknownContext": "Impossibile ottenere il contesto dell'utente. Assicurati di inserire prima il nome utente o di fornire un loginName come parametro di ricerca.", - "sessionExpired": "La tua sessione attuale è scaduta. Effettua nuovamente l'accesso." + "sessionExpired": "La tua sessione attuale è scaduta. Effettua nuovamente l'accesso.", + "failedLoading": "Impossibile caricare i dati. Riprova." } } diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index 3be72f46dc..6aa7ba23e8 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -3,7 +3,11 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SetPasswordForm } from "@/components/set-password-form"; import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; -import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; +import { + getBrandingSettings, + getLoginSettings, + getPasswordComplexitySettings, +} from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ @@ -14,24 +18,21 @@ export default async function Page({ const locale = getLocale(); const t = await getTranslations({ locale, namespace: "password" }); - const { loginName, organization, authRequestId, alt } = searchParams; + const { loginName, organization, authRequestId, code } = searchParams; // also allow no session to be found (ignoreUnkownUsername) - let sessionFactors; - try { - sessionFactors = await loadMostRecentSession({ - loginName, - organization, - }); - } catch (error) { - // ignore error to continue to show the password form - console.warn(error); - } + const sessionFactors = await loadMostRecentSession({ + loginName, + organization, + }); const branding = await getBrandingSettings(organization); - const loginSettings = await getLoginSettings(organization); - console.log(sessionFactors); + const passwordComplexity = await getPasswordComplexitySettings( + sessionFactors?.factors?.user?.organizationId, + ); + + const loginSettings = await getLoginSettings(organization); return ( @@ -56,13 +57,18 @@ export default async function Page({ > )} - {loginName && ( + {passwordComplexity && loginName ? ( + ) : ( +
    + {t("error:failedLoading")} +
    )}
    diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index e865bfd479..4baff6f6c0 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -6,6 +6,7 @@ import { symbolValidator, upperCaseValidator, } from "@/helpers/validators"; +import { changePassword } from "@/lib/server/password"; import { RegisterUserResponse } from "@/lib/server/register"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { useTranslations } from "next-intl"; @@ -27,7 +28,9 @@ type Inputs = | FieldValues; type Props = { + code?: string; passwordComplexitySettings: PasswordComplexitySettings; + loginName: string; organization?: string; authRequestId?: string; }; diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index d5ca8c671e..6906f43039 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -5,9 +5,11 @@ import { setSessionAndUpdateCookie, } from "@/lib/server/cookie"; import { + getUserByID, listAuthenticationMethodTypes, listUsers, passwordReset, + setPassword, } from "@/lib/zitadel"; import { create } from "@zitadel/client"; import { @@ -109,23 +111,17 @@ export async function sendPassword(command: UpdateSessionCommand) { } export async function changePassword(command: { + code?: string; userId: string; password: string; }) { // check for init state - const users = await listUsers({ - loginName: command.loginName, - organizationId: command.organization, - }); + const { user } = await getUserByID(command.userId); - if ( - !users.details || - users.details.totalResult !== BigInt(1) || - !users.result[0].userId - ) { + if (!user || user.userId !== command.userId) { return { error: "Could not send Password Reset Link" }; } - const userId = users.result[0].userId; + const userId = user.userId; - return passwordReset(userId); + return setPassword(userId, command.password); } diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 3b032200e9..014602478c 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -500,6 +500,18 @@ export async function passwordReset(userId: string) { ); } +export async function setPassword(userId: string, password: string) { + return userService.setPassword( + { + userId, + newPassword: { + password, + }, + }, + {}, + ); +} + /** * * @param server From 73a385c653bb91aa56272ef2f00629434fb6b4e4 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 12:23:38 +0200 Subject: [PATCH 269/640] setup playwright --- .github/workflows/playwright.yml | 27 ++ .gitignore | 4 + acceptance/setup.sh | 2 +- acceptance/tests/example.spec.ts | 7 + package.json | 4 +- playwright.config.ts | 79 +++++ pnpm-lock.yaml | 107 +++++-- tests-examples/demo-todo-app.spec.ts | 437 +++++++++++++++++++++++++++ 8 files changed, 636 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/playwright.yml create mode 100644 acceptance/tests/example.spec.ts create mode 100644 playwright.config.ts create mode 100644 tests-examples/demo-todo-app.spec.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000000..81162484ab --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm install -g pnpm && pnpm install + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + - name: Run Playwright tests + run: pnpm exec playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index 978119f359..5cf3100aa4 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,7 @@ packages/zitadel-server/src/app/proto .idea .vercel .env*.local +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/acceptance/setup.sh b/acceptance/setup.sh index d3db4f2554..3d1295d53e 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -11,7 +11,7 @@ echo "Using audience ${AUDIENCE} for which the key is used." SERVICE=${SERVICE:-$AUDIENCE} echo "Using the service ${SERVICE} to connect to ZITADEL. For example in docker compose this can differ from the audience." -WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.acceptance} +WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.local} echo "Writing environment file to ${WRITE_ENVIRONMENT_FILE} when done." AUDIENCE_HOST="$(echo $AUDIENCE | cut -d/ -f3)" diff --git a/acceptance/tests/example.spec.ts b/acceptance/tests/example.spec.ts new file mode 100644 index 0000000000..b9baed2d6e --- /dev/null +++ b/acceptance/tests/example.spec.ts @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test'; + +test('get started link', async ({ page }) => { + await page.goto('http://localhost:8080/'); + + await page.getByRole('heading', { name: 'Welcome back!' }).isVisible(); +}); diff --git a/package.json b/package.json index 4ec7f249a3..023717b879 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,12 @@ }, "devDependencies": { "@changesets/cli": "^2.27.8", + "@playwright/test": "^1.48.1", + "@types/node": "^22.7.5", "@vitejs/plugin-react": "^4.2.1", + "@zitadel/prettier-config": "workspace:*", "eslint": "8.57.1", "eslint-config-zitadel": "workspace:*", - "@zitadel/prettier-config": "workspace:*", "prettier": "^3.2.5", "prettier-plugin-organize-imports": "^4.0.0", "tsup": "^8.3.0", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000000..7f5e846e51 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,79 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './acceptance/tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a2f4ff98a..1ec76ca2d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,15 @@ importers: '@changesets/cli': specifier: ^2.27.8 version: 2.27.8 + '@playwright/test': + specifier: ^1.48.1 + version: 1.48.1 + '@types/node': + specifier: ^22.7.5 + version: 22.7.5 '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.3.1(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1)) + version: 4.3.1(vite@5.4.6(@types/node@22.7.5)(sass@1.79.1)) '@zitadel/prettier-config': specifier: workspace:* version: link:packages/zitadel-prettier-config @@ -43,10 +49,10 @@ importers: version: 5.6.2 vite-tsconfig-paths: specifier: ^5.0.1 - version: 5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1)) + version: 5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.7.5)(sass@1.79.1)) vitest: specifier: ^2.1.1 - version: 2.1.1(@types/node@22.5.5)(jsdom@25.0.0)(sass@1.79.1) + version: 2.1.1(@types/node@22.7.5)(jsdom@25.0.0)(sass@1.79.1) apps/login: dependencies: @@ -61,7 +67,7 @@ importers: version: 0.5.7(tailwindcss@3.4.13) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) + version: 1.3.1(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -85,13 +91,13 @@ importers: version: 2.30.1 next: specifier: 14.2.14 - version: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + version: 14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) next-intl: specifier: ^3.20.0 - version: 3.20.0(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) + version: 3.20.0(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.2.1(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nice-grpc: specifier: 2.0.1 version: 2.0.1 @@ -1036,6 +1042,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@playwright/test@1.48.1': + resolution: {integrity: sha512-s9RtWoxkOLmRJdw3oFvhFbs9OJS0BzrLUc8Hf6l2UdCNd1rqeEyD4BhCJkvzeEoD1FsK4mirsWwGerhVmYKtZg==} + engines: {node: '>=18'} + hasBin: true + '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -1270,6 +1281,9 @@ packages: '@types/node@22.5.5': resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} + '@types/node@22.7.5': + resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -2403,6 +2417,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3473,6 +3492,16 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + playwright-core@1.48.1: + resolution: {integrity: sha512-Yw/t4VAFX/bBr1OzwCuOMZkY1Cnb4z/doAFSwf4huqAGWmf9eMNjmK7NiOljCdLmxeRYcGPPmcDgU0zOlzP0YA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.48.1: + resolution: {integrity: sha512-j8CiHW/V6HxmbntOfyB4+T/uk08tBy6ph0MpBXwuoofkSnLmlfdYNNkFTYD6ofzzlSqLA1fwH4vwvVFvJgLN0w==} + engines: {node: '>=18'} + hasBin: true + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -5375,6 +5404,10 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@playwright/test@1.48.1': + dependencies: + playwright: 1.48.1 + '@protobufjs/aspromise@1.1.2': {} '@protobufjs/base64@1.1.2': {} @@ -5589,6 +5622,10 @@ snapshots: dependencies: undici-types: 6.19.8 + '@types/node@22.7.5': + dependencies: + undici-types: 6.19.8 + '@types/normalize-package-data@2.4.4': {} '@types/prop-types@15.7.12': {} @@ -5659,23 +5696,23 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/analytics@1.3.1(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1)': + '@vercel/analytics@1.3.1(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next: 14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) react: 18.3.1 '@vercel/git-hooks@1.0.0': {} - '@vitejs/plugin-react@4.3.1(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1))': + '@vitejs/plugin-react@4.3.1(vite@5.4.6(@types/node@22.7.5)(sass@1.79.1))': dependencies: '@babel/core': 7.25.2 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.5)(sass@1.79.1) transitivePeerDependencies: - supports-color @@ -5686,13 +5723,13 @@ snapshots: chai: 5.1.1 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1))': + '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.7.5)(sass@1.79.1))': dependencies: '@vitest/spy': 2.1.1 estree-walker: 3.0.3 magic-string: 0.30.11 optionalDependencies: - vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.5)(sass@1.79.1) '@vitest/pretty-format@2.1.1': dependencies: @@ -7007,6 +7044,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -7792,21 +7832,21 @@ snapshots: negotiator@0.6.3: {} - next-intl@3.20.0(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1): + next-intl@3.20.0(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next: 14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) react: 18.3.1 use-intl: 3.20.0(react@18.3.1) - next-themes@0.2.1(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-themes@0.2.1(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next: 14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1): + next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1): dependencies: '@next/env': 14.2.14 '@swc/helpers': 0.5.5 @@ -7827,6 +7867,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 14.2.14 '@next/swc-win32-ia32-msvc': 14.2.14 '@next/swc-win32-x64-msvc': 14.2.14 + '@playwright/test': 1.48.1 sass: 1.79.1 transitivePeerDependencies: - '@babel/core' @@ -8065,6 +8106,14 @@ snapshots: pirates@4.0.6: {} + playwright-core@1.48.1: {} + + playwright@1.48.1: + dependencies: + playwright-core: 1.48.1 + optionalDependencies: + fsevents: 2.3.2 + possible-typed-array-names@1.0.0: {} postcss-import@15.1.0(postcss@8.4.47): @@ -9017,12 +9066,12 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - vite-node@2.1.1(@types/node@22.5.5)(sass@1.79.1): + vite-node@2.1.1(@types/node@22.7.5)(sass@1.79.1): dependencies: cac: 6.7.14 debug: 4.3.7(supports-color@5.5.0) pathe: 1.1.2 - vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.5)(sass@1.79.1) transitivePeerDependencies: - '@types/node' - less @@ -9034,31 +9083,31 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1)): + vite-tsconfig-paths@5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.7.5)(sass@1.79.1)): dependencies: debug: 4.3.6 globrex: 0.1.2 tsconfck: 3.1.1(typescript@5.6.2) optionalDependencies: - vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.5)(sass@1.79.1) transitivePeerDependencies: - supports-color - typescript - vite@5.4.6(@types/node@22.5.5)(sass@1.79.1): + vite@5.4.6(@types/node@22.7.5)(sass@1.79.1): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.21.3 optionalDependencies: - '@types/node': 22.5.5 + '@types/node': 22.7.5 fsevents: 2.3.3 sass: 1.79.1 - vitest@2.1.1(@types/node@22.5.5)(jsdom@25.0.0)(sass@1.79.1): + vitest@2.1.1(@types/node@22.7.5)(jsdom@25.0.0)(sass@1.79.1): dependencies: '@vitest/expect': 2.1.1 - '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1)) + '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.7.5)(sass@1.79.1)) '@vitest/pretty-format': 2.1.1 '@vitest/runner': 2.1.1 '@vitest/snapshot': 2.1.1 @@ -9073,11 +9122,11 @@ snapshots: tinyexec: 0.3.0 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) - vite-node: 2.1.1(@types/node@22.5.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.5)(sass@1.79.1) + vite-node: 2.1.1(@types/node@22.7.5)(sass@1.79.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.5.5 + '@types/node': 22.7.5 jsdom: 25.0.0 transitivePeerDependencies: - less diff --git a/tests-examples/demo-todo-app.spec.ts b/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000000..8641cb5f5d --- /dev/null +++ b/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,437 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +] as const; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); + }, title); +} From 8c6d957f4ad9547e9cffee76551ba03aeffc2ee3 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 12:24:29 +0200 Subject: [PATCH 270/640] pipeline --- .github/workflows/playwright.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 81162484ab..f6d7e713fb 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,9 +1,9 @@ name: Playwright Tests on: push: - branches: [ main, master ] + branches: [ main ] pull_request: - branches: [ main, master ] + branches: [ main ] jobs: test: timeout-minutes: 60 From fe289e32fa71e4465ff135b7c36f125a0ca4b8a2 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 12:34:53 +0200 Subject: [PATCH 271/640] build and test --- .github/workflows/playwright.yml | 8 ++++++-- acceptance/docker-compose.yaml | 2 +- package.json | 1 + playwright.config.ts | 12 ++++++------ turbo.json | 1 + 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index f6d7e713fb..77a1e4cd3b 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,15 +1,19 @@ -name: Playwright Tests +name: Acceptance Tests on: push: branches: [ main ] pull_request: branches: [ main ] jobs: - test: + acceptance-tests: timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Run docker-compose + uses: hoverkraft-tech/compose-action@v2.0.1 + with: + compose-file: "./acceptance/docker-compose.yaml" - uses: actions/setup-node@v4 with: node-version: lts/* diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index b39a3a4fe9..fa84b9d843 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -48,7 +48,7 @@ services: environment: KEY: /key/zitadel-admin-sa.json SERVICE: http://zitadel:8080 - WRITE_ENVIRONMENT_FILE: /apps/login/.env.acceptance + WRITE_ENVIRONMENT_FILE: /apps/login/.env.local volumes: - "./machinekey:/key" - "../apps/login:/apps/login" diff --git a/package.json b/package.json index 023717b879..3fc480546a 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "generate": "turbo run generate", "build": "turbo run build", "test": "turbo run test", + "start": "turbo run start", "test:unit": "turbo run test:unit -- --passWithNoTests", "test:integration": "turbo run test:integration", "test:watch": "turbo run test:watch", diff --git a/playwright.config.ts b/playwright.config.ts index 7f5e846e51..9e7492bfd7 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -70,10 +70,10 @@ export default defineConfig({ // }, ], - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, + /* Run local dev server before starting the tests */ + webServer: { + command: 'pnpm start', + url: 'http://127.0.0.1:3000', + reuseExistingServer: !process.env.CI, + }, }); diff --git a/turbo.json b/turbo.json index 7d38ae4c7f..e2412dfe97 100644 --- a/turbo.json +++ b/turbo.json @@ -21,6 +21,7 @@ }, "build": {}, "test": {}, + "start": {}, "test:unit": {}, "test:integration": {}, "test:watch": { From ac7cbe5460ff5a8ea0f58f303d9e40c540727e05 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 12:35:34 +0200 Subject: [PATCH 272/640] test on pr --- .github/workflows/playwright.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 77a1e4cd3b..943f6d87eb 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,9 +1,6 @@ name: Acceptance Tests on: - push: - branches: [ main ] pull_request: - branches: [ main ] jobs: acceptance-tests: timeout-minutes: 60 From 5be0ed45ae7262720351d01cf89729650a53a47b Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 12:37:41 +0200 Subject: [PATCH 273/640] rename pipeline workflows --- .../{playwright.yml => acceptance-tests.yml} | 3 +-- ...{test.yml => unit-and-integration-tests.yml} | 17 +---------------- 2 files changed, 2 insertions(+), 18 deletions(-) rename .github/workflows/{playwright.yml => acceptance-tests.yml} (97%) rename .github/workflows/{test.yml => unit-and-integration-tests.yml} (97%) diff --git a/.github/workflows/playwright.yml b/.github/workflows/acceptance-tests.yml similarity index 97% rename from .github/workflows/playwright.yml rename to .github/workflows/acceptance-tests.yml index 943f6d87eb..e0d635c901 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/acceptance-tests.yml @@ -1,6 +1,5 @@ name: Acceptance Tests -on: - pull_request: +on: pull_request jobs: acceptance-tests: timeout-minutes: 60 diff --git a/.github/workflows/test.yml b/.github/workflows/unit-and-integration-tests.yml similarity index 97% rename from .github/workflows/test.yml rename to .github/workflows/unit-and-integration-tests.yml index 91c78a4db2..ecdc443ffa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/unit-and-integration-tests.yml @@ -1,18 +1,12 @@ -name: Quality - +name: Unit And Integration Tests on: pull_request - jobs: quality: name: Ensure Quality - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: contents: "read" - strategy: fail-fast: false matrix: @@ -21,31 +15,25 @@ jobs: - lint - test:unit - test:integration - steps: - name: Checkout Repo uses: actions/checkout@v4.1.6 - - name: Setup Node.js 20.x uses: actions/setup-node@v4.0.2 with: node-version: 20.x - - name: Setup pnpm uses: pnpm/action-setup@v4.0.0 - - uses: pnpm/action-setup@v4.0.0 name: Install pnpm id: pnpm-install with: run_install: false - - name: Get pnpm store directory id: pnpm-cache shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - uses: actions/cache@v4.0.2 name: Setup pnpm cache with: @@ -53,7 +41,6 @@ jobs: key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - - uses: actions/cache@v4.0.2 name: Setup Cypress binary cache with: @@ -62,10 +49,8 @@ jobs: restore-keys: | ${{ runner.os }}-cypress-binary- if: ${{ matrix.command }} == "test:integration" - - name: Install Dependencies run: pnpm install - - name: Check id: check run: pnpm ${{ matrix.command }} From 2fa16d5f8fd40ad100923e629f6576b28e6de61f Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 15:10:49 +0200 Subject: [PATCH 274/640] generate --- .github/workflows/acceptance-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index e0d635c901..948367774a 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -14,7 +14,7 @@ jobs: with: node-version: lts/* - name: Install dependencies - run: npm install -g pnpm && pnpm install + run: npm install -g pnpm && pnpm install && pnpm generate - name: Install Playwright Browsers run: pnpm exec playwright install --with-deps - name: Run Playwright tests From 653b499668b60d486567b951396d348955e9fabe Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 15:21:26 +0200 Subject: [PATCH 275/640] timeout --- .../tests/{example.spec.ts => username-password.spec.ts} | 0 acceptance/tests/welcome.ts | 7 +++++++ playwright.config.ts | 1 + 3 files changed, 8 insertions(+) rename acceptance/tests/{example.spec.ts => username-password.spec.ts} (100%) create mode 100644 acceptance/tests/welcome.ts diff --git a/acceptance/tests/example.spec.ts b/acceptance/tests/username-password.spec.ts similarity index 100% rename from acceptance/tests/example.spec.ts rename to acceptance/tests/username-password.spec.ts diff --git a/acceptance/tests/welcome.ts b/acceptance/tests/welcome.ts new file mode 100644 index 0000000000..b9baed2d6e --- /dev/null +++ b/acceptance/tests/welcome.ts @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test'; + +test('get started link', async ({ page }) => { + await page.goto('http://localhost:8080/'); + + await page.getByRole('heading', { name: 'Welcome back!' }).isVisible(); +}); diff --git a/playwright.config.ts b/playwright.config.ts index 9e7492bfd7..5214aec6b9 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -75,5 +75,6 @@ export default defineConfig({ command: 'pnpm start', url: 'http://127.0.0.1:3000', reuseExistingServer: !process.env.CI, + timeout: 5 * 60_000, }, }); From 69bc1c19e3f829708cc0e887ca9594f6604ed798 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 16 Oct 2024 16:29:48 +0200 Subject: [PATCH 276/640] redirect to /set --- apps/login/locales/de.json | 17 ++++-- apps/login/locales/en.json | 17 ++++-- apps/login/locales/es.json | 17 ++++-- apps/login/locales/it.json | 17 ++++-- apps/login/src/app/(login)/password/page.tsx | 8 +-- .../src/app/(login)/password/set/page.tsx | 13 +++-- apps/login/src/components/password-form.tsx | 24 ++++++--- .../src/components/set-password-form.tsx | 52 ++++++++++++++----- apps/login/src/lib/server/password.ts | 4 +- apps/login/src/lib/zitadel.ts | 17 +++++- 10 files changed, 139 insertions(+), 47 deletions(-) diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index a731b2b3c6..f07fde4eb9 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -14,10 +14,19 @@ "register": "Neuen Benutzer registrieren" }, "password": { - "title": "Passwort", - "description": "Geben Sie Ihr Passwort ein.", - "resetPassword": "Passwort zurücksetzen", - "submit": "Weiter" + "verify": { + "title": "Passwort", + "description": "Geben Sie Ihr Passwort ein.", + "resetPassword": "Passwort zurücksetzen", + "submit": "Weiter" + }, + "set": { + "title": "Passwort festlegen", + "description": "Legen Sie das Passwort für Ihr Konto fest", + "codeSent": "Ein Code wurde an Ihre E-Mail-Adresse gesendet.", + "resend": "Erneut senden", + "submit": "Weiter" + } }, "idp": { "title": "Mit SSO anmelden", diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 4eae3c1a4c..6aca7e220e 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -14,10 +14,19 @@ "register": "Register new user" }, "password": { - "title": "Password", - "description": "Enter your password.", - "resetPassword": "Reset Password", - "submit": "Continue" + "verify": { + "title": "Password", + "description": "Enter your password.", + "resetPassword": "Reset Password", + "submit": "Continue" + }, + "set": { + "title": "Set Password", + "description": "Set the password for your account", + "codeSent": "A code has been sent to your email address.", + "resend": "Resend code", + "submit": "Continue" + } }, "idp": { "title": "Sign in with SSO", diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index 4fa10378f2..e730412458 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -14,10 +14,19 @@ "register": "Registrar nuevo usuario" }, "password": { - "title": "Contraseña", - "description": "Introduce tu contraseña.", - "resetPassword": "Restablecer Contraseña", - "submit": "Continuar" + "verify": { + "title": "Contraseña", + "description": "Introduce tu contraseña.", + "resetPassword": "Restablecer contraseña", + "submit": "Continuar" + }, + "set": { + "title": "Establecer Contraseña", + "description": "Establece la contraseña para tu cuenta", + "codeSent": "Se ha enviado un código a su correo electrónico.", + "resend": "Reenviar código", + "submit": "Continuar" + } }, "idp": { "title": "Iniciar sesión con SSO", diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index 8f7d4c3344..1104a08d68 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -14,10 +14,19 @@ "register": "Registrati come nuovo utente" }, "password": { - "title": "Password", - "description": "Inserisci la tua password.", - "resetPassword": "Reimposta Password", - "submit": "Continua" + "verify": { + "title": "Password", + "description": "Inserisci la tua password.", + "resetPassword": "Reimposta Password", + "submit": "Continua" + }, + "set": { + "title": "Imposta Password", + "description": "Imposta la password per il tuo account", + "codeSent": "Un codice è stato inviato al tuo indirizzo email.", + "resend": "Invia di nuovo", + "submit": "Continua" + } }, "idp": { "title": "Accedi con SSO", diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index d6c87850eb..43cb969dc3 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -32,13 +32,13 @@ export default async function Page({ const branding = await getBrandingSettings(organization); const loginSettings = await getLoginSettings(organization); - console.log(sessionFactors); - return (
    -

    {sessionFactors?.factors?.user?.displayName ?? t("title")}

    -

    {t("description")}

    +

    + {sessionFactors?.factors?.user?.displayName ?? t("verify.title")} +

    +

    {t("verify.description")}

    {/* show error only if usernames should be shown to be unknown */} {(!sessionFactors || !loginName) && diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index 6aa7ba23e8..e99f79ef92 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -1,4 +1,4 @@ -import { Alert } from "@/components/alert"; +import { Alert, AlertType } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { SetPasswordForm } from "@/components/set-password-form"; import { UserAvatar } from "@/components/user-avatar"; @@ -37,8 +37,8 @@ export default async function Page({ return (
    -

    {sessionFactors?.factors?.user?.displayName ?? t("title")}

    -

    {t("description")}

    +

    {sessionFactors?.factors?.user?.displayName ?? t("set.title")}

    +

    {t("set.description")}

    {/* show error only if usernames should be shown to be unknown */} {(!sessionFactors || !loginName) && @@ -57,9 +57,14 @@ export default async function Page({ > )} - {passwordComplexity && loginName ? ( + {t("set.codeSent")} + + {passwordComplexity && + loginName && + sessionFactors?.factors?.user?.id ? ( - {t("resetPassword")} + {t("verify.resetPassword")} )} @@ -284,7 +296,7 @@ export function PasswordForm({ onClick={handleSubmit(submitPasswordAndContinue)} > {loading && } - {t("submit")} + {t("verify.submit")}
    diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 4baff6f6c0..51e3f1325e 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -7,8 +7,8 @@ import { upperCaseValidator, } from "@/helpers/validators"; import { changePassword } from "@/lib/server/password"; -import { RegisterUserResponse } from "@/lib/server/register"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; +import { SetPasswordResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; @@ -22,6 +22,7 @@ import { Spinner } from "./spinner"; type Inputs = | { + code: string; password: string; confirmPassword: string; } @@ -31,6 +32,7 @@ type Props = { code?: string; passwordComplexitySettings: PasswordComplexitySettings; loginName: string; + userId: string; organization?: string; authRequestId?: string; }; @@ -39,12 +41,17 @@ export function SetPasswordForm({ passwordComplexitySettings, organization, authRequestId, + loginName, + userId, + code, }: Props) { - const t = useTranslations("register"); + const t = useTranslations("password"); const { register, handleSubmit, watch, formState } = useForm({ mode: "onBlur", - defaultValues: {}, + defaultValues: { + code: code ?? "", + }, }); const [loading, setLoading] = useState(false); @@ -55,9 +62,9 @@ export function SetPasswordForm({ async function submitRegister(values: Inputs) { setLoading(true); const response = await changePassword({ - organization: organization, - authRequestId: authRequestId, + userId: userId, password: values.password, + code: values.code, }).catch(() => { setError("Could not register user"); }); @@ -73,19 +80,18 @@ export function SetPasswordForm({ return; } - const userResponse = response as RegisterUserResponse; + const userResponse = response as SetPasswordResponse & { + sessionId: string; + }; - const params = new URLSearchParams({ userId: userResponse.userId }); + const params = new URLSearchParams({}); - if (userResponse.factors?.user?.loginName) { - params.append("loginName", userResponse.factors.user.loginName); + if (loginName) { + params.append("loginName", loginName); } if (organization) { params.append("organization", organization); } - if (userResponse && userResponse.sessionId) { - params.append("sessionId", userResponse.sessionId); - } // skip verification for now as it is an app based flow // return router.push(`/verify?` + params); @@ -129,6 +135,24 @@ export function SetPasswordForm({ return (
    +
    +
    + +
    +
    + +
    +
    @@ -179,7 +203,7 @@ export function SetPasswordForm({ onClick={handleSubmit(submitRegister)} > {loading && } - {t("password.submit")} + {t("set.submit")}
    diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 6906f43039..faa0bbeb04 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -111,7 +111,7 @@ export async function sendPassword(command: UpdateSessionCommand) { } export async function changePassword(command: { - code?: string; + code: string; userId: string; password: string; }) { @@ -123,5 +123,5 @@ export async function changePassword(command: { } const userId = user.userId; - return setPassword(userId, command.password); + return setPassword(userId, command.password, command.code); } diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 014602478c..846552ddda 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -28,6 +28,7 @@ import { } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { PasswordComplexitySettingsSchema } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb"; +import { NotificationType } from "@zitadel/proto/zitadel/user/v2/password_pb"; import { SearchQuery, SearchQuerySchema, @@ -495,18 +496,32 @@ export async function passwordReset(userId: string) { return userService.passwordReset( { userId, + medium: { + case: "sendLink", + value: { + notificationType: NotificationType.Email, + }, + }, }, {}, ); } -export async function setPassword(userId: string, password: string) { +export async function setPassword( + userId: string, + password: string, + code: string, +) { return userService.setPassword( { userId, newPassword: { password, }, + verification: { + case: "verificationCode", + value: code, + }, }, {}, ); From f1b756b7213716d32ed27818860bd74d5ec4b494 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 16:50:48 +0200 Subject: [PATCH 277/640] submit username and password --- acceptance/tests/username-password.spec.ts | 13 +++++++++---- acceptance/tests/welcome.ts | 7 +++---- playwright.config.ts | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index b9baed2d6e..72f2d86780 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -1,7 +1,12 @@ import { test, expect } from '@playwright/test'; -test('get started link', async ({ page }) => { - await page.goto('http://localhost:8080/'); - - await page.getByRole('heading', { name: 'Welcome back!' }).isVisible(); +test('username and password', async ({ page }) => { + await page.goto('/'); + const loginname = page.getByLabel('Loginname') + await loginname.pressSequentially("zitadel-admin@zitadel.localhost"); + await loginname.press( 'Enter'); + const password = page.getByLabel('Password') + await password.pressSequentially("Password1!"); + await password.press( 'Enter'); + await page.getByText('Skip').click(); }); diff --git a/acceptance/tests/welcome.ts b/acceptance/tests/welcome.ts index b9baed2d6e..22734b0c11 100644 --- a/acceptance/tests/welcome.ts +++ b/acceptance/tests/welcome.ts @@ -1,7 +1,6 @@ -import { test, expect } from '@playwright/test'; - -test('get started link', async ({ page }) => { - await page.goto('http://localhost:8080/'); +import { test } from '@playwright/test'; +test('login is accessible', async ({ page }) => { + await page.goto('http://localhost:3000/'); await page.getByRole('heading', { name: 'Welcome back!' }).isVisible(); }); diff --git a/playwright.config.ts b/playwright.config.ts index 5214aec6b9..ccf7516dc1 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -26,7 +26,7 @@ export default defineConfig({ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://127.0.0.1:3000', + baseURL: 'http://127.0.0.1:3000', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', From c5620e6acc8d2edc0d773023f8e234d9f8c2a4a1 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 17:11:51 +0200 Subject: [PATCH 278/640] concurrent setup --- .github/workflows/acceptance-tests.yml | 24 ++++++++++++++++-------- playwright.config.ts | 5 +++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 948367774a..c20bed2953 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -6,17 +6,25 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Run docker-compose - uses: hoverkraft-tech/compose-action@v2.0.1 - with: - compose-file: "./acceptance/docker-compose.yaml" - uses: actions/setup-node@v4 with: node-version: lts/* - - name: Install dependencies - run: npm install -g pnpm && pnpm install && pnpm generate - - name: Install Playwright Browsers - run: pnpm exec playwright install --with-deps + - name: Run Docker Compose and Install Dependencies in Parallel + run: | + echo "Start docker-compose in the background" + docker-compose -f ./acceptance/docker-compose.yaml up -d & + + echo "Install node_modules in the foreground" + npm install -g pnpm && pnpm install + + echo "Generate and create a production build in the background" + pnpm build & + + echo "Install Playwright with browsers in the background" + pnpm exec playwright install --with-deps & + + echo "Wait for all background processes to complete" + wait - name: Run Playwright tests run: pnpm exec playwright test - uses: actions/upload-artifact@v4 diff --git a/playwright.config.ts b/playwright.config.ts index ccf7516dc1..fed6afd502 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -43,11 +43,12 @@ export default defineConfig({ name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, - +/* TODO: webkit fails. Is this a bug? { name: 'webkit', use: { ...devices['Desktop Safari'] }, }, +*/ /* Test against mobile viewports. */ // { @@ -74,7 +75,7 @@ export default defineConfig({ webServer: { command: 'pnpm start', url: 'http://127.0.0.1:3000', - reuseExistingServer: !process.env.CI, + reuseExistingServer: false, //!process.env.CI, timeout: 5 * 60_000, }, }); From 576cdcc76a3c80c92785b9a9524e8e9362c5b1ee Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 17:17:13 +0200 Subject: [PATCH 279/640] fail earlier --- .github/workflows/acceptance-tests.yml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index c20bed2953..43699aaf8c 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -12,20 +12,34 @@ jobs: - name: Run Docker Compose and Install Dependencies in Parallel run: | echo "Start docker-compose in the background" - docker-compose -f ./acceptance/docker-compose.yaml up -d & - + docker compose -f ./acceptance/docker-compose.yaml up -d & + DOCKER_COMPOSE_PID=$! + echo "Install node_modules in the foreground" npm install -g pnpm && pnpm install echo "Generate and create a production build in the background" pnpm build & + GENERATE_BUILD_PID=$! echo "Install Playwright with browsers in the background" pnpm exec playwright install --with-deps & + PLAYWRIGHT_INSTALL_PID=$! echo "Wait for all background processes to complete" - wait - - name: Run Playwright tests + while true; do + wait -n $DOCKER_COMPOSE_PID $GENERATE_BUILD_PID $PLAYWRIGHT_INSTALL_PID + EXIT_STATUS=$? + if [ $EXIT_STATUS -ne 0 ]; then + echo "A background process failed with exit code $EXIT_STATUS." + exit $EXIT_STATUS + fi + # Exit the loop if all processes have finished + if ! kill -0 $DOCKER_COMPOSE_PID $GENERATE_BUILD_PID $PLAYWRIGHT_INSTALL_PID 2>/dev/null; then + break + fi + done + - name: Run Playwright Tests run: pnpm exec playwright test - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} From 77c00fb6661b069c7a816c5bfe23f16aaadaa01e Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 16 Oct 2024 17:21:52 +0200 Subject: [PATCH 280/640] should not prompt for the moment --- apps/login/cypress/integration/login.cy.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/login/cypress/integration/login.cy.ts b/apps/login/cypress/integration/login.cy.ts index a286e00d44..bb83ca375a 100644 --- a/apps/login/cypress/integration/login.cy.ts +++ b/apps/login/cypress/integration/login.cy.ts @@ -104,16 +104,16 @@ describe("login", () => { }, }); }); - it("should prompt a user to setup passwordless authentication if passkey is allowed in the login settings", () => { - cy.visit("/loginname?loginName=john%40zitadel.com&submit=true"); - cy.location("pathname", { timeout: 10_000 }).should("eq", "/password"); - cy.get('input[type="password"]').focus().type("MyStrongPassword!1"); - cy.get('button[type="submit"]').click(); - cy.location("pathname", { timeout: 10_000 }).should( - "eq", - "/passkey/set", - ); - }); + // it("should prompt a user to setup passwordless authentication if passkey is allowed in the login settings", () => { + // cy.visit("/loginname?loginName=john%40zitadel.com&submit=true"); + // cy.location("pathname", { timeout: 10_000 }).should("eq", "/password"); + // cy.get('input[type="password"]').focus().type("MyStrongPassword!1"); + // cy.get('button[type="submit"]').click(); + // cy.location("pathname", { timeout: 10_000 }).should( + // "eq", + // "/passkey/set", + // ); + // }); }); }); describe("passkey login", () => { From ca0e551dd5015d740246aeb3fa5097c43eb174fe Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 17:26:28 +0200 Subject: [PATCH 281/640] kill -0 --- .github/workflows/acceptance-tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 43699aaf8c..8e528e62d0 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -34,8 +34,10 @@ jobs: echo "A background process failed with exit code $EXIT_STATUS." exit $EXIT_STATUS fi - # Exit the loop if all processes have finished - if ! kill -0 $DOCKER_COMPOSE_PID $GENERATE_BUILD_PID $PLAYWRIGHT_INSTALL_PID 2>/dev/null; then + # Check if all processes have finished + if ! kill -0 $DOCKER_COMPOSE_PID 2>/dev/null && \ + ! kill -0 $GENERATE_BUILD_PID 2>/dev/null && \ + ! kill -0 $PLAYWRIGHT_INSTALL_PID 2>/dev/null; then break fi done From bc3e09ed0c47475a30d29630513489fed4b954ff Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 16 Oct 2024 18:11:54 +0200 Subject: [PATCH 282/640] self service change password --- apps/login/locales/de.json | 5 ++ apps/login/locales/en.json | 5 ++ apps/login/locales/es.json | 5 ++ apps/login/locales/it.json | 5 ++ .../app/(login)/me/change-password/page.tsx | 78 ------------------ .../src/app/(login)/password/change/page.tsx | 80 +++++++++++++++++++ .../src/components/change-password-form.tsx | 48 ++++++++--- apps/login/src/components/password-form.tsx | 6 +- apps/login/src/lib/self.ts | 49 ++++++++---- 9 files changed, 178 insertions(+), 103 deletions(-) delete mode 100644 apps/login/src/app/(login)/me/change-password/page.tsx create mode 100644 apps/login/src/app/(login)/password/change/page.tsx diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index f07fde4eb9..75771c1ac9 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -26,6 +26,11 @@ "codeSent": "Ein Code wurde an Ihre E-Mail-Adresse gesendet.", "resend": "Erneut senden", "submit": "Weiter" + }, + "change": { + "title": "Passwort ändern", + "description": "Legen Sie das Passwort für Ihr Konto fest", + "submit": "Weiter" } }, "idp": { diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 6aca7e220e..2481d047ac 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -26,6 +26,11 @@ "codeSent": "A code has been sent to your email address.", "resend": "Resend code", "submit": "Continue" + }, + "change": { + "title": "Change Password", + "description": "Set the password for your account", + "submit": "Continue" } }, "idp": { diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index e730412458..2643f763c9 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -26,6 +26,11 @@ "codeSent": "Se ha enviado un código a su correo electrónico.", "resend": "Reenviar código", "submit": "Continuar" + }, + "change": { + "title": "Cambiar Contraseña", + "description": "Establece la contraseña para tu cuenta", + "submit": "Continuar" } }, "idp": { diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index 1104a08d68..d13863ff3c 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -26,6 +26,11 @@ "codeSent": "Un codice è stato inviato al tuo indirizzo email.", "resend": "Invia di nuovo", "submit": "Continua" + }, + "change": { + "title": "Cambia Password", + "description": "Imposta la password per il tuo account", + "submit": "Continua" } }, "idp": { diff --git a/apps/login/src/app/(login)/me/change-password/page.tsx b/apps/login/src/app/(login)/me/change-password/page.tsx deleted file mode 100644 index d87f184ac9..0000000000 --- a/apps/login/src/app/(login)/me/change-password/page.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Alert } from "@/components/alert"; -import { ChangePasswordForm } from "@/components/change-password-form"; -import { DynamicTheme } from "@/components/dynamic-theme"; -import { UserAvatar } from "@/components/user-avatar"; -import { getSessionCookieById } from "@/lib/cookies"; -import { - getBrandingSettings, - getPasswordComplexitySettings, - getSession, -} from "@/lib/zitadel"; - -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { - const { sessionId } = searchParams; - - if (!sessionId) { - return ( -
    -

    Session ID not found

    -
    - ); - } - - const sessionCookie = await getSessionCookieById({ - sessionId, - }); - - const { session } = await getSession({ - sessionId: sessionCookie.id, - sessionToken: sessionCookie.token, - }); - - const passwordComplexitySettings = await getPasswordComplexitySettings( - session?.factors?.user?.organizationId, - ); - - const branding = await getBrandingSettings( - session?.factors?.user?.organizationId, - ); - - return ( - -
    -

    Set Password

    -

    Set the password for your account

    - - {!session && ( -
    - - Could not get the context of the user. Make sure to enter the - username first or provide a loginName as searchParam. - -
    - )} - - {session && ( - - )} - - {passwordComplexitySettings && session?.factors?.user?.id && ( - - )} -
    -
    - ); -} diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx new file mode 100644 index 0000000000..cf8a970cda --- /dev/null +++ b/apps/login/src/app/(login)/password/change/page.tsx @@ -0,0 +1,80 @@ +import { Alert } from "@/components/alert"; +import { ChangePasswordForm } from "@/components/change-password-form"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { UserAvatar } from "@/components/user-avatar"; +import { loadMostRecentSession } from "@/lib/session"; +import { + getBrandingSettings, + getLoginSettings, + getPasswordComplexitySettings, +} from "@/lib/zitadel"; +import { getLocale, getTranslations } from "next-intl/server"; + +export default async function Page({ + searchParams, +}: { + searchParams: Record; +}) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "password" }); + + const { loginName, organization, authRequestId, code } = searchParams; + + // also allow no session to be found (ignoreUnkownUsername) + const sessionFactors = await loadMostRecentSession({ + loginName, + organization, + }); + + const branding = await getBrandingSettings(organization); + + const passwordComplexity = await getPasswordComplexitySettings( + sessionFactors?.factors?.user?.organizationId, + ); + + const loginSettings = await getLoginSettings(organization); + + return ( + +
    +

    + {sessionFactors?.factors?.user?.displayName ?? t("change.title")} +

    +

    {t("change.description")}

    + + {/* show error only if usernames should be shown to be unknown */} + {(!sessionFactors || !loginName) && + !loginSettings?.ignoreUnknownUsernames && ( +
    + {t("error:unknownContext")} +
    + )} + + {sessionFactors && ( + + )} + + {passwordComplexity && + loginName && + sessionFactors?.factors?.user?.id ? ( + + ) : ( +
    + {t("error:failedLoading")} +
    + )} +
    +
    + ); +} diff --git a/apps/login/src/components/change-password-form.tsx b/apps/login/src/components/change-password-form.tsx index 317a377afa..d54210c1f3 100644 --- a/apps/login/src/components/change-password-form.tsx +++ b/apps/login/src/components/change-password-form.tsx @@ -6,8 +6,9 @@ import { symbolValidator, upperCaseValidator, } from "@/helpers/validators"; -import { setPassword } from "@/lib/self"; +import { setMyPassword } from "@/lib/self"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; +import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; @@ -27,15 +28,21 @@ type Inputs = type Props = { passwordComplexitySettings: PasswordComplexitySettings; - userId: string; sessionId: string; + loginName: string; + authRequestId?: string; + organization?: string; }; export function ChangePasswordForm({ passwordComplexitySettings, - userId, sessionId, + loginName, + authRequestId, + organization, }: Props) { + const t = useTranslations("password"); + const { register, handleSubmit, watch, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -51,9 +58,8 @@ export function ChangePasswordForm({ async function submitChange(values: Inputs) { setLoading(true); - const response = await setPassword({ + const response = await setMyPassword({ sessionId: sessionId, - userId: userId, password: values.password, }).catch(() => { setError("Could not change password"); @@ -61,12 +67,36 @@ export function ChangePasswordForm({ setLoading(false); + if (response && "error" in response) { + setError(response.error); + return; + } + if (!response) { setError("Could not change password"); return; } - return response; + const params = new URLSearchParams({}); + + if (loginName) { + params.append("loginName", loginName); + } + if (organization) { + params.append("organization", organization); + } + + if (authRequestId && sessionId) { + if (authRequestId) { + params.append("authRequest", authRequestId); + } + return router.push(`/login?` + params); + } else { + if (authRequestId) { + params.append("authRequestId", authRequestId); + } + return router.push(`/signedin?` + params); + } } const { errors } = formState; @@ -99,9 +129,9 @@ export function ChangePasswordForm({ autoComplete="new-password" required {...register("password", { - required: "You have to provide a password!", + required: "You have to provide a new password!", })} - label="Password" + label="New Password" error={errors.password?.message as string} />
    @@ -143,7 +173,7 @@ export function ChangePasswordForm({ onClick={handleSubmit(submitChange)} > {loading && } - continue + {t("change.submit")}
    diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index ecb77b8ab7..dc194d6530 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -61,15 +61,17 @@ export function PasswordForm({ }), authRequestId, }).catch(() => { + setLoading(false); setError("Could not verify password"); + return; }); + setLoading(false); + if (response && "error" in response && response.error) { setError(response.error); } - setLoading(false); - return response; } diff --git a/apps/login/src/lib/self.ts b/apps/login/src/lib/self.ts index 64addf845c..e9efae54fd 100644 --- a/apps/login/src/lib/self.ts +++ b/apps/login/src/lib/self.ts @@ -6,6 +6,7 @@ import { } from "@zitadel/client/v2"; import { createServerTransport } from "@zitadel/node"; import { getSessionCookieById } from "./cookies"; +import { getSession } from "./zitadel"; const transport = (token: string) => createServerTransport(token, { @@ -19,26 +20,46 @@ const sessionService = (sessionId: string) => { }); }; -const userService = (sessionId: string) => { - return getSessionCookieById({ sessionId }).then((session) => { - return createUserServiceClient(transport(session.token)); - }); +const myUserService = (sessionToken: string) => { + return createUserServiceClient(transport(sessionToken)); }; -export async function setPassword({ +export async function setMyPassword({ sessionId, - userId, password, }: { sessionId: string; - userId: string; password: string; }) { - return (await userService(sessionId)).setPassword( - { - userId, - newPassword: { password, changeRequired: false }, - }, - {}, - ); + const sessionCookie = await getSessionCookieById({ sessionId }); + + const { session } = await getSession({ + sessionId: sessionCookie.id, + sessionToken: sessionCookie.token, + }); + + if (!session) { + return { error: "Could not load session" }; + } + + const service = await myUserService(sessionCookie.token); + + if (!session?.factors?.user?.id) { + return { error: "No user id found in session" }; + } + + return service + .setPassword( + { + userId: session.factors.user.id, + newPassword: { password, changeRequired: false }, + }, + {}, + ) + .catch((error) => { + if (error.code === 7) { + return { error: "Session is not valid." }; + } + throw error; + }); } From be84e9492696ddde6b807f459369bb20617e0969 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 18:22:55 +0200 Subject: [PATCH 283/640] build with env --- .github/workflows/acceptance-tests.yml | 32 +++++++++++++++----------- apps/login/package.json | 1 - packages/zitadel-client/turbo.json | 3 --- playwright.config.ts | 2 +- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 8e528e62d0..2b990f9984 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -12,23 +12,26 @@ jobs: - name: Run Docker Compose and Install Dependencies in Parallel run: | echo "Start docker-compose in the background" - docker compose -f ./acceptance/docker-compose.yaml up -d & + docker compose -f ./acceptance/docker-compose.yaml up -d DOCKER_COMPOSE_PID=$! - - echo "Install node_modules in the foreground" + echo "Docker Compose PID: $DOCKER_COMPOSE_PID" + + echo "Install app dependencies in the foreground" npm install -g pnpm && pnpm install - echo "Generate and create a production build in the background" - pnpm build & - GENERATE_BUILD_PID=$! - - echo "Install Playwright with browsers in the background" + echo "Install Test Browsers in the background" pnpm exec playwright install --with-deps & - PLAYWRIGHT_INSTALL_PID=$! + INSTALL_BROWSERS_PID=$! + echo "Install browsers PID: $INSTALL_BROWSERS_PID" + + echo "Generate gRPC stubs in the background" + pnpm generate & + GENERATE_PID=$! + echo "Generate stubs PID: $GENERATE_PID" - echo "Wait for all background processes to complete" + echo "Wait for all background processes to finish" while true; do - wait -n $DOCKER_COMPOSE_PID $GENERATE_BUILD_PID $PLAYWRIGHT_INSTALL_PID + wait -n $DOCKER_COMPOSE_PID $INSTALL_BROWSERS_PID $GENERATE_PID EXIT_STATUS=$? if [ $EXIT_STATUS -ne 0 ]; then echo "A background process failed with exit code $EXIT_STATUS." @@ -36,11 +39,14 @@ jobs: fi # Check if all processes have finished if ! kill -0 $DOCKER_COMPOSE_PID 2>/dev/null && \ - ! kill -0 $GENERATE_BUILD_PID 2>/dev/null && \ - ! kill -0 $PLAYWRIGHT_INSTALL_PID 2>/dev/null; then + ! kill -0 $INSTALL_BROWSERS_PID 2>/dev/null && \ + ! kill -0 GENERATE_PID 2>/dev/null; then break fi done + + echo "Generate and create a production build in the foreground" + pnpm build - name: Run Playwright Tests run: pnpm exec playwright test - uses: actions/upload-artifact@v4 diff --git a/apps/login/package.json b/apps/login/package.json index 9617300a2e..8863d361e7 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -22,7 +22,6 @@ "lint:fix": "prettier --write .", "lint-staged": "lint-staged", "build": "next build", - "prestart": "pnpm build", "start": "next start", "clean": "pnpm mock:destroy && rm -rf .turbo && rm -rf node_modules && rm -rf .next" }, diff --git a/packages/zitadel-client/turbo.json b/packages/zitadel-client/turbo.json index ea36369013..52e8c763f0 100644 --- a/packages/zitadel-client/turbo.json +++ b/packages/zitadel-client/turbo.json @@ -6,9 +6,6 @@ "build": { "outputs": [ "dist/**" - ], - "dependsOn": [ - "@zitadel/proto#generate" ] } } diff --git a/playwright.config.ts b/playwright.config.ts index fed6afd502..9795c4abb0 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -75,7 +75,7 @@ export default defineConfig({ webServer: { command: 'pnpm start', url: 'http://127.0.0.1:3000', - reuseExistingServer: false, //!process.env.CI, + reuseExistingServer: !process.env.CI, timeout: 5 * 60_000, }, }); From 17e516abcdb7b65cf6e1185d637748299e42debe Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 18:25:03 +0200 Subject: [PATCH 284/640] compose to background --- .github/workflows/acceptance-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 2b990f9984..ad82736b75 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -12,7 +12,7 @@ jobs: - name: Run Docker Compose and Install Dependencies in Parallel run: | echo "Start docker-compose in the background" - docker compose -f ./acceptance/docker-compose.yaml up -d + docker compose -f ./acceptance/docker-compose.yaml up -d & DOCKER_COMPOSE_PID=$! echo "Docker Compose PID: $DOCKER_COMPOSE_PID" From 3ff49c3affa3550007409a98381db8eb238ce0d7 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 18:37:56 +0200 Subject: [PATCH 285/640] wait individually --- .github/workflows/acceptance-tests.yml | 35 +++++++++++++++----------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index ad82736b75..2ce8b5bfe7 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -29,21 +29,26 @@ jobs: GENERATE_PID=$! echo "Generate stubs PID: $GENERATE_PID" - echo "Wait for all background processes to finish" - while true; do - wait -n $DOCKER_COMPOSE_PID $INSTALL_BROWSERS_PID $GENERATE_PID - EXIT_STATUS=$? - if [ $EXIT_STATUS -ne 0 ]; then - echo "A background process failed with exit code $EXIT_STATUS." - exit $EXIT_STATUS - fi - # Check if all processes have finished - if ! kill -0 $DOCKER_COMPOSE_PID 2>/dev/null && \ - ! kill -0 $INSTALL_BROWSERS_PID 2>/dev/null && \ - ! kill -0 GENERATE_PID 2>/dev/null; then - break - fi - done + # Wait for docker-compose + wait $DOCKER_COMPOSE_PID + if [ $? -ne 0 ]; then + echo "Docker Compose failed" + exit 1 + fi + + # Wait for Playwright browser installation + wait $INSTALL_BROWSERS_PID + if [ $? -ne 0 ]; then + echo "Playwright browser installation failed" + exit 1 + fi + + # Wait for gRPC stubs generation + wait $GENERATE_PID + if [ $? -ne 0 ]; then + echo "gRPC stubs generation failed" + exit 1 + fi echo "Generate and create a production build in the foreground" pnpm build From c413805d6d31db07549ca05571ff22e215f8a30c Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 18:44:08 +0200 Subject: [PATCH 286/640] await setup --- .github/workflows/acceptance-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 2ce8b5bfe7..148de463d0 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -12,7 +12,7 @@ jobs: - name: Run Docker Compose and Install Dependencies in Parallel run: | echo "Start docker-compose in the background" - docker compose -f ./acceptance/docker-compose.yaml up -d & + docker compose -f ./acceptance/docker-compose.yaml run setup & DOCKER_COMPOSE_PID=$! echo "Docker Compose PID: $DOCKER_COMPOSE_PID" From b058b75f33ee65e997ef8a8f0a0bcf7bc76dd503 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 19:03:20 +0200 Subject: [PATCH 287/640] default uid --- acceptance/docker-compose.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index fa84b9d843..8a8ad79758 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -1,6 +1,6 @@ services: zitadel: - user: "${ZITADEL_DEV_UID}" + user: "${ZITADEL_DEV_UID:-1000}" image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:latest}" command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled --config /zitadel.yaml --steps /zitadel.yaml' ports: @@ -42,7 +42,7 @@ services: - zitadel setup: - user: "${ZITADEL_DEV_UID}" + user: "${ZITADEL_DEV_UID:-1000}" container_name: setup build: . environment: From 77fb366044e8a60dec4ecd82362752527802158d Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 19:12:16 +0200 Subject: [PATCH 288/640] use current user --- acceptance/docker-compose.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index 8a8ad79758..94a2be70a5 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -1,6 +1,6 @@ services: zitadel: - user: "${ZITADEL_DEV_UID:-1000}" + user: "${USER}" image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:latest}" command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled --config /zitadel.yaml --steps /zitadel.yaml' ports: @@ -42,7 +42,7 @@ services: - zitadel setup: - user: "${ZITADEL_DEV_UID:-1000}" + user: "${USER}" container_name: setup build: . environment: From 76430e16288c514b52d62d5bb2fa2275bc50a7b3 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 19:15:19 +0200 Subject: [PATCH 289/640] use root --- .github/workflows/acceptance-tests.yml | 4 ++++ acceptance/docker-compose.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 148de463d0..63c1e89953 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -1,5 +1,7 @@ name: Acceptance Tests on: pull_request +env: + ZITADEL_DEV_UID: root jobs: acceptance-tests: timeout-minutes: 60 @@ -11,6 +13,8 @@ jobs: node-version: lts/* - name: Run Docker Compose and Install Dependencies in Parallel run: | + cat /etc/passwd + echo "Start docker-compose in the background" docker compose -f ./acceptance/docker-compose.yaml run setup & DOCKER_COMPOSE_PID=$! diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index 94a2be70a5..fa84b9d843 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -1,6 +1,6 @@ services: zitadel: - user: "${USER}" + user: "${ZITADEL_DEV_UID}" image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:latest}" command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled --config /zitadel.yaml --steps /zitadel.yaml' ports: @@ -42,7 +42,7 @@ services: - zitadel setup: - user: "${USER}" + user: "${ZITADEL_DEV_UID}" container_name: setup build: . environment: From 5969b145e06a149398169f4de7407a7c9d1accf7 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 19:17:56 +0200 Subject: [PATCH 290/640] runneradmin --- .github/workflows/acceptance-tests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 63c1e89953..249a51875d 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -1,7 +1,7 @@ name: Acceptance Tests on: pull_request env: - ZITADEL_DEV_UID: root + ZITADEL_DEV_UID: runneradmin jobs: acceptance-tests: timeout-minutes: 60 @@ -13,8 +13,6 @@ jobs: node-version: lts/* - name: Run Docker Compose and Install Dependencies in Parallel run: | - cat /etc/passwd - echo "Start docker-compose in the background" docker compose -f ./acceptance/docker-compose.yaml run setup & DOCKER_COMPOSE_PID=$! From 4cb17953b0e9994cc244977dd204ab2f3bcf9216 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 19:19:43 +0200 Subject: [PATCH 291/640] root --- .github/workflows/acceptance-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 249a51875d..f0faa073c5 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -1,7 +1,5 @@ name: Acceptance Tests on: pull_request -env: - ZITADEL_DEV_UID: runneradmin jobs: acceptance-tests: timeout-minutes: 60 @@ -12,6 +10,8 @@ jobs: with: node-version: lts/* - name: Run Docker Compose and Install Dependencies in Parallel + env: + ZITADEL_DEV_UID: root run: | echo "Start docker-compose in the background" docker compose -f ./acceptance/docker-compose.yaml run setup & From cb8f0717293b7d0ee52f89aaf814201751607ba2 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 16 Oct 2024 20:55:37 +0200 Subject: [PATCH 292/640] simplify setup --- acceptance/Dockerfile | 1 - acceptance/docker-compose.yaml | 8 +- acceptance/{machinekey => pat}/.gitignore | 0 acceptance/{machinekey => pat}/.gitkeep | 0 acceptance/pat/zitadel-admin-sa.pat | 1 + acceptance/setup.sh | 137 ++++------------------ acceptance/zitadel.yaml | 13 +- 7 files changed, 38 insertions(+), 122 deletions(-) rename acceptance/{machinekey => pat}/.gitignore (100%) rename acceptance/{machinekey => pat}/.gitkeep (100%) create mode 100644 acceptance/pat/zitadel-admin-sa.pat diff --git a/acceptance/Dockerfile b/acceptance/Dockerfile index a2283d09a9..36f6ba8f19 100644 --- a/acceptance/Dockerfile +++ b/acceptance/Dockerfile @@ -1,6 +1,5 @@ FROM golang:1.19-alpine RUN apk add curl jq -RUN go install github.com/zitadel/zitadel-tools@v0.4.0 COPY setup.sh /setup.sh RUN chmod +x /setup.sh ENTRYPOINT [ "/setup.sh" ] diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index fa84b9d843..afc6d4d6a8 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -6,7 +6,7 @@ services: ports: - "8080:8080" volumes: - - ./machinekey:/machinekey + - ./pat:/pat - ./zitadel.yaml:/zitadel.yaml depends_on: db: @@ -46,11 +46,11 @@ services: container_name: setup build: . environment: - KEY: /key/zitadel-admin-sa.json - SERVICE: http://zitadel:8080 + PAT_FILE: /pat/zitadel-admin-sa.pat + ZITADEL_API_INTERNAL_URL: http://zitadel:8080 WRITE_ENVIRONMENT_FILE: /apps/login/.env.local volumes: - - "./machinekey:/key" + - "./pat:/pat" - "../apps/login:/apps/login" depends_on: wait_for_zitadel: diff --git a/acceptance/machinekey/.gitignore b/acceptance/pat/.gitignore similarity index 100% rename from acceptance/machinekey/.gitignore rename to acceptance/pat/.gitignore diff --git a/acceptance/machinekey/.gitkeep b/acceptance/pat/.gitkeep similarity index 100% rename from acceptance/machinekey/.gitkeep rename to acceptance/pat/.gitkeep diff --git a/acceptance/pat/zitadel-admin-sa.pat b/acceptance/pat/zitadel-admin-sa.pat new file mode 100644 index 0000000000..9ac8de6447 --- /dev/null +++ b/acceptance/pat/zitadel-admin-sa.pat @@ -0,0 +1 @@ +fEJWwOJ3lFAn-COq0QxdXz_xCGrmp8Kj2l4i-xGWbh1UM2OtNwNz3_MblwOf_Lsd13B8ORk diff --git a/acceptance/setup.sh b/acceptance/setup.sh index 3d1295d53e..5359659efa 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -1,125 +1,34 @@ #!/bin/sh -set -e +set -ex -KEY=${KEY:-./machinekey/zitadel-admin-sa.json} -echo "Using key path ${KEY} to the instance admin service account." +PAT_FILE=${PAT_FILE:-./pat/zitadel-admin-sa.pat} +ZITADEL_API_PROTOCOL="${ZITADEL_API_PROTOCOL:-http}" +ZITADEL_API_DOMAIN="${ZITADEL_API_DOMAIN:-localhost}" +ZITADEL_API_PORT="${ZITADEL_API_PORT:-8080}" +ZITADEL_API_URL="${ZITADEL_API_URL:-${ZITADEL_API_PROTOCOL}://${ZITADEL_API_DOMAIN}:${ZITADEL_API_PORT}}" +ZITADEL_API_INTERNAL_URL="${ZITADEL_API_INTERNAL_URL:-${ZITADEL_API_URL}}" -AUDIENCE=${AUDIENCE:-http://localhost:8080} -echo "Using audience ${AUDIENCE} for which the key is used." +if [ -z "${PAT}" ]; then + echo "Reading PAT from file ${PAT_FILE}" + PAT=$(cat ${PAT_FILE}) +fi -SERVICE=${SERVICE:-$AUDIENCE} -echo "Using the service ${SERVICE} to connect to ZITADEL. For example in docker compose this can differ from the audience." +if [ -z "${ZITADEL_SERVICE_USER_ID}" ]; then + echo "Reading ZITADEL_SERVICE_USER_ID from userinfo endpoint" + USERINFO_RESPONSE=$(curl -s --request POST \ + --url "${ZITADEL_API_INTERNAL_URL}/oidc/v1/userinfo" \ + --header "Authorization: Bearer ${PAT}" \ + --header "Host: ${ZITADEL_API_DOMAIN}") + echo "Received userinfo response: ${USERINFO_RESPONSE}" + ZITADEL_SERVICE_USER_ID=$(echo "${USERINFO_RESPONSE}" | jq --raw-output '.sub') +fi WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.local} echo "Writing environment file to ${WRITE_ENVIRONMENT_FILE} when done." -AUDIENCE_HOST="$(echo $AUDIENCE | cut -d/ -f3)" -echo "Deferred the Host header ${AUDIENCE_HOST} which will be sent in requests that ZITADEL then maps to a virtual instance" - -JWT=$(zitadel-tools key2jwt --key ${KEY} --audience ${AUDIENCE}) -echo "Created JWT from Admin service account key ${JWT}" - -TOKEN_RESPONSE=$(curl -s --request POST \ - --url ${SERVICE}/oauth/v2/token \ - --header 'Content-Type: application/x-www-form-urlencoded' \ - --header "Host: ${AUDIENCE_HOST}" \ - --data grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \ - --data scope='openid profile email urn:zitadel:iam:org:project:id:zitadel:aud' \ - --data assertion="${JWT}") -echo "Got response from token endpoint:" -echo "${TOKEN_RESPONSE}" | jq - -TOKEN=$(echo -n ${TOKEN_RESPONSE} | jq --raw-output '.access_token') -echo "Extracted access token ${TOKEN}" - -ORG_RESPONSE=$(curl -s --request GET \ - --url ${SERVICE}/admin/v1/orgs/default \ - --header 'Accept: application/json' \ - --header "Authorization: Bearer ${TOKEN}" \ - --header "Host: ${AUDIENCE_HOST}") -echo "Got default org response:" -echo "${ORG_RESPONSE}" | jq - -ORG_ID=$(echo -n ${ORG_RESPONSE} | jq --raw-output '.org.id') -echo "Extracted default org id ${ORG_ID}" - -echo "ZITADEL_API_URL=${AUDIENCE} -ZITADEL_ORG_ID=${ORG_ID} -ZITADEL_SERVICE_USER_TOKEN=${TOKEN}" > ${WRITE_ENVIRONMENT_FILE} +echo "ZITADEL_API_URL=${ZITADEL_API_URL} +ZITADEL_SERVICE_USER_ID=${ZITADEL_SERVICE_USER_ID} +ZITADEL_SERVICE_USER_TOKEN=${PAT}" > ${WRITE_ENVIRONMENT_FILE} echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}" cat ${WRITE_ENVIRONMENT_FILE} - -if ! grep -q 'localhost' ${WRITE_ENVIRONMENT_FILE}; then - echo "Not developing against localhost, so creating a human user might not be necessary" - exit 0 -fi - -HUMAN_USER_USERNAME="zitadel-admin@zitadel.localhost" -HUMAN_USER_PASSWORD="Password1!" - -HUMAN_USER_PAYLOAD=$(cat << EOM -{ - "userName": "${HUMAN_USER_USERNAME}", - "profile": { - "firstName": "ZITADEL", - "lastName": "Admin", - "displayName": "ZITADEL Admin", - "preferredLanguage": "en" - }, - "email": { - "email": "zitadel-admin@zitadel.localhost", - "isEmailVerified": true - }, - "password": "${HUMAN_USER_PASSWORD}", - "passwordChangeRequired": false -} -EOM -) -echo "Creating human user" -echo "${HUMAN_USER_PAYLOAD}" | jq - -HUMAN_USER_RESPONSE=$(curl -s --request POST \ - --url ${SERVICE}/management/v1/users/human/_import \ - --header 'Content-Type: application/json' \ - --header 'Accept: application/json' \ - --header "Authorization: Bearer ${TOKEN}" \ - --header "Host: ${AUDIENCE_HOST}" \ - --data-raw "${HUMAN_USER_PAYLOAD}") -echo "Create human user response" -echo "${HUMAN_USER_RESPONSE}" | jq - -if [ "$(echo -n "${HUMAN_USER_RESPONSE}" | jq --raw-output '.code')" == "6" ]; then - echo "admin user already exists" - exit 0 -fi - -HUMAN_USER_ID=$(echo -n ${HUMAN_USER_RESPONSE} | jq --raw-output '.userId') -echo "Extracted human user id ${HUMAN_USER_ID}" - -HUMAN_ADMIN_PAYLOAD=$(cat << EOM -{ - "userId": "${HUMAN_USER_ID}", - "roles": [ - "IAM_OWNER" - ] -} -EOM -) -echo "Granting iam owner to human user" -echo "${HUMAN_ADMIN_PAYLOAD}" | jq - -HUMAN_ADMIN_RESPONSE=$(curl -s --request POST \ - --url ${SERVICE}/admin/v1/members \ - --header 'Content-Type: application/json' \ - --header 'Accept: application/json' \ - --header "Authorization: Bearer ${TOKEN}" \ - --header "Host: ${AUDIENCE_HOST}" \ - --data-raw "${HUMAN_ADMIN_PAYLOAD}") - -echo "Grant iam owner to human user response" -echo "${HUMAN_ADMIN_RESPONSE}" | jq - -echo "You can now log in at ${AUDIENCE}/ui/login" -echo "username: ${HUMAN_USER_USERNAME}" -echo "password: ${HUMAN_USER_PASSWORD}" \ No newline at end of file diff --git a/acceptance/zitadel.yaml b/acceptance/zitadel.yaml index 07a79bbb6b..1fe754dcd9 100644 --- a/acceptance/zitadel.yaml +++ b/acceptance/zitadel.yaml @@ -1,12 +1,19 @@ FirstInstance: - MachineKeyPath: /machinekey/zitadel-admin-sa.json + PatPath: /pat/zitadel-admin-sa.pat Org: + Human: + UserName: zitadel-admin + FirstName: ZITADEL + LastName: Admin + Password: Password1! + PasswordChangeRequired: true + PreferredLanguage: en Machine: Machine: Username: zitadel-admin-sa Name: Admin - MachineKey: - Type: 1 + Pat: + ExpirationDate: 2099-01-01T00:00:00Z Database: EventPushConnRatio: 0.2 # 4 From bfe3448f5a961cb02b19901a1a15bdca148ce196 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 17 Oct 2024 10:06:17 +0200 Subject: [PATCH 293/640] document password pages, move redirects to `sendPassword` server action --- apps/login/readme.md | 40 +++++ apps/login/screenshots/password_change.png | Bin 0 -> 123203 bytes apps/login/screenshots/password_set.png | Bin 0 -> 153578 bytes apps/login/src/components/login-otp.tsx | 6 +- apps/login/src/components/password-form.tsx | 143 +--------------- .../src/components/set-password-form.tsx | 66 ++++--- apps/login/src/lib/server/password.ts | 162 +++++++++++++++++- 7 files changed, 252 insertions(+), 165 deletions(-) create mode 100644 apps/login/screenshots/password_change.png create mode 100644 apps/login/screenshots/password_set.png diff --git a/apps/login/readme.md b/apps/login/readme.md index f95a63619d..14a9e386c5 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -24,6 +24,10 @@ This diagram shows the available pages and flows. passkey --> B[signedin] password -- hasMFA --> mfa password -- allowPasskeys --> passkey-add + password -- reset --> password-set + email -- reset --> password-set + password -- userstate=initial --> password-change + mfa --> otp otp --> B[signedin] mfa--> u2f @@ -103,10 +107,14 @@ Requests to the APIs made: - `listAuthenticationMethodTypes` - `getSession()` - `updateSession()` +- `listUsers()` +- `getUserById()` **MFA AVAILABLE:** After the password has been submitted, additional authentication methods are loaded. If the user has set up an additional **single** second factor, it is redirected to add the next factor. Depending on the available method he is redirected to `/otp/time-based`,`/otp/sms?`, `/otp/email?` or `/u2f?`. If the user has multiple second factors, he is redirected to `/mfa` to select his preferred method to continue. +**NO MFA, USER STATE INITIAL** If the user has no MFA methods and is in an initial state, we redirect to `/password/change` where a new password can be set. + **NO MFA, FORCE MFA:** If no MFA method is available, and the settings force MFA, the user is sent to `/mfa/set` which prompts to setup a second factor. **PROMPT PASSKEY** If the settings do not enforce MFA, we check if passkeys are allowed with `loginSettings?.passkeysType === PasskeysType.ALLOWED` and redirect the user to `/passkey/set` if no passkeys are setup. This step can be skipped. @@ -115,6 +123,38 @@ If none of the previous conditions apply, we continue to sign in. > NOTE: `listAuthenticationMethodTypes()` does not consider different domains for u2f methods or passkeys. The check whether a user should be redirected to one of the pages `/passkey` or `/u2f`, should be extended to use a domain filter (https://github.com/zitadel/zitadel/issues/8615) +### /password/change + +This page allows to change the password. It is used after a user is in an initial state and is required to change the password, or it can be directly invoked with an active session. + +/password/change + +Requests to the APIs made: + +- `getLoginSettings(org?)` +- `getPasswordComplexitySettings(user?)` +- `getBrandingSettings(org?)` +- `getSession()` +- `setPassword()` + +> NOTE: The request to change the password is using the session of the user itself not the service user, therefore no code is required. + +### /password/set + +This page allows to set a password. It is used after a user has requested to reset the password on the `/password` page. + +/password/set + +Requests to the APIs made: + +- `getLoginSettings(org?)` +- `getPasswordComplexitySettings(user?)` +- `getBrandingSettings(org?)` +- `getUserByID()` +- `setPassword()` + +The page allows to enter a code or be invoked directly from a email link which prefills the code. The user can enter a new password and submit. + ### /otp/[method] This page shows a code field to check an otp method. The session of the user is then hydrated with the respective factor. Supported methods are `time-based`, `sms` and `email`. diff --git a/apps/login/screenshots/password_change.png b/apps/login/screenshots/password_change.png new file mode 100644 index 0000000000000000000000000000000000000000..183de6df34278c13f445410af4f223c2fc39f6bf GIT binary patch literal 123203 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
    Ip(?%5f&yC;PRMNNOVN#|<2e49oY@23u5^Q64n} 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 M9fN(@d+me&32Kyvwg3PC literal 0 HcmV?d00001 diff --git a/apps/login/screenshots/password_set.png b/apps/login/screenshots/password_set.png new file mode 100644 index 0000000000000000000000000000000000000000..15b5ff49ad5b7d5e3e9e1efe9ceed5a7337e72c6 GIT binary patch literal 153578 zcmeFZbySq?7B)@{Fn}P4fFLn+gObvNNJuN)h|($D3?QO(cPUaLA>C!rUD7DsARWVe z58(SA@x13--}(LbTkEhE%)^uW-uK?uzV^QNGq05tq;RlEv5=6Ea2`rOc#MPu0wW=z zvx3oqJwbvE1V~6&AI!wXl^%+VLzL`njLj^KkdUNb$HZYMDK8Vj5bguw_aEMWN8lDU zg(8jl22Cj(m-qe+NHpGk-#{Gv=V-&efnYU&$?R&htj}TRe&MKtvm@32Pq#v$9tEHJScMgm{fBt&c)(;7l8yQUl z=_xyhY$p~D4*Dy(@rI57ccjFk3N{8i_{j-^nb|{(=sFVP&F>&DA`bHJ%CG9@Uei5B zQaZtJXnLIM;yaF)^)MVAe%>klrt;?Wii|Qsju`0dFl>?M zN?RqM)lcH(a7iTa_abwpU}7*#MANMQKET=rjvA4PF47$6jOiSV$&_q|x1odzc`MjG z*yT3C&RCGQS2K6%mAj_B5!c#02giPJHpS%#pL192IqvZCCp*$IvwHyZ*qlf>L!%y+ z>;7=d)yBgoY*S9|osb^uUhK$?Z2$IIG_x~1Z#Ab%kNlfQ5R>7T?LBw=Z{HH=g5hvN zN#EXRL~3e{x{ma?iF5h0>vc`^LLW8hipi5J4iaZJy8#fvpu4mF;QPZF?(SG zy~yZ|H#JZMXFwA$;w>~E2~%Q}^`^Zp!(Z74ceL)BYqEkA~5N zzeRlyv-^epjE4(>4!U}?)OYklY7f+U?1SdQnMXIFOzBIE!qIeruMIQqD?QJ75Dh9L zRei-Qq55`3sVm26&`O2F{MH^xh=g+AwNkn<9CBFbgPq=lrJ*H52mWfTNtm_xwB*4X zsov?k&w`1*qRa+K@v~-mBnkL&jLS; zemwmlh=zvdiAL;ygmvFv$Dh-G7E7J*_@+n{Bdz_7q|$I&8Y^mYsYMl9-oMCsmv=L7Fh@t3!>mmoLP}PQClmJ`$t!os%BQR4s1-V?uLlW;PpS*6dgi%h924M_zO4G{ z5t2#j&ckkT&7z-slG`=WKaqi((!5D)LF830JH~>e5dl-?SUt?Z0Id%=`6KYOEeCRc;fempv6<|Jv)FSE^Ut z83F2BqkMRHw|GDJQ&BZTi3AL;ePoCI4r zJ2`WCilj(+VRx*euxWb`+hN zT5Z~Tb?|MoZ#8$ZZSTXb$AZz!mji*dJzt}*GEc~j>h{aGV_nkr&sv>~3Jog`(>+c- zu{;Glnd&3z=j#>WyYR?UbGYOwIb8j)0%U;Vi&Bn0v1n@Vl5E~Boo+w#zOfwgSjk1n zwYS7z$Kavuj*g4<%!HLaWe-;(L*mFSl>7In#_y-TNPJ=Z@{E&GP}ITe%jx2i@+T~x z#y;hKmuN*)eyQ-aIN185eM zgcd_SL%l06hVm`04bK&lwC?rfYsu;)Rqu}oCDlgO9<06{;aRj;TwFLPJkZzIPS=1i zRW>wPeX6JI6$N`QO_(-3ANjiJf7-k%!7Gs|p&#V>WyHG_{&B5kTmE&D0H2`MR1I2< zMCtsLT2HU z$EKQ#`rzj9Sl#o37U5IjihZ~B{PFC%LeuFucr#oW+<+&$vGYP}S6$=9m8gRS0 z4^yLo_U{*Y(-=~FL`A)*7Mo3jDvb^e`@ftG)8&V;f6=KP;BCm?vFn}BQ$H=xF*hmG zuUzrkTjLqAFe%_FfFtjOL;dLBeE-mdaQ4Y5TaPxe&B;W) zU4Z@K*GX6BnoWtR)vAUqFPrNPCR<`-etg$UC_{zYJ%e3!4j&vnTvpD{j%i4Mt4*&C zx)=0h7tU%PmPo;C&(s@C;Af5y1pkriGWI0#ke`#ENxoKBE z4ZNP7D~g4pf|7=k=u^^D!3}I)*=HNA?g0n+8E-NaMc;YfI?*_M2v;65Syk;X$~d3hveU>l4CLMBB*1GbQX zUlHWne{V}7Ga{j0>_ueA> z9rdqmbXMRPl9;mi!-v49@>4q_BWrt88;2Ch6kT8krmeK5JrWWT-T5!_!^gB+!2Q3O zsc1N8$jb>lwXtG<^32B2h~34?_WV9bLM{Tprj?Py6NrnIrM10)i!k-Y83Mre`ECws z$i*oR7Q)mT@=6eK8#^NiFZ&(#JJcdr5C}xb?wPT`;|G$zE(iV-rZ#nOuod9oaCUZP zcjjWZu`}Vg%g@ixafg$GlamcNgU#O6+Tn={o3%a7wx4rVsikn{UK zF|=`X5T>R+f6(7QmvS1pnEmTX*7m=K1q_hm{0PTg_B$MZ-y678=zOn$l9`K6G>+FKv4zw1kCLG2bBo;!+7}# zY$J0i3!aXPA|XMM9zGCLaY0_2#7Mp=LDcwN3jOXI>bKOlknJ0jyVr7a(hYGT=yLJM zk6yoh3nW+QAT&&0#sv?z1u})Z&FsFA4?kp-ddb1xnquovau z_Clc0+kf6137HBCrTO>0D4^1-Qk)BQ2W1YDP6B};p@6UaK}H$-a~BHujxUV&-_Asl zgfXFWqy9gNCW+UJ{<|hF6w?U4^95%7?{@})O;N9k1xVQl{wL9HA!ps{z4mW+h9Mza z{h@o{-&HkTUzl+R<$p%;%d7rp z6#qXliuYQ#b2Kwva+@mYuZ-leIIa#xXq8z!%unPwC`sm>w9ev6s8^;#a5X%}Pia3>xE$p4XEklniiT5lrcW%#dWiYmE z#FEj1SLf+`1bo`r8>b_)0pSvj)k|S|ecyZ9^m-}Am>)tvAO?=+jnhDR>a}(99l^VG z(sa6t6mXm3b2TLbkLbr_5CxQY7fE`!T^aY0pzD_y4Pf4v1A?T z)UMUKvl&7*o8pwSA-)eZEF}+XY|iP7+mj3R!5OiclPEv^s27CLqVi^?x}%hkhT3xf zw_EuPNkRQUIIAO^#2*gYELx{K;$-^@8`^F}Wx$%jzN-D`+|!McAtP~#W#(?{kF)Vu zPwlJtVhm!E@i4B6q(*?8;#Q>QmpFA3R5v$IFTG{oR~SrthbS1Lfz|+$=bM18B$}aV zImk5mik{IoG%_EO-J|_Ix_?MTNz67_=P`HX$5Js^?lWtIUa?@y?@oHiO5#y8PTyqZfxk8J+p^(yQWIticwTirDj*v^U-qPMiagQ3>;6e74 zIc;?`Z~7|Fdq;{Jr?<3mn&@tR`i&$0o4*SI38zjUz#+}A;$&!AtCGmuQ$a-mF?G?5 z`tRM&rvWVLlVIzQhS;d^JDS$}p;w+A%~B*;mq`EoP1|*ojahcB-GI~M@y+?+whn({yP{)46j01= z0-sKX54WDT?xt%}P$vQQnNIucHI~<@pRN)iBdozf?EzZ056gQaUJ3fFL5RbW&h?!= z`SW|U9S>lu#Y+!zVBZo}dbmc0{ugFk@S|6BMNMO6nUW!d#irY7dF$N3usx+ywvesuMYplR9sVfdi!E#pIS9#cNv6H16_LKgN0l-8pdQ6l-9=>QN3 z5rC$TG`N8Uqqp}4*eibk2P+%%j3nFDHIh|J|n2HLmY5tW9*8qaF& z_XU`ty@P8hWFUSTW2r0i2@OSiYmaw`K?R+W-Qr^L$wBgI6Ehf7##g-CDiaxUb#mKp zvN&b&Ixl zBBR*h&5&{5bg&$dk%%d=KjOU|O$Y&=!W(HBbOjYv%AxHyQH5Qeu|j}?P?|Y)3k@_} zSq%lmKmW|+sw(KP@F1q&7~iUEzfObdkuyIqV>6Gu4@f)|(Dmha8-=D~x*(%)K@DCm zK(=JZU_PaUXR$+P3^ZW7eSEDxB=59gW;?t?#?uGC%_B1CeHK&;7e%=1LMQKCA895N zBKxuywNDKOq95MY%ByW5OXO8i(fQHPp?Jl#kbIbF!+8PCvvCsPRTX3DKK!EKk0kbG zg4$fO4+)`b5km~Qvl`^Z0G-*{BDiYCGAJN$t5|r(zNrMNL)aS$U^uT5CVw9d5(*}! zDGX}gLO8|`-qkQeMekRmb&!my(U!!U>lD0+4`z$>gP8QLfv?L!!-s;2jUv39 zI$|exE#(HJ^|cElY5K{ve8WI7h4fL(tHv1*odae<>zNQR6NZawB!cwNaDsUyZs-At zmIMsK(7Oh@`UE*3UFQee^;X{RLz;YQN(gK>cCudu0T5a(k`KwPI~fyXAh#RA5b3Rq zobDQ23Do;RZbksibfWJ)D}pd8gX!r0;51WbbJ z`;cJ;@R&J>23sNu@$26oGo*F_;;mw{Aq0>+{9rlS_ph>V%{3aH(o<*E;JqL4y*C!V8y zw8LBCj}O_&8O%|HHEz#d1FRpoHuD^T?%h`=ziMbIkQ%@wm_gthaOnH#Zt*^(I#}=f z{tGO`QobgHes>=vD^@UMR3~@W)=~omCPU7>;>chXOrSIh?7wYzdX5uhxqlp|J=bRE2Oj{! zX>*cCYC>-V!w}QsLuJSPefrCDoTq} zj0CKa>@}@_=!A;Y6>)Fe0P3%>#*b!SuH%LKHAW=0P*vt>x$Es33X_A0}Y@b6EmcFd{r0@M179^K2-fP}m=0ynlg&i~{V( zx$6Z3ZWTKC$MG9Cw@`o^i&6b|a=d;(NEmmIBJAp*kWe0;S8D)s0-gC*`U3-qMKjX!j#mr~B6dl&09i4a7xxN)X#gVDNEa+f{5SRbnEz$% zKLFzb4E<)-t$TKVda^WNFMvrT5h0^ULH`g#^*#_!fC+qgOBBF16e;sRj$>j0Q9a5p z9q_*YMh?K?0%ggIK%!IdaYX_#I8t$FRfw zfO>Xl$iMLAA3XNbr?UW;#t*-0+0YogOLwOF`k&lc?;N-oY5zMpB68mTxY08E=xUgN zLNI|Pi*svG*93t(BcvZMwf^ZRRF`;{r1qciE*%+Vy@C(`6|wo+DaVO(RQy>@V*C8} z7^$?5)+z=pnzTrg@d1$>FTVLE>OXkwrB4q~LubI!S1mhT23P|*pFq6X|K!f+6anv? zBK$XUNUJ=0{(5#h=PJlU!e3lMU3nfW3TQ!^mdW8C!0-a^1_&F zpQEDdIC0Kco&@^*gU4R_v>86c7WD3_Wh>GBa_3k7(VYRB z&JV_F`|sqSeVDQE^AM!@J|#_z@q7SC`3b<3D)prB9avZj18xs%7Wg_~p)@ z|C2j60vS6Q$p62SgZ_ppkSe(d{Q=@&TB=K^TW1Eaj{n9(Eb@N`!%IRr5cq%PH2_om zZ)k%uP!;(HkG=G1t^cp|1t^yMkG}lDvi`61MFpC!SLeoQacsiGv)F@>*V-sjY!@K< z6&cd)K$qYbOLPf-S3oB`1m@J7?Fp|REYugx9K&x(W~KE0>Dpf1u4metE~W)D*#05< ztR&wlcTHK7t|f_ooy)8jvSXodi7JizYis^N0DDJfDd|?o>hMMyJzJ9EXNlsUG;H{4hHyiVE7RlvHr09 zlqZ75n^5$U!M~#WJ5t0F-_FGRe8EIRLJ5ID0i^@rJqI+0MM0g5)tqd=p3?=``r9DF`9)$9v8{hsT$d@1G86*>}Ax z?vs>%_UY^G)?sZMs=bxrz3ITyqY3Q7)&z2nP>-;f%P{ojfWv2b4PjLc)YYec}&rmdji%P_|eU#B*Vu%&fo|n;l-O zcRIqLij~G)iI2C|rQTLyH5*O5XtQgZ7jDkeyethuE^-my^t&o+66f%b6nRrkEPoz=2)PZD|HCD8&w#0aV+ zt1{Vkf%RSMVM_wDNw?dY&MA&_b=O5=eSJxo#^=o6sO4)F55*>RlrLy#ot-Sy{9HYq zwktb{hSTChdQp?V)#p#}?e0fEUB3UYZMs+@kC%{CqG*}^c2ZAm!eFkh)Ud9zRi6}- zLYf}qmj(`L!O#vYajhW$WlfYF%(@7t zuYNuyKUeq%C_4&hETUn3;r4v5-}TmeEuKd|=Q$@G#-v1q6kdvM*?tIO=71KB7YII_ z)|-5GJ?fRBAIPb1mXHw6oFN$&=i@x_w1M#Gcra?bV`f|?3Xva9_;Z^ft>bP>8il31 zOn{T!QRZUDu(lQchaG)Z-Ez5w&IFdpf>jodhU1qFV@D>Bkx_&{UZ3*nb12n*mogjf zP8mG~x0cnWEc=l|b3G=nu(A~-t0j{nP?o_P;g3z+Cb;RzII_RQQsR|JUM26!vR6`9 z-x6bYza~Qh761F&v^@`kcSj53lym(oZmnTl-dHxxoaZ4J8v3ge1QhM09m>Y#FBqL# zO;!^c4Gf^Cwm5iXPOUziKYeN*2<|-ZaGq9-da8J@!(qmosYFH-*_UF(DJGA7)9_6C zu6Jg%a{Zy*doq*I3WKKRVC~fDBi5=a{hsK93d^z@R_+W4z2YsVll2;tcJ~CLufZv_ zx`i^6idj}2+G)>V>5h5%^Z5v&C?_mYIhJtY13^x!y1M(DKf*{H3(4$y&-Pb1^(`3A zzKT?|7oAAOROuedl26$cF3tiM-M$ac%n)1(ZOCmFxz>8l7frsDLec%alAj9|+>Al4m?Av#Qh`CtKM}I1#Xp4` zy&N2AyZ1qF6~5X#JMXRUNnRH8Z8Z`XZ@2zCY1QuAZnH)EA54qw-cv3tR`3mD<`!(l zFhy;f!o%4x(M+{M_NX_Bc(ms~&5 zTJBnnSd9;4XfIy_F=Tpy~vKweuspmt*Hqz}|i$Zo2yzQ4%HHS&`%iGj*oo*ZNx z$;atymiNw^4`suNOp70!9mmT>#YIj|JEHgYo6FR7sV58S2leLbhH>>5u?wZO_0?d| zO$cIj%ZX&?R;l0yqpR)VOsS{S>Zl@xhpF7yaWM+Wjj!&L*&>>ckL`TK^$E5X3i}5x zWu@PYD7>IC9KCxp3$Frzh8GfLRnL;J#1FcyD#mYQt$OMcX56KoBetr zZfsczVk{_Xzz)2m@={H3jo=9MDlXNNm2HXB**apac#$f?hZbQ#u5eH7g5OE#0LnhkgTiB=<;cD{Z>HU{ofaprhsI=jL8nvg znp{mXlQV`#KD!KL5odkqxe0MuT|dZ#w~=>vTK>qUtX&X3>z!KuwjJi~{AS(pSU?#D zC74=n^(hWSm-UK278|>BASb@pF%}2c>j!W*79Gs3WDsZDC@C7SJnhBk*j0*EvqVEN z?s%_7kMKw<&30?Hw5NY%bJb9;!?V|>J=T|J@T1QZ z%BSa%5_WcUyd%;sm=CfPdxNE*1Py z_FH}wuhZ`XrQN$b9QE`j$pdtmGAb~r6f^fYr=D+f4n8)e*KRJ^=zetc?9zR}s|U)> zV!8t3(vlc!Hj1CNYr}0wSp7ChQ;XS?{M*5Jr<^BArDXVF@`=>C>~*~NR6Pw}h;-l4 z8C$cjH05l(H(wxV9m%C2c-LwmM0+islX+&8>8Vknu#@a4yN3vq%}b*4@$$^mgQ4ul z!+Hik@#Hv=Ew0IgQOW6f>J=8gIlAj#w~V zhfwc4@`EyU*D!_TbGG34H(RKd!+{6#9ei6V zdyUtFL1ez`7Iq9iMOx_vFq_0{YBk}M8Y zbEK|o+cD(FxoLHD@`>CEqeD-mQfSjWjwRZwi;0>?xD5w2s>-F(ir;ThT*ODYzYB+V zzkFk*#kuzz&01l@c6qX`Om^whFS?INkbU(RJ(8RRj#V(_ z1(xCxjNKYZEx5Oc>(b!p6I9N)RiZ|^uGnd{i1*ynmIKjUSwe7M2Z08QqK!q$1{rsO z7Yh$svrOwjHO~l^KCW()o>!nDx)&i(+J;aw|Mg!*2Qjru%V2>jlX!-8X=>pKILnSK{r*jl{xxfTsA~4VFI7 ztKBAOBV@7TQfRYgpZ6{0=N7r^3>_pse=ACNR(t>n9UI(N!D7#prL21!HFDhRan{N=d(nIf9?|RxDzCi$il>9hf zys?Dsk^S^_(P}@B)3(K?0x+IOLhLuiw8nHr!&Zr^0&VrGm7lv4DA}p5KDE2;t5Z~* z!pqRKYw3#M)~sukZZ*k+B71#d(}+8|`(uddDeF-F6z$e<@;AEt zP9?l9mXGgUR-w+l_4jfZkpDfO#hFXum{Q2geTd;`TCV%{Myr|BW5MXr*qrY>PAw`2 zQZUFUl9-A$vZo<5%6J@2@l~sXx~LF2ZTF)0)1JJ{Rl3DYOM%tw(PHJK{_Ybd^0Btw z8taFy`oyv9+Og&A8-aui&jUYgcxyPwfG(Ioqwe1|4BHkR;e$&{94hg&9RjkY_h|c5 zB_TpPa~WpY_%UzJK7PF3PKA+)J5(O@af|JL$9Z2rYe2r#yBX;!*(a!uZfL2$N=j^)v z_EV>0r7N!l*BG(K$s?^8awlOW4y;azvnLUtj2sVW~qyeM7?% zJU9{8u0MCN-J-A|rWkMqMPU)G{YjIs=q?AD*9vTX#d~Vnt?o7=X+O`3jp&N|jB^#0 zq(@#@0!8>g5`)|o^Qk?c;sGC@z~8mr-x z>AHjPZ2B+-_v~%@e4{Fo8ym!c8bRPQ&p9ei`LPDK7ajcg8MVlN=` z9Fo-}R(KZw)A%Kt=YuQ}<^lG^B_#q1^IYwUg3g3((G#mp#~>@GD1f|Y=9^qJZb0X; zFRPv_zb|5>qo8nz@;!2zG-`=XOo*&rCO4_uA!8Q^&{>RZVXK%P(-uf|G;d#MAVjch zmnqcM$iW~9NKM!Ey&)mPx(r|RiJQa7cKKX(%Ce2dhsO_trg|!-y!1l_w?CYtqv;}| zg;GS`{l)j8Nw>CEsjnES4JRwYeV-GY`lQ%bGKiTj0#}~iUj^}|_lP_17fCMi`E)n0 z`eYzURWb2lt1zXatc4qn(bd8?FzhT(xCVw-19RqlM`@V{KOB}|iNv{OeKL*9kwO6$ z@vUIc9ZVtk=lD`7oR!6Jvt3yINQF9I6+kJDIT;0TGWQft9zN>hXsRyOdqBrHxXx!Y zDoMnuaZ4q{?eWV{)4oTb^Bl8?oZ}*`RSWsGzV`FS#roR63S|&%pPa>hi#;HFilRCY zxmA^o@e(I;zlAd8XQ~r>#gvmG)0wAO`*DsxON~=R>LiDvJIm=S@kwAM9SCS$eB&xv z@nlF{yln7It3y=qbX&#rh~TlZsJFKHNP$sdEPynR1X7(f+kt4smF-Dy@u3egzYe5v zlDI;Gi3P0Z@zr*s=F3q z;#FdR9ipWhrY7gN#1S#W%ll0XqDLQ+N9JX`@BTE|yj>6>%dwKYKb2ndvQ55XBB6Xr z|4rI=*&dNtUI{TNNa^Uw9e`pFOmiO|&1^OGZ&YUZ8hO4MKWK0bpWvT6i{5*LO$rl0 zOq9G+m=v&^=izHVY)O4J7VLOoB!m~M8R;lpUkh$r)Pu)EMt}pbB$yMpN1)D6j)gJ0 zM+EWMxaUmsoJOqGpA^^);s>-QTJQ!7LE~f#V_N9tUwicElkCl*dMa{t>pRUm{`?$m zQa5t8e>~`0&*v1yr6}5_Pk748bfm4dS*fUHD0DtqZm=1EAVm`Exv_0wH}v*6MujBF z3}neaJF(J!`01s^3%-Y%UMJ*SDRVEDmT8%8C;FRBdoZpRlsLJR)t;P5*+rP~9Y4UX zSu1O?=5*S%)ihb(6&U+knlBL}C&oCrAHl3u#-*TOHZD+Y z)ZjPPUNH^Fb7UO8sF^)u`n#O-jHzV-0lx4R7`-15a_1gH37TU@_^(ka65yCOH{F{b zxULt~m1q_DT)9g?-*6$gurJ+f%oMezKgD^P0p6MNJ?B0QA`HcwSLt&+xOSVW?ie>% zSJzJ=kp`mNht&5&^lZxx?Zb%aWH1d;EUTuW9ad>Xrfi(?Sj;o5`$vSOvxwEg9i!!9 zqqi;n<&nA9YG)~`fSe~`%J`yn4N7#eCLu=DMQ1($l#I9-`Y%=+-#Rr5T;L`v+S^oJ z{b6>8z{b9oGP;sl9e%${P-})h zI`~BU;<=p{R5`rL@v|EPP-Y*ce2De*O))+|%9OHTxE1WD8?&4hAE%SKkIZZ;E2d() z${hqwRH~{FIA)k@?PFn1N&sP(sy|yeDdv3|*c4WUgO}r;mb4N6oPa)VS#K&Sv2<&@ zMKVxipVPVz*83_VOa>_U{ZQL3AD9bNl=INoHQ|*YFrNChNaX4;F*~aA^inRnizOJw z?AOr;P8Y18y=2Z4kQkB*9SBK04?mmocXQt87vM_>T5_3m#ag)UM#SlR(ijFE<|T5Q zv3}a1V02%rmrW3_EAu=49KHCi1!udsZzNY)d!h4?(KDlrEZM0(ok-~Csw``+n1LE7P;=?%Ef{*G(dv4Va(Mfo`8){Qb(@hdc z+#0?KQ7CPG6Fr&e_}Q8f);3WkF|XRS0^bo#qtopcZE@$kpXHP_fSct7&)0EX57A=} z{(x&>H8DF@pr;Um!#B~Wm2~_#`?hI;PejJo*P&$T7nSC@^H^}4Edeb3*@e}(t=+vS zSLR8%&oJXd(~Hq~C!am#!ZDBY3Xi^6fz5k|T>3^M57yWks>zo28C9t$~-Z}MC!62Kw zB8s!b_$EKd6PW0 z&8p%vyUL2u=iFZ~VKLQPVuRusx<-(voE@6%xzN^MJwIH0cD&kF*w7vKk$}FVCC!7v zozn8v(aZ#^Ym(4G5?iOg*9(dhNDIVzAxKTyU zg3qWp(;pZpDOpydOiKAqJuR)!s)=aM=o^DrvA@HXQjpBS3b2tgJ&gdpV0{ zYQEp1W0HLXy+@veq0D?Jx@OZ;Db;ghzH+E`y>ynx)Ifi8uOUoWHI_p^RV@ukj1-fF znNcYMNeqWe&HhIvhEYDx$ZqI+pYqX?Md|HL>}PYIX6HK!>kDQ^m2iCCv*9hCRGI4| zn*=njFkCjZfHD=Q&;5OMd>4MMO-?GZ+VuYjv1qCquNY z`kLJlU*ZrnejB8zQCwQ?>t)Y=e8Z^oRHcR5_G7llEQV&g?_uK2&CxtzFaoOpdp|$H zXp7P1*xj#w*7lhHWI}eX;>UEyJuDlla7vWt^=Uh^GF4chpHhF<#9Gs?m)RB4UQp4P zlPX(oP%%NTU~-SBuwI(vN{ofuZUn~}ACDD-d+DeIu865O0og5j**JDt_XcVfV3n_A zVlX>Ey^I>!7g$`C#uc1{7K$MI26~_jwn?T_BctHpnubHMm9c=T()bGdxl*e2X%@qv z^_Z7!3RXbR*LjPvaM<6icaeDCylRH+TJr1abXSWOqZCCsUVnP9u@jBE{%%$?+hy~7 z_>1fCcaC{hwZ|YZ^=r)pai2gCc%!l`%(6k3Ws?;hmz24%et()Q6~E?w`U=+PBd>yL zlb!^sA~&qd_PLIp-&$hB!(pDp-axL`Up`4fDA4~^o-gv#b$HxT?SMS588bE!YcypR zVXJaaE#tJOD4S7QnZ-zCmo|4CJ8?42c$xKb-NDa1o7U_!ILEh*d}onxI@wrI$9@pc zuqi~_rp#YwdR9i$J-@$dvf_F@U#nPp8}790fq0>e3N8vxAeo`0xM_=g+Thk~r{>So zpu+a?g?OVFaa@r`_hvr|ZG!HuL>_IClOHSzceWpDK3RxuVFGf)DZvfEW@&qb4kDdB zgyTF9M#ak~YO7AIC+g4k-ATBdB2RyI6p}dSj&JrEtIrly#StPZrUU24H?!K{G!zkE zf)KAhR7|;ryc-zJZeLiqx0>)dd`CXTY=O&d_}Ls{zlO4ZWogl2c$uMNf4m}+{x)yy z`75dtJnq?u7xltVM{^k!HLuAY$H{_lp4$af2+vjF&FTSMV>KO* zCk&%5vmu?1J#kIW*$PklXcMI#yaSYZwnK9Hoq(al7zfHnUnpv7%C z&S9DM_#G~MVzE1AQ9kZ``7-kufChFT0SvM<%NmM~j*g%049`yUe6&Aq((~;hmgDCx ztt_k5=VPzmHr&=qby;yenmX1!oG2xiPhO6zc3hPn&j<@Ampicp8ZR~Wwk3k6 zX08aou)1kgMBSERm$KcF5%H~xYCUb&XPr-(lE@rp>ifUyl$1ZI+ZZkJj7$O* zf@z12l&MXx0D`ccdOwGi{`)(h3E|{F$IX=P{_s~Q?BB5&wPV>VIo|0Jm9WnbCOL5V zI+uNBT?*eU?+Bebc!V0oy2oHY2{b^Z9Mq&w`#dsgAuo@CA53-#F&WF6Sr$B>s@0!6 zq0Dlh78}dT2=ppETZMVn_!uJk#$8N3<%+N(Ux^UO$#5bjyE_M_imQ;O1?UQy1K z-Rib(U&;7<@ynw+l@>2lbhPoq`eS}v^0V-YGZ=iIb6VLwF@0?qcoDkdzBk9$;H0^- zs`R(65nTr*x?wADEW7Qmj5N7!!{DXSD|enO5KS< z@2DrqZk+t!nd2SAw#^y0zpc^w>Gbo(qI3fI+~)uZL_B+5FsZQE*Mycv{~#w7?qzLP zi8QtJ6b?b?7SIthw`xAfAEMw1r=%^r>6jXf`=_pH=CAvn*mYb2KYc@fGIC z0HkNH&RSvBcG+V_VKJGWhlfe-K_ARtujcG~p@bkU-$BjPDxRbzJD0reJCj1>;+@cO z4Er^%5U82++9SK~M<(tyOh+d+{Ku68{Z3|K&-xm_P2|`>Addt&-6dF$rzk>9P4Pz2i?UsR0J^hA>ukSB*ru7nj*0tR zY_a5gG19$bWbz(Vfwu`6=k(h=%?`LNb)im90##kOU_EjB1lr>{NHc46`c(1V`u##} zf_KKp3cXHVJ#{p-3Jv<2VIwCmEsUHA8rxL#?o{b-klE>P>E=2XY}n37SIWxLvO|EusN#ei|f;9)e84opDN&%fAqwNcXAFrg72W}Hc2k{(@&B) zO}9cSqt_2g>NxahV2$sIfiC5<1SOzLO%?G(rH88*k*q;inzOm|o~SD^(c9vVFI0>X ztn$bQGfV_>UL3V`8tY3kr7%03Q|?I+%<;xp&Ct^^IqMbON)z4PgQs=rYSsm1Ip@^X zq$5<^jz{C>QlHS&X5t@AE>AL*3wj)TKAH9(5lr=3Tu#$<5#>(5>)OniYjkE?zPd{J z-id!FIjA26w0{D82|>KzQbWRyC9UJi2M;&#X=LYj!L;8Rg-XsM0$#c)YV{s#DQ^CR z8%gq6KG}#prnyxrUYJt!lVWJeS<6vB5$|!@OuB^N^J+_PTsL`!jv06ROp!v*;!QWgq{hEcd(E$_6c^a#`oxm(&T5i z^e@x7jVOmOgp%V6#cmch#JV_#IKpEWK1Kjv=&-$?K_0-@^$KQ^9gGv|*-o;UP51pa z@3VB1y`JxxJ@OUVjt=X%8KDB(n_7DALv(MQtKo0YBpOZ)1y*pL>F&IeLnQ8=3Z{&x zE}RODOmhQou?E`I!^N}`1V4Yb3&rd=O-bajXrG;>{JG(EJifHqeO!`V5YSJ4ck zM15L&epazzD>Tn}Y2&tAfTeksglQjAfD`Q0ZiJiyd@_$3eV$diZ%Pil=8TwV?AOAku`-R`sn0m0v`o_KvyU2#wVM| z_17N+S0cL8#Rb;R9?>`M)H*Y}nYg9Z0Z=w}GQhXTexPaM5ROP_Sw9-!#N|{*uh!Q! zzq7dL+P_`f@z|GdJyfv$_S?;A&ANjP?Yi%W)eW}Ua-L~kKl;+S-i&+vTt)z;Y51Oz z5`73IzC&}*adwL7NflyB(AVi`ZHJ>{Qd~WhqVLI|IQlf}YCP4vRi7!M|z{U`iiX!`NoEGJ!s*GD|UiQ?iWGf1Z$lBSpI!cua}8y z(_MTTaWKS3lAV{_yAf~jSuBf5l7TdCCpe5KCG>gI7j^eSjLBiXcPCLXKxgE;h5NkBXKEevG4?uvt2y=gqa0d@}w`)N#%)RG^o4 z3b7zI70u|`uq*i8X(vVL_#jAuX2nT&c~)i5Q?T%b!uQ_K1^YM2$rGNZl6-lCtUxZD z`Gt3Iv)NtTN^Jb+P@Mi;NN1J%5~?0~nX)+Bv93^W@Y-?Ds*P*MagUz+8a>}`E%RYP zyy;k(XbkNLvM)8)k4y zx2K_73$d$jyLrk3NXJvMf%YI%3~51xTXqk7qF_@}idBH>>Aoj96v*6)g*u3PQxZMU3hS0{UiRvfn^ zh_<=v$^`JP24;i`sZ{Gd79iFVO!O zqAyzTX5V+X=}yzt@!Y=+=ZyraHo#aDi90)huaxl4?rB(h9}Rg!QD{?WU7X#9_C~^w zlUR4S%Y7Xo4r`2?VL%y@0^0O}ml20a1Hmtd92dP{ji+!b;4Q%Ox0_;U(?6x`DZIy} zI|v9r4j7(Z?#Qo!39NmiwDwxVG(iItxxQyY&gC{kQj$WHqBspdj&TlU5rn4O5ApDc zRW6Z#GD#)7?Z6p!Xu`gckexC|moTtC?TL6w0AV6$!!MXFRCngcO(_sP`&`nIY;54H zmGN?HRb)`k3a(J-Epn`<`DNqgl4(E?;>b@|inesrk)iNCE@Hz7MOZ%(A2lIxGIvCw zWebBsp=QTwWa!Bk#RP?{)IxM*?v}=%^Br~ar3ASt4^yK44`W{e6;<1{4Z|QI4T{o= zGzg;7F^EXFbQ*wwbjJV#DuPlqu0QnAEfRJl;%*Rfy|u&d6xrW76sE z6KS{D(IKJPCw`2oKR5TyeX^`h9`*@I>T*A}O>ukNwiP;NC1L??oHU&e4xaAieKAYH z9i381a@_1S+f4BF`QGV$EjPUpTAi~q0%CvS4QBgZFN#zon^==V_s*w;*o4%Z!cj10 zqk$=6F4cI+EyBI-XNr8`{?Y_HH#bhnFW~!b@s1WtE!`dBA%Ul!JJEQCkA>f41#mlw z)ko@xcSbsJSAF*|7!ns^no)gtL-G?9jTy*mL28pZ{w1{4)DEX z0D~Id1N5cN&ElJnBVGKFn`Nf>B+JF?)AG$dG|Su7Z-$&Zs~UzAYFl@#&>L_om%AG* z8o!-T4xUpT*Xj3`e}uc89M=bb_-4G>cH%PN6z{BdZninA{B8gB>!JM%R0hxmPl(Db9sey=(6pegHV)GAucU*7zQjJ-9&drB340*+cR@26eINs zUY~bBi%f*)x%ARM3=lu_318&cP869XZW`Wj5^GSp)7!W7%MyNP^Z58E%J8f3R=u*( zvzt~}UUb-;J;6BbQ`YvelMc`XmqTx^ZB~?Vmm&G2;V`Yu{G57O&xM)sJ60py$-(8 zQFz;Nef`xKCdi=BAxStMsvA1pgVdy#cJpiXwN{4ru(6zZxDYS?8XsSq{C>_)O!N$6 z+2g>|+&k^#WNY=p!5!qOx%jY;c$}f|YjryBAGOzLCvCs8#n(xW_6t7(^tPkr`F`=c zC_~4B!wTAWftHgCz7D>Kz-zpY_o2^aUxHziZkxtmo}OEiF+RH?%wUG2U=Iv^%uPBw zaQ8NEneSveyzvTbC6{(%4EXL+zu^HdojANcqWdu6v}`PY-Y&{rbu3nhkt{ zC7OMu{}}+W!r8JCrvzr36m;{XF<>Q^_1C1l1JXITiWx&$mT-^8Ji|wcR%YDiSePEc z70z$Pe{YlTY;8vl!`dC)G(;iB-Njsm1+`x*N4N03yX|Tf`YUF=cN0t<<0l zD^;1QXI7l|0S&`8@*yKuSpoB%{iSe^hz{`>Yy*GX6Ll;{crZp!m&bO%*@6e{Jm+52 zmpw&Ln`E7dM~zGo4E5cneatlEl%>I+mcezgI;wMyT1zbcdtuFF1(!(=qAwF?c@Cp{ z)iD4aInpn;-Yqhc6h|3D`RZe9$P7AoWjUj{Bh^6s_;+bi#?<*burgz5d384%l{&6_ zJW?1?@$}F`m!H+4O1U?xQ@t7hX&s~CT@`F>$MYI7a|nGvres}eAQDbvxw*JX z-+w@VbU>eB_t0Vdr0~KgZPuG2ZOnK(qN4OW>g%(H?S%3-f*-rkbc>JYZcYUaW1k4F zPIf#G0w7|%3nb2&DZqe+QEyl2^b~Y|>eI0s&=Uwy66__bFEwXz8Zf^;<~GP{+oR%! zDo48)q7qr?sF?ux9YX$XMday%dO#oiE{Sf$cPebJwY12iy~Q77H%hJ$*s&OdMb2tVj2 z)EuH1^%~c-Z$^kAbAWlt8Y743i_$8x{^5ekM|_euwE4MG()P(O47$1X47LFY%AW``W}K*@DC5>Q)bKE6@cRO2+5 zL94_9NpS;6{}6n*E#hks26N$ z3R}ZyRN0HKh*)`Qx#F_N;MQJdQacjBu|Wzxu6=Y=9M;&cXL!~f%ex)iC+&EQLiL8l zccjWS#2n|>9X2*2El$GXk7;vU_BYlBgZ4e_4Zi-i@pU6eiA;8xBm2HD)mbmhjP#2gYLxp(6YUS5@x6M7X^@Ssg*rtREzyJv60`F#=A^3nB)C~W{j zl_dlMC?_Te0XjU5rH9$3W@H!11L~#Y4SvUq0>xLxc5Fig#Obwtc`+Uq*H{>*2hgD$*3$fg_gE+w?Kj%zE)FOU21vm z!7kJ`J#t`{;goY$bKJjKwpv1jh8~YnN^SLR8rLa_?NlZ8r_&%?Gop;URClyGV7)mNQ0Mo=AFFIC#yUPk{65!79Fw%D^}5%jx4GQp z_51`#T$j&}4_EHgZdH_4TAtDNF$7&p)%+$cwOO&&h1?4DdZzmwZ*8_>t8Z)pJ>Dvp zm8oZG9Y~bv6r7i(ELi>h^+f@L$?a&6|Mlj?-K|aUlerM0vA|%uH#TEYQ;mZdBWZTT z*n8-5t5w#p1?HZrEisQaq(?bky^ABd8mLf_gB}H#uGD)!aAT=y2RY;e195jLT~;VO z>5l7xtarh-NJhGS+2uRI-mNa$3LC4j-r|x-S-PA=697i*ri|Bn7VNpb$E{8jvBw+VzD6;dldD9UpUfZQPK`TQHJb9>=g&=A*N-u|H*&=B{n z)jR)1%|9sv;8=&(ijLM&yjJP6v=dr{<$(67lm#A8t>2B-L`itw@+%`0x2=_aM(OEO}Sxw-Y-N9rPYeJ zNVBiMo|5=!3f9v+wepeJ-~bEk-t5p&#a7v zq2vp&z%iDytpOom+C$Y0^=w$B!q3e&W@FE!y_Z?v0qZDa^0mav+es2`xfwez1nEg} zdwAPA;rlFlb9rZdiiz+W*W(Q}Z=3I!pvfVi4`lzmmj)ZaG!uY4kJCbwm`P0NAL=Xb ziRe$lD=A<19y*3cF4kvh7Q`wOGY_(CB*LF??=y|g^UxE`j>37lm%RyMfME00#~wpZF_|yX*(DZ z>*6uxj05eC!KoP>BPdiI&g$G~hk+JS!8M z4(ji(*cZ@12VStHitRsiUy3K5uV|E%%CuT8a@x>0aBY2TsIhUn`i4+ZUvn5tu%6<= zyH&H~Xt$sk9v{EGCHeK%rh7lE z34SP7{dM08UcDMi#TX0ymg zMF#ugZAl7u3I(QD-0q#F+^DL5X|K8L@=Nk?YC$msk7ewWga7!HQAVQwx87$4oh`BM zsMTw+S%<`nvNw-!DbwDuTW#H_)5iiTXzoQp=qH)rH!LIdhE6r zm7oY)3lhKBoz$6)D@NzB!QOvaJ{VbnMcfiFl z0?6p6ws{MyevEyK0ub#H*pAN;i?KG$>YX&Y&_yU8P(EL)(>(KYSjT4$3{1wuS|;3maGxS z2A4KuDdnEunao)jLZ%M3T@PG-P}Wp87^(Ly`E;#)UC=?wU!a-Geb~@n0{!BHXKsWz z^o}}oOq)E#&NY_p_P<$!s+pvjp0cf>iq0%rG=_oEw%0b7~La= z^nOVFgQkNfH-;}FiPyWurN^R^A|m;bo?6!?+|4xo`;+%$Y+mQA_}teix#w)qDVP+1 za(pfKoZ-PiFndw5*U;Eef_Q^=J%5AzXj~{Gx48&ky$luLYNB;>O z?i=IT?ABC2Rp!jZNvZwbfGe2!pdVp-2c7@-$5`?c)jE;3I!&_aCc{4b+msH zD2D3w(vazTeKTaGjLkdx^L6PfW+gq3tFHq9uX`8NZ1eFz@uBWSW^d6K=()_8ObuTT zbf0*M)Musiy}+`Ouw6i~BwjatY~a0MO+M;84JZn`Z$dA#z(Qk>HrHg`!Fa*yM0I>^>1OIyfGmQq6( z^9%l}qw#B1yce?=m%w4*uNIs?JWaIzcR|TglWyjhkf*S$zv@!|axH}6_MZlYPVz^{ z8&wxNfO5?8KT=5jRiy@g`hOu5{3pTI<;$l7g|Yuk<9C$@asSOK^=g{_Utb84)P{Ej zqe>-^0zUzu8-QB;_%|9Xc8RO3%Jk-ce!zb+9$rcazTfxX2-2?Z`8TiTf2R3+e@orAlmBnjgnt|2|Ak=l#+BS9d=Pbi^CbPH=D@J% z-?WKWy1V&*f<|B2j{BwU{0HtgqDxKN`%l!bf0#GhuGAOcT$WMzH-bE}KSXe*|K$Py zNt1UeB6+|Z{xd1))ja`~xMJ1)FWhifa>v00?Zx~rgr(=*NH>J6YUzv91p)UhI6kWs z=SZ>faJQjEV9E$dF$|t$jJbaIrG$zq(3d8njIgJz|AjOuEqz4D4JkX&8x(E5k2k#yR z3}dc>^O!+~VWI5)m?}7qHHMA^cNri*uEnI5{>R1lLU+N-em*zBV#39#xB{vaz=h$& zpc?`|A+M%!@Io>E@wrEfsq`QTGF_@J6zD@PHA8{xdDqmLi~;i>A65{E0+*HK_qu8D z-RY;QRiwaABUmgo1A^^J6orc6lF10>e{!b4WC_f`0fT@?U$aL13j61; zr-@)5GbCZdx|P;hK}rAZ)6mf^K5n&FlXr2H>9o3<$Psz%w3jNR!&8uuhQU z4)DcO9RKM2AJ>L5{CQ;E|N6*25Fb0en^2N_q4v6z8y63vNQ?{Kmk1r2^a*0bqzAKW z<}uu0e;x{S?PRMud5Rot%ugm zTaPpARW7zga_PO^TjY8W)&3cHhZR{LG2Ay2)1_v@mm(yVCn>qu6cz*0+>}a z126`2G{e|6*yR`6B3MD;%SgnpuNZg~ESMj0V)@QEsQInGps4xU_?YN;fb|T23yOOQ zfgzT-5f_?)p9z^Je^B}r5<`-uQ~F^b%TVzX$oXqWY#x_hrJVI9-)Oy6?OQFI8t2W4 zixusy@_|6;!E9d1I#9BYF_1~2@^r&Vp)bjvcnoimbvi@mUm1a8 z10^CcHRMcW;GY$;^O3fLtQxvGSq*`5s+^hU;juLF{K}KE_7O!zI%ifB<&s0$4UgNt z8xMAg+*oSL{qTeM!U(n{Fe0xF=4wUhKF*c@8F<4WUZ{1Bp} zsRJ(KHc%QfjojnHf7gUjJl$QV9}Q>Js$jFkx^`*xun|1ScD_+z!LF8yAtkUN;w5iK z14pjERw847rLjO@J|MtiS4Dh;RVl15OnW;Z2Kc=Iyy=-@*|}#($@#X3+;ScE9D*0d z=#JfzkmHBy*T^O*6l8xtw#hwBIcKG~7sJ;MI1d>Y(6{@V4PFM53LhH)8YK1hd)A7F zrlrt(DtGZ942UYOPhz-o^QaKXR9e7A&Vn&vfbGm;#7c!i)X02Z<(eu|TP|t38td1* zt*)$amS)1I3D=Q0A!iM4YqDu1cGG(uNrrz*#s>_1Ub3hZzr;45$V$HHJM-4LcQ$=& z=IuHYFAK1MykvhCFzR0x@cA?aRuGIHI932`k-Y*A8Y~07b6T^1z4OJyy*AQbz|}Q6 zuumWrU<)~H?u#E^SleimO|gI5afUwj?# zpOAI%bQD0~C{*LK(5_`6T~-|RD-O_w)~7xoOl(l%8Ne+7cmaM8CY0p&Q@}HZJ_WyR zhb?dnG$reDP$!p`P>pCQa>y+5s4Mb+E6Bt9|MJNUCJUejKYgi7_MbRXE4Vbx!d?%N`Cn(gLxqZ$1_kE`K834BK4kRI9}`$oL7EO?zuD3Fe`CD zn6NB^6s~c(?@+$3h5x&QGT*Ne0zJG^KQR5=VPtq2s`q#Vek)$=d=E%@`U$72Y&Yw$ z^(Psa13R9AS|$IOMbQ5+i==OOW#(}I17j=UdkReLn3VKfc=~rg0}d?|vJd_|_ayK2 zL$l4(hQ z`QmkxY(dW;hN+s5wKFvNjR7YjqX;6w<*{y0A@&`p^Z99Hd9i(E89@M_={wt?nTLt@ zLn+B(ki9R(P2B)PL5AN3`DGyY#?x`N0&5bk2N@R|EzB4M@SXLj_Z0D4&b%BgdhyB@ z^b62yHD3%UNslkWOxo!}j^D8^cHksX=B+mbrwp)g#v-#=O|^PYL7i&*@;u#mu5w;E z`AYjiwE|jf4cC{O?mD#&(h{<+pD%1gCJ6%skWPjHV?Av{`PI9Jac3znI)1D#Ndlgfjcpd5dpGD|8-+vXtjBS>aAkXJwddRoqa4m4 z;KpgBJnDp+dD=|5rNiN6i>2_3K;sGz3(Q#E}pqB+$Z`=g+sGa^x4u>iNjLYBP+I8DPWt0TnD$| zG(G`BkM8SnzcmGc-S+1Z)`eNzJJ;PNxJTj*yepeSue+1U+%Lrv2FtK;4Y{fJ3pw(F ztX}4~><4)bxEB^%_)wjHESee?$rU3T!5(3dmc(aZ;DC&cn{Yu$ueLo zywc>kfb}?$#HCk2GzCk!*Hb@f_$69L_Y)Qg9ub?mgpi3QYa2VoQMFgs_}wY1DYf~f z1-wgBF~Eh;_{fAtRZ5zoceH9AecE7vkxBef=;i;Z(71TO2~LtRkF2vaIY&4hmQ2jo zV4vExD9e8b|P%3rt#W-MfrGo&rRLH`R(?O6in1R-f*5*Em{GjXwdI zD_;2z+KGSjA1Xh<2X998*|iGhMJF~gJ&9=|GwN~l%cfY3zz>%{A%1=rxNuBQ6%Zdg zF2a*NO3u#$^yFWFh=m&y7ksPIq!&kG$MEBTXzhi|E8}1Q0$qt!!&gIf_sCfFD)P!E zbIaG&9Co}D&ob?K%jIjL3Yj0vMcgM97}^-SGgn!_O=@MrcqtrGFfXs1tp9=4AS5;X z^k`Urw8Bd7u2K^(@V~PPe*!D{i>3Z+8U6~a`pC1?Ya<`M)i$!XA8JqeDWCr0Sw@^j zfkhtx@4gZDrB)(xD{U&=mDfZEa4JlnAf*{W>Fy|t63-*KR>RVY*N+qOnP3!u%)r^; zpJo6-xF#9ay^hY)i(-r)9Pot>Quw*P!*6rbNMEh}`N7+PujS?f#)C+eTvaGrdgRNe zBk7TSy0vdjIDfwDc&M)U;9Efk)jdg{ra~h$5zdP9l9Qp}$TxxC zsyaLl&OPD^6870?00sG5yMOA~~X*2!Mvc?2 z3<^|yxbPV_fvm{)JrLz*XbH@8+F!WN{;NdgcltD*41>~T*njJr)np8ZcXu=k5JtAV zw?O&2zKbDTYmHDwMipkM0;D`n9GXkxMm@nuNt>@9taPt2>4=%Z>TKK3Bu-AB8mi^q zemha#uS{y!dVoP|fhzQF)*R%oqFSeHL-niP9sFWSo3`14T* zP|;=iigaxkUFWEGMG}bq=6^q6KA?FtH3B`eBedlrS>?0N%)9UWEluh`5th`VviH>P z?q80+oT>{{&aU+G2V6iS;wNC4te|}f$aPSnIQUX&MHom}>A`XVZCx{-hKUFuVkc8d z(#((Pt5U@hEeBF}-H7`fERp#%QF{r{S4N|2JxWJy$9u7fuiGS*m&WO>r3IN@65Kj` zS5qh;e?O;jt_fZcHX$JT&a24kYsa|iX>fV4*s5*xqjSMlm43FNoQ3ZPNHl9}XD6XQ zOU_yAT@pm#3<~en{B{=(&Y<_nfrw7+0e$16>mRjjT}{7lm7>2m)EIJF4(zzTtF@+Q z9X5nU-(TJ1&S)2$ypP9Tfz|++n0`~>`u6ZV0lmNjSg|sw01nzG=*TAK`HPJ*?N65I z{PgmD|20cg0fVX7?2;8?QBMBUn0*1Z9aKciV7lKBRI5_jFCjS!LA=(li*VE zTd>kAdnm3BWar0zFzKbcbzW7shm^KZDHh49cKw8&emz!%v->mLs;I`Hov{3-ae-}k)tpG=xQ1lgCA(7nmd!#(`~ z4HkZ)RK0q)@LQaQWtux2;X@)kMSJ9XdfYmU-0|fr@ZNa|yHd5?p)! z*qA=!jOE98&fN)=7kVSWej4*+rHKOfyXx%uQ*wk4ow_YR2kIB+rNnKN`Ot*W`*-U* zJg`QtW*9L1)u)qQh&E9F%^vkXmLa z4P&?KX!;jyq3g#mmp1uA1^npy16_2$%&W2p*|y+=gLFLJ?ZsNB66oOyL*h}-0D8JYx!d8uy>jMHGa!3SpWe2I-)ojSArVKx27!mgL#?*4FN8Y$>X@4E^7vq})2U=>GsizttEDBl z--G2O#`zdOGyG6&`y;0TPp%5fdoJBaUpEc9)x0=_TIslA*UZ@(&d&Ws_!mv0kJh#h zviB>DSl-7Ii^?d8qn?g>Hw4#oBUZ7x?lr?jWW8d~%{fzGR6C2gS@&;=sKhvpY*^91#Gq+yG*8C+ z}tS97=x6*vzaj3@+qMltZ?-6#_lL6Ff?L<##Yn ziVLRHcQvWMf$BImYVX5WtRwS=der?cj|diKcfjfY-iI_CDRNtl}BJ;GBHGW&}Qz}@}ao*SN)NdP|#4jsOz7G)<7 zg-hdB1~vxq?~{K^8n3517tTNb?zHNCW+l-@DOo<=MQ%-fwz)w0tktfW=Jx%7I+1gy z>hoX1@V%d@h2Qz{-$epku543ocPJ*khOu^kHmghSHp4-)U1`Hp9Z~#)&cAO@pR`hQ zv6Ozn)Vvmb)@;;kYUU2?0e6|N@)c*ne*|h$3J6ZSkNm;HE}LIC*at{V-Y(|e%rt}+ zJBpYdCLJ{O&MjmfibW`~0B6D(4mELj3}yB&#IrQJ+qqz!U$>4CXrn%TtKl~&9j^Z5 z9krqRlQyk_tjY(jRLJ~7L?tXSG;`Cq`z@Px(YCr*n%i}9T(7g0hYQ-g3_ns7I%fP) z7>nncxw?bGgbF+3W#T_Ry=uY*A1D!|O#6Xsx;9R~X4Z2=q2P`37T3W@rSh;^tJpmK zYF+Nz+6C*ID{Pdg75dk*+g$@%^8l}V57rs{Kgm32^4kA7*k*5>ONI?37|RjGc5}?;Nam3jOHOCpQNlLV zK5vEIs`s1S-d=XJwk)kd72U&&RjE&MtGh0EqhHZ9qv znqQBa#WYs)``hVQScMy_66nUwv+oHxdv2L+s+4gnw23=2QM?-~K5oBTwGO_VDG9U* zAN>5y#(RcxfxG5WGgkg+mh{S7k{0tIS=S{&tclfaF&g)bRiYJlkLN3$Xy^f$!RI<2 z_jd`oA-_LnJ_AP5ob?<=K$vtu(0@=RviqC$-r!8J|7L`tXjlI@E4@5AdKw>0a?M?T zL`&@CF3+tWg2u;w6hIG-?h#B{ZI`KIVA&7Y%`@J+B4JVJNG-A3qzvLHB`;D7?Du%q? z8HTc$ZcRrFJ|B6uy0^-UKtx*lk5v+J+!$t0dCs7)WgpwM_`}xHRID?~yqno3H-nOP z_qZF{*vy=VXYu+pbDpc#|9+FRdHrK82y-aLpXWs|Y8@I;hn~q43MRHaS#N4Ro@n{B z=EJ{(`@UR{#a^snb6k3}dW{)QxiE7aq^W*O!Dq=`;A@<)H;kalJQPXWU60T4nOu2g zRq;htQS;I6vZQ3k3hGd?k}z_qxjv=xfzu+uZHpR_?$h$cm!Fk+gmwO=?^kK-COX13 zvaN-kT{shFG}I41664{=C4Myxs}~p zUfG_zqgw9NO4lh*QK`=h^ks*4YG5uc9OTHJW(F!_q#*J+za_i@=^rBQ9e>|xZ79;5 z-jiL_;}CUFOU*(vEXDlUl`}a$HLLVpk|a*xv_f}*BS)+Z+kJRlCGu=> ze5;))$|$#TtA6iUBX1zC^~t^x3UlYh;FrbZFcGmD&KPY(|JupgF(;H`VC2bjJ7xErFP{n5;5ax3YT2&^gYaBHeQY^>aJ zWtr0i(`kRlEL!q-+%&|T-7J@hx1Z@L?{>&N#T3^nv=*5YU8Fqd-gM%h!WZ{t_I6SD z;)QAe1;k)+2cyIYlZ(O&F76Lt_&z;^;7d(stgBf9&u`V%nA=54h_0I_6;anW$2L@~ zHHOICyb#uVW?z9**qQs5W+a%|wXtWi>nmOD-s(TLc+=B{n_AeQr^&5L70w(ZQ=Cz_uEpjD#NL4j&rRryYmtZazU6!?VNn(PM0`ych{QAwF#HC0VAJ}gD@j+ z|ImZ-Pj~(flxpzke`4`?M4hG4_?F+xch}?|jb!}50t?W-+misF=zr`!*~1aVnR4nl z*ofMajUDn>7CbYH^SJY6@%pw2&sr3>$b*GndYh3CxRr3_v_>GKrnkC$8L|*0Vf{*i zsydD$$sr?tBi(~>NAPi+yodPiwQwL&z7tPz-$gRoe`}0 z?_(R0e)T?LFbr`L#e4R2(Bl5C(<^$ECOZUW=J{KyUUS{0j!OHyG6q%>A1Uo&#H}|#VJCkgn2JCFxEgF#5N_( z^)(`%w@49I$|wsBlll@)NYzHcNnHdE3BOJ7WHm~_^m|CZcz7gN>&)4kSTJa?X>Lkgl>`6o$HMO3^{H-KXpj2p%IV-byC9m@FZ-UDZX%-* zc8BJ649T_zkK7SEtCh3VevrxX2d52=Z5I-GnMe~Nz{9&5w`?Gd$%9AFU5>F|#2)?n z@t|)Z%O_Qaa|TEG0QV;o$!FJnuNCA5%?4}1i@jh&_%J70UXQxU4>}H`r{VhUjREq! z&6#lciLcfl{ayvRLQm%vqRUqltF(1Y+aruVVSSrK69n;@BGn`p*!?JpJ^R6qb?d(7{+0I9W>H^ROI)gyoPOA_vgv2D8bMCs*%#50H(it=bX#?T$2}> z@fhu8?;Lu9p%-Zl*3Dr;@ob3zHFZ({f7yVX$5MW5V7iY5>iD=iLo$q33URp7ZSE>ioy?p~O5F;Y8G?4f#>9Rdy&ImOzP9 zAYa9)W#}9>!7sqT(-|#nL?mi|*oHT=OCe>H4k?PQC&equ6!?i5*YKw=aYk*F#2J6l zEcz1r$MF*X)p5Ygy!9Ilw3<|2gQ*@{FnlWvGTAJNxQ)Ilt}NpmaYfj`tRrnOIc0y3 zn@zp5YDP-aGcQEE;wNWjH^RPtzNgW%wUVr+D46_krqISaGRP9kCbUy*&j3ihE(%c0 zRDD`7m!i<1#J!3Zm+`&sh5PsPCW!sXafRsJL+c*9#fi~J_F2|;Z>HaylI zJ2%Su_Z`oqagKhIq+ zgx!2i9I7v8Lt;^7#HMyKU^N#gY3(%mVoxZGvuuy!iRn5%;H(@gl7L#>CD;|5wczzDiA?{Gu1plP{jtAbH*pmzv5NzRS$mXZ( z@>VB0RzSoacn_eYSntCy2YZ z#&PznZx-CLng{LM1}U#c$~}6~Mf`99m0YXX-RX65GND%x$%nJy?0LAfPjAxJ1Y6ix zcYJTvQ1)1wZ!@sdTjGU@w1Y?-#D}-20@@76Vm|af6_l=_@y=LRsi$1uo~iws)_;Do zY_PXz*4pTwI8S>Y5ciGW3n$8HI{m-gjT@Gb{^onGoc??p`FIJ^UTJNoBIOoC=(X`_C z2i;=#&EjrO+gu0p6ISV-a6*I>q(_zz_a)??y&NOfwB*T^ty(Y|ne%kpPrh^fsKtB0 z&djx4HO66%n&YX@(ODDSp)=)xwE$%uz+S+_VOdW z<-hiE7*uU1dZ}^k-P1$Bw^KRvL2otiX{%8v$=<$nK0xVyU~c@PueW(UqORQGqIBQGJd7xPuG zf+;?_Fjs*v577Ia{=W6TK6BrkVFy+tMXE&V8H=KWKJW^vB1VzZ5%mNal7dO6)q%(c zQ^nzxot?jug1MsI-z(!GbzV5R!K94Rmc40%bX;j@4Ja5(@?ESoHD`ASR7!`LsiIJg zp{MHWYEz!E&$+-_<)kKytcI-S2cQY{@p4mP$dY_YZ+EezyP(TPc6io$T-~yABMo4r zQc4H`c734DmSE9StsphZU+Vb-Rb{#_sa9=@ST$Y>j_0-8W-QsRY{2iFN_i!`;=bf$ zQSM6+@7$i@o&ID&&ai&LVyyAH#hAps%h z52?2^;EHRaciDeAf4F6x^cJ5=WZ87ZWr2yAYCnj7_m<>zdq`kuQI#AOyYyB(A^y3p?fe^S{}}&{;LVgVGmp9diu$G ztq=R3=>s4aHAXz7593JiIgc5g!xE`xo<0-pfv}oQE&cVS(>$$Ti1V7Daco5x!t=Pn z8?9-P0|SHNSPZ=e5uWGZ{YDaco>%_FAlE7WMz62d2k%dUpD{oTGV9M%oKH>LnjmMT z+!<^9Yq!962)vC$0d7P74Gu(Flas5DP)Uw3=_bk^zBgTpNP9RHBI;NO@UpUj=9Vq)hDY)g6jx$3IA`*Y#ODOr)&=y1kr@f7MT7>>P(a zmQ&-Cp6K?$5$sN7I}^MX2OjW2CZg2KZ=O97YcNM63_x_^L{Cf7d`m$-Kde2748=lK zX@p_}Fx970Nv54t?&gDv7CE;vnlEscY1jUsRTEY(^V%!4Dz?&to?E{qKx`5SkqIjR zc?gGgd?}Grs@KjpO>$fa&+^WT=umgbF9v$YRu;xF1TTOlwYk5|_REm%olGOSVY(u(ebcc%zl4h`e{D_JbBWsEDweBUHGn_0U{wt0A2dQzn3>MfIg`@&qb-09|yTxdAKCjuPXVC0t zdyO=^Uz_RseX#BeJYeUZ;JO|`;1o*Hp5F~^ZS2+uss9)dQG8$a9`C%!=a{v zD{2{P4~E#iGS59v8hC}d`Mt19?p**vSzla2A@cRg2lDyU`{Ztc_@cmlz8GN8=kXE% zgU(f8_TbU?i&TW2;v%t5x^^(=@`Z63x z!{7GD(T>ljTE(iBZ00?>03+SI{ulbv4?O=mn|e(G$egaQ?j!~wtx=a4`Y-AXEjU5K zG7Khoh#d+A8F{wxhnxoQnes@KH1A5ZQ;_4`WVJJ<`Uwl)_>>1=oFMij2(>D0GM6Y_ z=9}t0(>;;EOE5V70nZ%%J9y^HI|u-4M)i4Qb?x5ZU=sGoFVJDvA?vYckQE}&1s*80 z4-5bWmEC9f^x8cCl zQc4_lJrhDk0pD+~MhxPANk1&21B@|S0inPFp^oI%ihOMe zihP|t!8{ZXyf);@>-<0VzB?$YZ&??HA)^dPQqqt?qM!sxGXzPJ5l}J;ijs4NAqa|+ z1tg3U-=e6wr#=! z`1115TliE#0ojt=xEkA+i^rHD$r#um0`R1VoQxdm{KG?%BH%Kxm%(6-Pz+DftB;0t zMR@`o-ek1Lw9j_xIf`C?!vZ00%Y)9?VzECC&rLKx4lkEA&&eP{1`EIk4g0aMe1U zo<{u(0BSbtTd*`RzrAf_?!!X1^!lW?p2leI$yJBss?DP}G6mf$wD{TKe%9jJAD zeBk7S3Gs{NGWgU-fd1LupZtvM&530hbu?Q17udKA0H`zvzI%Zs4HlCz1=^HR3oZx; z`0Zr0D~xp5YrNL7sO24_Uvyp$m)x{B+X>Ytz*>LYz5&cskp}VRRxaMrIT-kc?rRzn z9?kRk$VKRm#mJ&U@=U7HI7nWgkZLmlwWcg;8H#2IraPWYmH<2oK(TCq$93Dy zB!GpL-FF z@c_;+92QC10^Ixk4%6pGUg2ehy!~t#mHy8B9k34Hm4Qp2hu_Koa?1DTW$Dr7hYUvL zgHKG+Fx<#)ra=|bLi~I8?&0;fe3dkbzzH-0eEQGtRT z%gRCp1T^*VwQj|LY=!j*Y#g*&+BF%MhTlad)oSBck(q=nxUY#ZhZCKN`!Ee<;n8%Q zZArESfTH7ulmia}sFo`Ke4#Jhent`g+;@HfNf-f8ERhM-%4IeqJR>h26l&ha8;rq$ zzocS7Q8j{tF}Se!qXV@svfxB>=-p_0!O2P97OUfpec4s>{Y^L$hq(yg93@Zl1geoZ zY2cW)eTsyK8;qf>z%Cf{PMIxG!VWnCOhmC+yW+K(EDQ%d28uW#ZHq5g1yKfC<0%GL z6_lVM{!y(xd$rX_S@au8PT=kZz|Nu>LAUj9oMHS$t?XN5+d) z&{JFs_pd`-$eVd43=-p@4Hn;d{gHB;SbY`{Rr}k{gmSXm;;xLqzxP6aWo(}l#(2I% zp~DYwP#mJVN6nZ1;t+*5SflKl-)JpECO7J`pC1KqyKMUu@u>mD;Q$V?UmyAFXtvD# zEYyn|IRKD+#}Z)Z{`demF*G*0^KUo&kH=sjeCAYafV5=H@NfA;|NJ!4$#pw3#!3Iq z7z2v>`{x((R|mb&zcD=id{7?x|LJ7^FVM;UH`IY3(KPkSn*Seq0O&+y=$Reo_`{9^ z-p=nIGE7t^)6-c1wHLl20df-;u9YAe4!?f@)&1pT;HM|{%c9)(9+Z=j?j&S9^}7}V z-!+oYlKyNcQr{WP0GXXR1G)dU$B>&1A#+A_IehUip6##Ai2foEIU6#1`~G)B{z(e@ z&)J1P<89RS$u_(*gVR$=&qtJwEOGVIaJS}>lc3K1OivtD#=}&S497m9P+$lJhSG;LK9X+(JO_Fc2zc)u08Az$2mvs8Uu10 zDQlnQPH@UCw;4~KeS3fN9}z-}z-`eSOlJJ;Kccjjp^1qR$nL5**|Uzhm%phR2G}yf zScDsYkb?_-TI7m;ES^W^99V#3@FzpIC^5jT8%e(^eK@M9%9$!0ZcZ87iFOZ=Ck;O6 z26IEnK~3c*K)Pbdzw@2570sRHM;nHp+}R}7A!;FubXrmk8u=)~HuGQllg^BIcq>=I ze25>^9H1^qBncI`OTj~(VN-7(s#xSMFGCh*DEGZRsR@o_hxLRFddH&Zs+DQ07a6`l7qvL_Z z=v+xo8ly9PQU6n4TF3IGMQhzLkBQS;vCRa2sftE2?CtQoZGl7j`qPLFD-*0El3#4; zBLr7vC2Ap*&B@`tl+BtG)EEb^P!jQj0L`MwN(WHRpwz%03uqn7X=U|*&*qj8C#=}GeIM4 zVpvKskb37!ib@d#IEQ<7X2QaW@m+rD8!GgNspaiOEli`QMce&b#ELo_}b!M*=sv_g8l=Ef&;wsyNmBBQ78f z2|)Vcy)E5>Sm?Q-bmWz-DpYL*iKlV7bdT2>fnf^kTA8zmyg`K*==5!GU~9#0iY@SZ zhu5z;AUUi*MS{HN^mJzY-8*oKzsJjiXu!k5egc|=NOPsCK|;^({!)PS>wp5}cwD>iy zZ;3SDelVSi^)CfV#|Oq~Vb3u2g$R8{P@{=ps57EuNtOjt;qM`db?6%N!aO$WH&clP zY~=0<#!or;Xa!gNYeO57fe@mpVa>h1GSM$tW1~BNIWY2ez<<>^0(~kvxPv5pQ*`6P zX>ww+Um@29gkBrcK5l{smaniuE_dK!A2E9pWn!#8leF$$F*qN)W3}6@Fn6Cj8i3vl zM_O`3WzJ#Gy~&}STReDW6&B_xXyW9vb^A|0Pv=a48gT0rC}{ zjj8cPIyLMpmqpol4>bAY?vTir!qz555i|Ihon57VwqL9wFzSe)FfudtjGK02N&JTV z0s`9>EynrvtK29as~Q|PKjpx(uHzibGIqmUi{r-WbZe%wHkL>wV%Lp$+qF3Ezv?E+KE=66+biJNzpziKoGiHk`|>@O*;>Z;q6 z+LXtNT^ED8=ihn3U3;KUevAB**k%CG6*^Xvv3%4iN6nF1 z1*y6(Q!{|HOHbF)F*J$4@<_lFGQ1@VjIY61R9jQr_UTF&hv%MT1xhhf9{aG-vJDt` z$*Og)Ul>lU${1ZID@nQw;m4_EZ^sMOKQGepT_e68 z{)&VE9AO{~{IOFk)3T|PP%*}ax(v3ng9njHsyO1D=)TR3lBE% zN0>$L4e?lVI$e!o6BlH!GsRL&H{}(JZ{0wt0@Lj>&%o)KWxVSm*nUtN79J@?>PLf( z!gHBO101kS6G4kkrJ}EvQ51?=zN$aP%S~YvzSd0%IClR;4SXa1b3SSPQF8<=&Bp6xMZNMM^z#{ zVPfDmkjzN5wWLHc7cF|T(=5MLD9rNevK#qqdg(TMZ7=jiM1@Ie55JR5#sQKy+AoD9 zl_u1IW@r-JWZX0)bdC)t#m#iwy$N|@UUfioci44$XWKLt0#2Zo39`k7bmnRF5J$NiL0OOcrlku&!$&q zeQjvNi#~thSy`!dpT@%+_=IE|<|x3tnt3^A6zy|5n#e2eY+N`n_i6>;vkT5}Qi3{WWnZd9#6G^|jy`MiAxa3?$VRB68ZBEQQR=;h9PwganoNdsd$A2fCo zrb7FVGm9347KJRs_umlL?0-IlXSUCJ;?h^tC)y3SE-cR|Ufkc>bl;5#qQ5*b;M^rR z{QA1~tgBn&qv8ka8d2P@R-;seZ&cBIy?seAufX$rOk|T%Y^zOuSB!#94GTa5A-auo z(4{Wt98)`7+C5-31LSvCb$a)&I5nRetxo2q9WH+Xl%`W3^Inz4tey%LQ9Lh_9p&u& z@^#|*$BVqSan63Geakf^jztf~KMrKNX8B@&X}gtMTTxA-bOMN_Wj-r0ees*rv5GDI z?a~KNI!-ou-IR{|1#QN@C$kzI6^D2zgit7(4SY7>hxhHe%s>S6eeYJT%%yN=@GH)H z@Ahr2)}$BpV#k;up{(r{E? zJY3X9%*!vb)On;%_R$%@@s z$ysFp$hI$LIJMh^+Tloo1UFn*8v%`Rk87jNUU}E-C?ZuLtY&DCBC8bBHt=SgOKo?vF_BFBEhv+% z&HK<<>pXtn@%>}>?L!0T6zk1MT%7TX{R)%ZSOpPp7Ti6zS}8Y%D2)~^JO;L^Z^q|( zNHpp@5mRq8NEcg;tdIkp%mBbjG}g|zj?wFlhw)!Xox=8=`Yn$|Kb=mfF$^Dimd;Bj z3G{oB;hD0;dhestc)3Z`uYkMJ8&I__mg4was=eu?gP|y8DBpPkxS$4G;OynWo=c=w z?~Fy+KEk%2Ie5%JoUmf1V~3bJX%E(ph}f%#<-4z?6530gB#WwA*Drbx7256k&V#Pu z{TR*F8x(ugf5L=ib1|LXKIuDn(^yVI@TP~oOBh3)Sv!Te9K}eQ_LT;#;nWe_g&?nV zJ1m)}daE>~_P%qO@38&W3sfkGsya*0i7o?yGyhsYd>^M4+1b)$cZ@^%l2CD!$B1h~ z&GE`%pY**oQoXq(hblTE;|7Y$p09CIch7lTeSps|l*A1&wynI4f7NyHJb7lFxN@?) z`OP|MZ{7GA_7CqSU#k{^nlw!%g^k?iW$kxfW-YjCvN5o}RJ$Wp`Wa>n~kBS2cBGd9@+>uFq?mG0CH% zLQtwgh}pV|*a;OacgJ|^N4qYIBElzDyn99cW`0%ma9in$jRgciQIoeBp~t>WGc>T+ zTjJ4@`^LNCba1kWErr7|N4MVVcxcW@W4E0U%=m;B`Ybv3sM%3s%lM+}=q-lTtdLl) zsP@CraMkHymz48wh{vZ#IhXB=l4{N0>N_nfKm1a|@Z*bURI|sn?Ws)@Bjn>9Vl4vB z%f@DbYM7>afQj8oQ_H1V%8{uJisnsKtC!rg^jcL2MP`|oC|^fs%ljP9P(X>3{Z zTv8Ry$|}FE2HbAUOq%$A7`wYxPoi|AvAFB}*Ri?R$~GySbR%SV-{Dup+mJWeVz+_Z zz`Pwiq>dcrYo?&$2w5Mi$jxW1%#cAW&o58=4;pr)CnO#;CDbh^t{oFgzK~0%%2559 zd+6rp44ptM93{stEXzw;SI=Lce>IjG?~AKlbL+#0TTC@ij%_dqLojMAq$}KDJ|X~) zOqoJFxak8sjnS!J^O|Ry&|n)6L{PKkvFX6DpPE{#%>)}8%6jwZ1cU>Aw&Bdp83m*=e_`1*TQ^uR#D-hRfpCTTeqcuA0 zy1rfJ`NSOe5olWa_T26d&D0hXLa^cFLoENEPQ`ogsg*Q^XnYPTDgDN79GF@BJ%U332B1ZHp|2w+fQ3j_(A^ z(T9ucm3JRT;PzAM?vzxT;^W}CMu|@&g0=-EKGp9%1A5JAHb>k~bI+~YN%7H2v4Jq_ zk2)TT!xytgjQr-LDj`4o_yY)okUeURu%8c{@U~M(t_tcW4S`wh7DVILZzCIK1*gETm#9}34A8pa+7p= z5)1RaZnE}xJeiuOYHK0YBichO*^73-1gJ2@?H-r$@N{p}=6HwZ7=)$wO^Lx&E5B5) z#aA}8r~-ccS?t)yzHf%o<5pm5;7JJo^He{72&+xaG953i;CJA1BPWQ{J*_KGZ8u`! zR_?-K@Uf^Jwx9NbA#ub|I;M{8NR3;`$#K;yxG8X$j6X-3$fD3U}0@2#6|Mk z55GkA9TR}}ajs7@LZj3Io_giQbeIglb%cpu3PLzb=tn)6TOZkfEn-tTFz&1pTr9t?E=SfPI2Rd=pF}&t;v6(z8SY`nP%qTV4ftiQ!{qfs{+WE{7rR z&M$7)pI-bZ%LYsqfk9k-Y*TS7p_Y+@&VAjSXn+{A=LX-zjkOQO8JAo~H1%-KUE~=* zUW?+Pr2oOhL{FVR5{kP^$h*wu+8}3(4HD~}Qa-x0{d~gyaAK-!+Qcjn!UKc2yR^A_ zQ4#+T=dElzv@&MPA5Uvj#r;Tfm*-jed&aLz)x{l%>LyFWDYsG>~M(CfWN$+PzzPC{dowdb1j`hw^xr^dTa~;RZBIkm^(`Ko+?n*?osJ==DxO9 zk_E4@dV1fextC4XazY3(zOVa^@i{k4ioNF5I7XMoqY0L(QyQ*w5%E30-;%0lCm)orc=fTbnUol|pY9@}{FI5#twBq{C~WT2v(4`w zrItKl;;sevX#r+r#p%Q_bVBIqc%isBZoSb_s4^ZKByI}ACULUFiAa6o@ugnz0iTLb zitC=Ys;<;{^QkUgI4i91@!(So&WsUUY?a88igJp z#BgSmR7yn;)Po-%jB`J)4*XORS~zYT=q?b7%dv+E3%)VkfOUzk7t$!cwdmj9-187) ztXS5e_J04W&kqZ5ze(+F>6d|xy@SCNuKs2>m&OyW!|P|Hq{JZK;iKn-^m}ypQfS^& z;PF(;JR!V~`w?5mmCtNq^1O|Q?F#lop2HW%HdlkByhpGkcCE@=*M|45Bu`vff0yIV zQzG&h6Eb@tXhXo5__5VGb|dC&6w{!eKZiiKjULoj*f!!)aP{VFE=NeH0&mz(exOJS zeGK1Rv!`>wK=0AX+@}@qLLsMS{l(mZrPr?u>LhLv8$*g-HaH#>=jIc;hlB!~qe}L? zw!;AlU&XSA^4_ChkyZ0#ejGf?o%yUy@-36y_m6PC#dL8|xdpwkVB!mrgV8>h1lJ$4 zL}J!SSj2_NZ`@7t$a4uZu5Wb-@$_n>T3w#2Zd+}dVoGs}aG-Y8f?GnRoeW3a(ZO}g5yvfqf-NA%qsXfu4UV!B7^Bqi!Qr5 z)hP_LD<+WoXBh~qjnFB`#j-i8u5wb$(4k|pSG+5dtRFR@u$M)>ALu4dGd=GRRqXR< zimy^@Qa&CRl5e&`qyiA$Xgu*=rJ(T|?(=b2y6d>#uS>x}n0dTB6>G(wyDHNqUDyoE zOV*TbQcR%BN?z3;>(Ap#xi-e)I}8f0-WH0mC7 z^RDwSaktC(p>a$3&dyaLA|Ae$U}j@$?%6@cK~TrBzs*2sej&Y6&7+n=5N$n!LAEg= zc#^I-qKj_2hR=HU%R{f9wE+AY2K?w_b}&BBmd3dc+wAzf6loVoD#r#1M}MTgCDc#_ zvgv`W&0es9eZVGC8PlS;%G&UKqE)>Mw{}vXdCj2S(7Z7XieJJzc!6ne3GTSU{STc zqZf;xW=k*Dl>l6%YY8BuN4!YsKw%>V zoE#6QY>bd|y4KJJNyrgc)*~fM&@0=0Z&VJ^!25oymH1~7*OZVurQCLOD@p7NKsJ1> zERT#E@9EBW=9wWBH7V!0RKFxVciWNFTQwT7H2~;M}$B$Mdx><@Vb?4$J_k zW9~u%2wh?Fbx@n=dg&(N%`ksH<;MBRG5Vm|zkN z5?CXC!PZ;XxDYHBLA1wsF?wjOxn@`VPT7~_3dY_d=Yb@dYCS zr^y7;H;5 zK#LXR(t8=JN8*V2$F3>yku({(eXMxrdmEo8WxSQ-PnjjTZai}k8gXytZZ9Kf&LPvjuqJR-bKi*P-?7fDqS&_oQXUU|;QA9bPgCJ=p+#@p`C+z#w4zUeA| z)xykNEPJh7+q3Wc<4duuK&{5(^>SnIov2bEpX0YcGJhIj3qn|#+&i4QvMeyZdk&?JzzXnNRl`CscM9(4fHNj5t zzQ>QD!|=M4ng~P15fKJDb3P${twP#6P0>$9H);Le7%|l&LZcVO=tciLKAcl604A$1&;Kz)D*{Kl&lPgqKpDcK+Ou z393v*{Z}!|wSt2Hl44+k2tl<`gKF1ITNpS-QYIK>HN7H*z15(>OdVvrPtiq98$U}| zodKFvN|v5b;Q4aKBfRq(L7y)y_JXIMGY_(6N2HhckO`V$7#3f#A!SH(%awTGWh^4g zl^Z;9y|XxSUU|fGXb<#bv?Re;K{lXrlS00J(Yk+RB}hE=ofl-#rq-ZY&-!A-r{nW` zOM)*Q{O0<exiw4ZZ$N?@f0!rV;1?6P@sfKwwjdT35nD#g5br zo~;K$IF$~UjZ7HTv)$RGpnP-hBcD?`it7zuR^2uqD-$u##2?h=vX#OKy89J<4tm44 zpD@>pJGFQxtj0V#OP;ht{ zdsaA=%n0VL*%1%t8*;M_8)yUw&b95^qlGt)xAPFwS+tN=1zMN|C%eZ@NsMSH)AnVO z-_v{|D1)*?rQ00r0L#PgM|9p@^d-mo&r6Oq4XbN9SJqlZw@Z*}-e;0Zf=4tY??pA( zkM&hWnQ!j$(nxas6CrNbMMJfQgo|SO(11H*ZJm$mEj~u7d}^;w_#Aroj-Q)!s8HzS zSoUCY(sPc=+HV(QG?p^NJ_jL957(o+%vtaq&7}iOFSKEchW(KWt)aQ=-P*FL3N&N;=yHs z5g_B(1YvhT|^r?v8ooO3d<1UgY;5esW7>%+o6I{=VNh1DG8w+*TU9`B9?YsR%f* z_WesFg#xOLpa#qX?c80VY{dnKBzO~XJ;7Lo7#|w8t*&K!(hbpf*(Tqpj|%NkR!E0I z9*IAzFbtnj6h>fp8%&I2V|qWB2t#SVFS(h!qo@w0&Bs2qxu=dsrMvqNdgShe+0L^= z0+=*6b{-*M&qt8gNUha->lUaI-ib_5$W=AHEjWaJHpf8m6&R#;#sBz$*!~EXS2W4I z@wnIoRP`zap*+1sA+1R5+J?+U$@S-;l3~N5^@&??B~p1;jbn!Va`(&0S(XmQ1aAk1KcYs0TC4F?;;w!z9O=W&01vbllx4nlzUY3h>(Ab3sO+ zywFxCl_cXuSO9f&?M7pdhTOR&l8L*RbregG=P=`qAqPXN)zjIEW7;P$3^s4g7q=@& zoH`O6CAo?eWooOO9B=94K5#ykvZ8ze@Tzox!XO^~&1J`rzfn{gH-R3-$or?9_&EeD zDn%qqMjkoS`%S6qUI--kuu`q08uI>%z&5kib(fS{8fx$6Oq;-k0`Emr(Lti}?#64! zJW-me5^$ZKIwNB_KuT6Aw5s=v!fru*%|29XvOZWT)V^NO%(Abo&Ov}^P!(S~wVL#l zrm_Em$-kHH`{H(CRO8BUGbmpZ-WcQM6WWK1Sf<1P9lV^|C8&HDFnXVHsLY2l&0EO& z;@3={b8QN@&<6}>0Q_UvAv4M8D&4FP|>jJpRuKT#@A<@aIFc3Be^zq?@~iUmx> zo3}>N>9o()@~)=X<)b!*B z=b8j*K!R#2Jy7eH;dlrP)^;FM-{SJt%I;$sMaAmM2oMJC^xSI1cpbK|o39r5zAI3? zYT?n={9N^T!{I!yFvBd>-6slQm%6&K+bw`Yw=6Qoz7iw)7*9L05X*ZY?9GgN=foJE zoO8VG2=d0mu1k{KQTUs9n~i}bZbBN4zipjcGdET#)-A1;IwL4u>$X&tEklHtxHh7J z;?BSu3p&bd3!vNji{`=ma<1RaUIDi>y&numLH?(NeWY)HNVBGn8}AzmC^u5zS)Pi> z`wUE&Y>X01zRWwIZUljGE|`+zg^wGa-%42ZR*NwpjyJ7c9e(#vv5WAtrZx@z1Kb>8 z$g-Ej@Rl&{N|x^Vjp|jO^Hp8nb-56LY3}V7AVy!>m$;07ae}Lm9iD+KGPPbwl5#*W z@`@N6oYQ--!v(vIL$F$+*cTB+T@q=y5yEnCrNPMx+9gbL=FbGnLdR4~%5IAW`;%E0 zHF8X4k?heK=(X@hXrm!xM;W6phA*yKFeeiIe77&bN31Yz;`2zUEPncXv9*G_eZzw0 zP>1&=RkYu5y$?I7x=k)O-G5kFW7e+o<^@)Kdn+4FZW7LSs>EXhC#wgNxWb{3Rt&(k zGp;u{dU)XChbiufv(!`MuRpT70nKs)Sni+kOLmcrRN``5ZP&4(tXA1JRV>#jlYJmT zU{SIRQwDm7fiOd*8r-ZlM);=&#*8(r?M9T zvV59YUYFYTD3^!J170>-pVB!6`WW6D%)#X_ZKBG_o_q4SY}MKG9FZCJh5onPNiT$b$Kz?g9R}{dWl*jFN|K$Z zjXYUcEFu7BzqZ2O9E^H6Ke8*?KX75Uo|C8civ#nM(kG;V9%5-W@pZO>@o04w9S8E0 z{LDN=S4PJcQ{?Xwf`cW+zr)|%k#?|Lka%t;bb8>CfAvwn3?UX(;mG?inDObX8tcMF zzsG8OVkP0$@k|^~Ps@*M{)(J<*w|Q*Pzi5NdDse8qJlqeuBybL zVyNh>d~LheL|02oxLog%UJWKUdun1t*R%Eh0^-f-@c>fm(Dus?F1^=f;HvBL z10b2<`Y253i*96Ls&Adcv9gP+`O!KBp8k6dEdxQT1D(&SLu@TqsT|^LYS)KBFVnd8 z+A6*;U?!_gW!0yO>7V9A8E$>VT@ql3?2}>aBqo{uI=QPDGOMh-&QKp)pvy6EzC!u7 zu_ck4*tgHeyUXm-tE0;kB%G8DjEq`qdXygV@a&B%qb+zLE60qHi{x)^dKkSrsGXBNb$nyq^9Q^tG)4H$P}nW&qLzXx0k!`+6vtt^E@{Ha;>i!cG^!}5QQbW zOnG&%$$tSTWwG(G#>W5up3BjIl^fln1y(GjS8;^{F&*P2`_ltAQVHGz7_VIeP-l}I-c~b<}KutI^EIdxKlm(Ab#Wbf_IcHN>1lcA-A|pWELHL z{z%YiN*rT_>C79oTVbyB*nYw48D2nD2UI_py2JMhl+!Xry#G)+)LuzGm4aeSxIP81 zb8!ywO3Y>oS@)`$=aOGoyCD$i`~KFUvq#(TyTzLA6sFdot9;nJ1vs&v0z|t#ov({k zn5b1+?mzXEAX{>LGW^NkAXoej!65w0d+JVkES!GV@X6dOGC1Hk8H@L0S5q5CSyL3N-Za`2|*=%3)rRiO@DK@d)-q&MVbh?zX zj1OLJ?&lB=tJU7Qb#2B%yVq+ZaeURKc+062tN)_auH|d&eA$rLJv!*8wof+VZ*m@0 z$%V`+V!WiiH-ACF>r0C5Dxxo79Mhh8PWqDW{xz#^pf)0~+wMFtdIm(+76$-|SAL{+ zN2>>Nibrb7WEGSU3PLEQPk$0YmFXH=6}t!Y2?{mnOaNG7_}8t90S6t!c%^~v@Ri#p zaV{cxwCjRr0z}z6Drg)9+v5Sp3q5)>r}J7o?uLW&z}S5E)iQsu;#B~C2VgUCuO$On zPruajBwim;m?QxZg4E3nh%tld?10N?MFhr=Z#kGaY&Zw34eyijZ9{wQR-NImFhWh{ zKgQmo^AtcL`d|tyz7_%=oZ~EbQ$|T#yw4KUG}H@6nZvb_n8Ci8tv)JlNVXla4#-+l z5%@lml|aCULf9+Xvf)2;S0@C|PN(JJcNF>6aS)No1T_QrI7L!S;ktyi?*d~rb3I+l z1*p0rNSZ^uX=-$~CD7gli5Un{g7Qs6$)H;r znz)VFcUfQ|2-|>)6rjNYGtiKvqy1n@<8-i(^!Y;z`8&kX9}OD>vp9ienRhpPZ9|E4 z2ZQSdz%wWmQaYM5$%kk7Iav!r*Q!Ef7t!f*9wzp(u%MFM@Tcvz^VegS=1k+NtF zd?qb`z9X?lRb>u51376zxDxT{(oFfcOCll1E%XlVwa z$zJH*7(YoXJ6cFAa9;s*QT5l1tdTPsA%9*gj_j!f-5GoBfa3L%A|ba!iyV*#mMnV! z)(yXI&+__DF#*kJ0MJ)-Y=-V-S(JAbI`@}%_3uH&h_mifd*-*L2$6b0z>Zep2e_>R zOU|^zCS_0Z3`$tXudMt+odRFj-GOQeYaJ)et3G7Mt!|&v8G&v{$Y_+|w`epQsDXP) z`pLn58S?BbtwS zdVlrigyWkr;gh2XuPznp!0uPFig(_zx02#3uvqjxWlWdNDOF%mSFnhXS0r5KjAT?Y z%UsIj-{ISm?5Z4HOcGdqcF|>Qy{=$&-_w0gVs~uwn@9a_joa#K@}?+5LKd(~(^vtT z{Q-la#jN{F~a6x8tPm%Q&qx{puR#_MZ2;z$YhK9$1kH)YbJs0ZjW@#tg zWE5WXWNCn4f&IeIAsIeuQMe$)?N8}^%qz?}`L%fG1#9sf1^qh#?*3Mi}AyS!x>b|rjXG0g+6znN-fYA zXl`@L-SdM~r>v^q5mfQ~KjRR`OzKu^LNLni7KxhNj1$kTbt0q#Z$a^fb^sm4zgEcx zp&|xpQkfcGStEhNJV>>K#PJ;~5ESGDrIzi3tRrPsCGDO+r-gxuw?v0Yj$JB4fOT-_ zi7k%HMJd65!8jNb1fQjdm}@~UQx5}V`r?A24qP1njaH83bHd%KkU2jF;Qyjt1H&X$ zlWy$G?TIT&#dyiqod&^!;7OI~0A@%^d4&+{3JH*@rPR_%OVuAL)+pBE;4rL-BjYjs z1Y5=)=wy*P6BY(S7xHC%azucCI>o1~CG$D8*>aT=9##=Xl8ne(n6bBS)^~WgjLSP1 zZxeD{Ti-6$rAO9Yq8U`4(_<~59;;+S5cKW40=!Lq%Y>W-; zpzx=-a3T%>nuL6~)=?GO;1A77z@)_eV}Y2(FvakGz#+vXNN(6edsQeO9r(r*MWrr0 zH?cR;Kc)9m8MyHHK4KuC_o|WQV|%9&Fyojj97InN7Enw0kDyF}zO=~adeG0+!F+My z^1Xjg2qF!-4Ef=pM?eR@lAhR`gPi(yw>SX(@JqMG(Wm)i>BHB+YtU3&c*_kWs35x) zCG|Mqx+nxe{MQQD$)QC~oq-ai92DQ&N=_b>cF6n z!Fe!aF+7d+|5)7Y>K5}C8N%fL784{G8}l(06cS*Gq$vl)i$U-@_*nxP>@;c5%*xM2 ze+KjbMm~23_1rf~U}NAtiT`^-rt&aske9X6dw=NT=&0-OkW)9TVuMV;!+Se@L;U~H zcG6_jCc+yE{ui@?`PT}poQeE3@=WWU26ht3zl;zQ0)rxzw*#PX07i-z_)!SywZ30Q z1pV6|!+pu|p5ekT|HtA+ZE~jR_3XdIgaImoqD}wupQhjXZxs$E!32YVBSIPJr>R99 zBJD8%BKUjBiSV;wsa;kau8@!6_8M78N4ZxCIGDunBC9Ui+Wn#Ju*M4w{=UF#sF61h zKhG}$X(BEhi9gH%Cf-NdGm#@1vjOom2q?4Z-y>uPdOrW_;qzbEp+XR19V*}SAB+37 zOh(5205Q71#RP*=fT>J<0fvMD(gk_A0fX|j$B{k$_^*}*=yQ&I&I0wEy%EyHFhBfz zLct7}y~l)F^XX*d?z?Fc zjA&}W{lgq!AS#&9B7cr#ys1So0SD2|zc(2+3~h%5kdaC6^uM=5+DN;X^Ph_wa96gT zg*U(c>$m_!?uQ{wggh~k?N_Cg>F~hj7o!@(EB?jO+&DAtNJ962XWUak2#N9xV1r4w zDI_6l@~H8SBF{?s1@nd`_#XxvUloHGK}6rg}9uF zTpj7TKePvSNbuhyOvN~}L&%P{0rCId4mA(}#~hUWKNj~h(ibtwBpa@O9~Vd;0_D53 zI3ufMXSZS@8o2IiP>~A#t->FJ(Z)R!{oKDd?pn|=c&73qIhx25a3RA3X%ZAh zi7JHuI8~sN_!Dp#a7H{w0+ZCD08HCDlrNA0hXRZK4|9M4{vL`Jxu7CSl%X&P>6O1n zNCq@mN7|vzj{m_9=^~w*^zZ+%xYb{EcMC|-h5db8Kp})@9!EaPXuClnab`COq`&{G zr6EBZ_tO8#xc~Hb|3j}Mlka1L=>*mv=D-*BjRY<78%WM=b}I><6yv{)5Clcrp_l)Y z9pVJMXFsj~T-=IUcieBZOsW5MTws7r;b@Oz1X;M@HzyO{@c7Gt(@dfCVq1buZPbKL zOZEM;rCR8=PYwqnQN$S=+w7%!BjwxW-nCo@WNrgbvHD-~9~_zdVgOl2g@IumCfKPrf(m`h^m>{<l${D2lDpUO)Xde~x-eouH&b1e+TN_=HdiPqsm~8Y72)tSiSACRKw;3M_is0?E zTd7-}^lZCquMssh6O*jd9;F&q3<@RyTMawCFf2FZMb?bhA3Of!DN6C=cPoQ|7)3k8 z&h^`a@yIDxh5QrH42FdOg@D?tol9+&kKbJ!DQ^c7la{)5`XU_q72)*?tTz)GnUbk5 zeB7^v0I<|~h36SvRzmV$y>^PM;IhHbo)K|qqz50B3E#@EpFclTbXU&X==}blzb{6L z(k8luXVu8YifJp)VH5lp4q3f^gjf5WL#EwNT*RS3TV2Bbd8q{LIfe4}>q-$`PvyH3 z4&zO5>&=FfQ{Z`Eka)A#GvU+$y@HkZ?>UIQ*X;JUN(%hqu>UX_#$BZ7odZHUSxC2N zBJCsvbNzs`OJRsVWBI8|mP%A}if8`_)l7+Qktxt~^fpkykGrk^NRJ$tV|N|-%;&oD zmbC5jt&YXRArnnS1?`JdY-V>2{|*QJXf-~==9)?9}U3J5{h!;s9m?14F?VE1Z&%4R*rQhyMwJ-y_2KG z)te*L&hyZu>!qEgDp=Q~xPVyHU^SjBhQ~nhF%cak(czxiGWJ^V%XQ=7tu98L`?vEA z-@eHMxTOaI zrhB7QOP$K&%J&_-6WJC8EyHnw`lA;5-VAOh2wKII(elLlblR1cT8&D%>$~L`jMaGo za1xs>o%}mio|p^wHgIgQ{p@(3hd(868*!KHGGZQoNk;%sNMFp}PvT*@NsQ4s_HJam z@)GFl(lgC;uaOx0?d@7ii9el8#2y3M7+yo4w8$lBZT@u>UBUhfM}Q*L@r z$&M(&?kiTzJ|5d>rdK+?Z2x|LuEj~m$Ye?$k?Mbuz%KA2XVk7e z*df!IBL^eLfB74vQ9yEJfi@OMj+l-$i2L*-ZGpQt(kDa|v)?WbsM$Usta>m$=CbVU zZ}7Q2NadpICkth(7bB}RI_9TqnwbliacTg(s_5H^$LFYL8bb;oNe>H8Y0C$XGriY} z#11|Ryj*wy=}fm>B2-nFQ9K+TJ5L!uh8H0f&|g{BJk$Ng%%QrjQ#N+~GQl6J7@m5j zikXY3^X6BS8Nm@{7{M4wIAVADcG%6}gga5+dyfuh_(G~|u8R62@q;ho_Je6o{w74D zJd4bf?;e@$F1sm7-mdsDz_tAfAFRn*$0hretjMjq`=m`HOMi&J3>PsrH9P0hQk+Eo zhX(=se&*)%qjoWH=J7Ly()9SOVAGm4htj@u8I(=eWM1&E92bQSuefa0sjG-)JQ?k+ zT)fu3d9c!5@%4(J_0SIKP?eKj%)_r>zuj>+?m^zRx95O^qfGfuStWDu?LQqr(lE5c zq`fF6cpa7eplvTM_kgg6r4gTx{6L&r5VCQwr@+4usIz2mE&1sg9nY;?t+pcE#!;-p zd!%6ZQTy7&?k%uK@D6Rkv+LJPt|A&%m-#$C>sQu}i&u)v?)(*hB16Fr2R$GZ#CP5O z{yiEM`ja6;f6%Ejtw)DC|K3WSVDAu^XT|rQT$q9LTTq4Y0eRZfB0% z+mYWKIYq5uLkS?0a99+y8X>(n@GkAM`IqLJBvpm|(Q%q1ZmsO_2R6RLxU|Oi>2#}| zlK?KTHqYYs=es_eZ(XK`)OoQF7p304xi#IS{$Z&qqg{LO_WAh4L3x~=>dyQGkw>9h z)WU;2hxEhMspHE+BYgH0{vNoa49`QY9soD0i~W1+TQm;~+f@ne_$2SA*D!m%B#w_BT@;(XB5F3d zrz58{uff7SGIt~F#rg%-k&n0+873nb{Ff~8tE95%dO7gk{kR&$qDWTmlkcIuI-?LNL&MN0f0Tr8v%?FEHLEb}0U6RH2GY>CrZY(0bp`*U{FMyTVNU4wE=za$sTY6h_}hN5ujF`+ z&F)UbF(+O&MYTAd>hOVr@j^J#y(y@<4wwGRAT^vBq}jg=QV7Ec@$#+I(Sz+Y@%VjX zXYynB`jZ&O(-Z0$R$+3dVCnVmO=eAPw?4-h1TEO9_1`S1a2&E9Dz(_m=Pdj)mJWxU zMSp46iC_O8aI)jBH2wcjy7>QUC|zo<_D<0(N2k23H_uSHEry#r@I;4w*gEg0orR|0 zFYUPm<>{5fW8Ug=5K?l-+{*3Q*vCspJ`wJErd8U%Y1Pl{T4dG%hcdO2|AAA~oL>bg z+Iqr37vjo|BRqGS%JDt16Onli#M+tKkpG@1a<9<4YQuUsu9_Mc+ z9`Q87S}5etk~}b7(DP7HRtWwN1xO6*HUkDihV^H1B)p~EPBj_j;wQgE=0H=qdL2@i zu{)jA>k_sB7t|Q*m)kV>8eA#_UyPX>a)?{o>8;pdW(VSkXfAcBr{uhs56On)2*`QT znM61szA!S-0ax+e!}CKiMapGICAS2v#<(tCB=QLLYs+s$CIN@uJ=f1iT}v=0!zP@v z#z%sv_G)n#4MnaT;k z-N-f`wz@SpGIjLoYDi5UDvy2>4=n2DY&ovj7R$*=#68#=z;Y&>8pxUlNqT_++tgSb z8E=+Z*K4huNR2e9{nV_6Ps$1RHfOnuoffr+>byMklQ`M15UxyW6}BcEcS^IP+0}0< zzTmP@RZUUv&r^s6;5$tt^DAFB;i!}@#y_zOwEMN*ebE<_Yb39w?X!Mmx{Cf6d+!w$ zWz%g73nEbjM1m5!B?<_Lh#;{M6%+*|=O9UPk}O%tQ6xu|AR-_+(;!IBL2^(sG&wfV zaH<=5U%!3!#eZ)8GsYR43x^loPd`<)YOPgs%{2>Ztrf=cAQMK*vox!kHkp%X*!CW+ ztT0r;e(|eTCH-^VQYm=?YzB79g}oLk!tKK%OO#9ZktD#0jN0Wv3Q!a<&WJgQ-oqpS zhBh5hTH9ez(tpb&ze7R7sGjV;nqDTrzj9BmXPdUWfWE`INLhlbeZ}RraFT>K?`!7qIk|${A>>Z<~?=>M*H&s-97x> z9M!sOmzTbI!%t+wHliZwv0Yic0x8-?f8m;H?hj6y?aJVjsf3_$(;iv}W3&jF9Pa@d zbGpbgltlT6?$3&fU-qZ+g1M3?@>k;(-PFk^?DFz9$DuXLV@OTL;rpUeJXqzejRSH> z3>f%L#f@Zh;{_&*f}N((sX(wg^-tpSI|f#Tk5j{Qy&CU#X^yo~i$v|(Lr=XAmw0ZN zTOv-F>{~WI}HNge^ zgcsC`lcj9}gCSI+57wTr@O0!i?9&w`HOBe<5c*ju+jnK6(qM5@fU#i_1{A;%#x-^a zV+F6cPde4SYJ;z#(eryWE{Hr#)gWvQeO$glB#i$a`@)|G!O;)u_M_m$$JrQF7ATxy zsCDeDOu=B(GyMN#kVwhSKXsm%p#d`d%ZJA{yO?Ay{0MlncPI3AZ%O?ho%5>w1PWF- z-Y+M^guh~@;K6ALts(6eNl1{^pMuE+nR8yLwGnnO{DunvCS1FVss9|^KRwZ*VRnEv zU8kWr0~L8#fi>AR(1K#;WAGc5oM%-vn_(I9sWkKw?qCa}b8Evo((~ysw3`K>K?64y zj`w~wf8te~7wGSO3c?gC-Mh}#-06|fMIK#$@n+n!>J{uM0hF}i3U|wO(o!(6Dvf$f z!CgoG57POKoJR!Tp+FaCPNNe*j4%Jak!vkO>MsID5(cD}K+Vl6;j~bKuB%hOAT7v0 z1Jp?EK<$G^vK&Tre;YWjsP3_SE|pUOPMgWZXXz%+#hsjLmC)_`x>z^ zu9qsnT-FzUW1!l}KZR0E+4cXy^*N;ikU}IbHrDJ1Ck`Inbd?1<*w-a#`}(<>+L|IH z?}Y!PN;H-=p8E=!8P9^uE%u{N2X{n|Rqy>?uTOId_rN2maFrL&tie$@=xJe z!Scm`-kFB!*&B8;%+gWsFA!0-w6A%Dv#q(`NZ_~XRN!*(MH`UUKHBU~_WXI@z-|H@ z(@F6vx@xp}cnZ3c;X5#d#?a$T>$qvhC)s=v$uAN+c~{}bH{$%x)#>qDujgH&*WVIS z6c}%=I2NWMkt=rg%Ib~_5_?iW9rWYsNmn%I?Wx>D!84l@7q&=B^ z5|QmQo1;%o2TPqysh4TtyFLg@K`F(R=YL_ z_<5N*SGsN=sT(lb{-Zj^uJ&Ye4an$r&ZU`??QatCEUCha!KaZtu+AOo8Q~VX4}|Zn>Jv_!7z|W<}=2BK9sy!NhT6wsIt7 z<?CrA@<19yirk zBq1J;X6D|gxi5HC&IIPttsp~PExa{7`sfJkR(oWwfUW({Mk+G;UZF*fCQbDp{2M6z zwI2!Z_-pp(+DC_!li`n)h)(Vb`hPvDPb}Z;DCG^lwIgQY(o4`g_3a=cA$?Ry#PItA z+3K*dR1pqESinuY`HmQF_1eFQBchKD2VY_94nt97JA+{ZNmlP9;VBbM4 z!QZ{jmVM$dPIWVmOzgq zl%VzN`&+}M_x#`ru4}8BSM%S~cSd*gS;uM__t5)J}Qj4cfCx3 zcSYh9Ukw0$5qjVTPQUI*`}|i6W8>`6b?2Kv6ZxQ8H3qR@CfCY&jt!b?2Rw6uT!SQpm zzFih2cR*aGlI+5AU>#2Nh&V=>v#a>fay0t7lIc(4rTjl5LA%I)HoHUN;N5WmWvCfu+W3*bObTi zP7EY15y|r_Cf#W4cZ+dRxU8s)#}OzB>geJ&Hn1Sziu7T{7) z$Fq;1-SPa|lUlgjTSsqx=4!AOFaHQ5v)$(4*e}M%xzHudFA0q2M%JII{Y=L2H(6zD zOnLx6&Hpz?ePH7Ul{(H@NxgYBHVrvtRu9!mEvt`qH3EaFk={>VA8Lu1M_F5!C;q7K?S4|JGlQm9$ZT+x~WFB=| z)NcVcg^Z$&$EKhRB`R?#$HRXCw7?zeO^uVx?c1gfFS1Hk5^;w=YN9oCQF{OWZIFAh z1Q!6YtV^E}Mrkw!sk@FX(cUoo`ITblNxbl^k9o-Tr~&4rk(P}nI>ur%h;A1WC^1BL z)$FVHbG%S_7=qtr?(l`yxj`=@X8x{g)pPDDhWQl7T`urAtwCwQShi0zEqVZs54ISc zG2S}d%z3;va^hf*@E!|=P4zTl_#1FXw7tZSot4R(;q*9Km6|t6M662}#G;3z5VX5m zUQ;C#sF0S)TcFrv-LH7CK2h*Yvd3d#{Nu^AhwFEak6NfgjwPa7y4|SFB3@UC{1-oF z9`i;`;j_8xMFg5$C z7HK&VP8;brf*{0zR~t{z+c&8t2WCA!pwbLb*C8gsSv?e^Z0o!36nfc^H=m+ZbUdUj z%gXcUE-hFMmabaW+*N<_e%+UQe;EZ|3bKBl$?|yZRSc_n66=i_i^sx z=!rH5_Jgl?>mif*_dRRQ3Ctf|dbIAuvt+|>F>;f0RQs}0x0Cw}IU4VOkU<=MatB)? z9YE>=+=bL*rbez6pB%et&$CJGPkLWM$>Dk=OftKdBO9~Rt=4FK7C)hOjwdNNRN~AQ z7}(h0huPk6K?8%4LvqmRSf!m*=X*j~G;g%nn{Ix^g7W-MIyLV>%V$KrE-9yFxk8ci8sjv9d}4DA+6drga?%;_u+m-_H0j6N_6Gg z=rDyg|06=2IET*b`x}|gwx)&Aal75g*psg}+@nu6vZ1N^M?sY1CW>hku9~hvRif@M zGH83eRS9s2=G{4zp30u*TpqLvM=cv9hd@WlA80tDid9-g99Lc$(ZA~Q*&OXYZ;0E>|}<>WHMhP<4u>y*5@J~t1Z;#Aw=5H<_REM zY7STF<10FHQz5h@-SDmxCLu0?$Z{p`lW$V1oYyG`G5*=u_Va`0!Rd%P??xX0>X$j^% zc!>Hi(H|r$lclUxOe1D>+y~H{LA93c?fwdP(eY#rFX%h?&r#Ef25zl_v7^pol}m!w zCsTVmGYB$SF=V)=fy=FZ!=Mc(^&0cHZl6`WzKfBdI(~j`8Gd(gc-cdx#}zqxv}V3W z;E_&9!=aL{w)UB@QIfr&4BKnB%*iEouupzZq35@8pWXL~LPi>?{ErVUc66TQ4FX_7 zGGj!)4IhWz1rug;?w^GjC(@ZoQlIid_{CrpCC!i0Jy2^LX-XM(&FWjPMvf4ba3ti) z*=sh2^dIaIv;Ptti8v+&%TSbXu1Kh*s4*af#TXJ-tW zDMAEE%$y1qB#L#e(JIS|Cd^$WSoYatj?v;!5Q|;4igqz7n`wePy+so71L1?@2}u~n zTX3+@X!ZX^GK$*1t_(5agX`q3PAEO0)^Cu6OtSqn<9<8px>Hp7nd*Mw+{*20=CJlG zk~}}!5<8<$QeH6f*LP67<=cVFK9lkX2~#^N$y+4cD_pBA5RZemA@=MuB$IB8L%TD5 zpAd359zV#4FGcOKgb``y&OCw&%uJ1J?ijD(l59t^$VPGGR`kf4IE!pfL3@7TagJ)f zaFt|l0~s^x=dGhy8(WQ(N^e0y#MGxOD_pqAtD$fkA!t(Yapt0`x=xx)Qxkf zCX*8E_DxXFPI3jj%OQnlJUw9;m{J8(&%P0Kv?OkAo#ca{F{fh`+l@*(!C&MlLLYye zo!jxbZ>0bpLdzYc6b{nY8?*d#koPim$H(3>BA*#Nb&NXWIgOK+uVQ~8-I$meL_OOa z4_=#1q+Ib9uoYK&p_F5%v8e(5EMNZa$lw75T(TaZaltQb4Kg59_mVWyRPN8X`>gVb z;3W?s^_gsG+D) zXDdhK=JMJMHg9k(XH>`9C!}2sKK-UfaiwC-{-#Ey29wy1y4;6)&M=bdE*F}G11T@v zZqEr@6rXk%nTpf8-RJP_`ryUgA-j3baD^`OS*sC!4>Ek=?NhuYru(eo_B=8KP4cq8 z=-Ny!^?3=&Ogc07u`l}Mauo``cl_aYKlMJ6Ssp4StHXgeATl!fe%5NH{$Q>nA-xfc z7$Lg$l$Mp1Z=>JAjmUbxhqYyMmZPwLGKnIhX>8yoZe?rU?*_!x6#w{Jrag8_JP#(1 z=1UB~EkF_ad7|e(2Hq*}=x{&9Cv-3i7bXQ_EBypK-_Ao@_Xn2StygWEigc7y#CV^T zDnBf&5td%zJF(2$N3m8O^mWy|1Eu^@7RQ4U^`M%LRetMlJaY7A(=Q=-2p#E~6NmWm zwDI4|biV@5t=1$7Y2T0)zCDx+R4*;B?i{r_+>?hc*dp!YCK35f0q$ZzGG511K3x3&6b(}O(lUDym9>D6tPW>h-9DXlq9 z`A6udt$7}r%ZBEOCcW5NB1%@rqodeKf0AZR&PMurd&22n#V@jwV?O@vyYMo$8_NxJVukQ_hneWXP(%zK?5yACv=GJ|8esoml_Hl0ywV-Lqktnr`; zcVH_YNr=l=fMXtLuviSgPLTBQQ~L62htNNMA0AaMLNbn;5O-|mQ_(UqW0%~~Ul{t3 z!xORVi?a|OsxK?hXXz+bK#qTe=izvQ_?@U~sKQFQ3=IAYhQbTiIZY_hjO}Llb=)4y z_|QGEv0$@o^!ZV>82I`NT1z_4_Epcf9A%*kJ*eBxQ=)mym$`?p%M9|~R-?d`VPw`;?oMsiYlasIU$_3`OzAK&P z=LzpbWsi@KYQBooa{G97WPfSM`265ev)jP@)aEMx2p6hC)b^;VvHadopyOIxPygy< z9Mm%(Wttdi!YTRW$MR$(DiQ8852140SSJ=WyJg+URF&)z^ZDSiyy256glpi^72ku@ zydP8stMJSe=Xhiz9cP>mYyF$#G>ZJq904w;d7OOfF330$CHBy~zt`oq&C z{IbYAYJ&kZO;2kD5h)TEx3S9R?NuIU264al34u;!@43*H2D`32E8q3iu3M_whQ0(x ztlPUklBN;dFGq)O?C7+`4+QzVu1~(kc7AolM3mfosN*f*Qx_oj>mN4`57Auukhw8J zW7RS+==|iwGFdspRIuv4pW6PcmmhuC9i>n3Z|} z^$p^+xYlxEsVrW+@Ppbp6}yFNcrGaTm)L9_*bID$ znw^IT_dQrKc|LtEuM!Ry+02viaheD80;L)%*rX_bpiI_h!_Kg+nP9t-k<+;?!1I!^ zyK17iIOm^94WMG0Q_sA2%Ax*;)QaS7NL9|cwu#(klb?ut@#yNZSoq1&Rv4Y*+)4e_ zN6@m(K(0q>^l4cZCf(lxcCPK4&F0m;7qld~;4wLlqIm@MXSr0xXA-E{gYq30NitvN zlun{)r~Kd>5%a2AaMW%14=5pyfmd1|L%Xjq*4WzK9SgT@61F4-s>ds8I0vO`8sU^yWSF_GO~^xBWFef;JV_XA*zMaMcE z!fS66ea{0c>`oLk7r#Ks$u9Vy^QocABV@N8!9jwQw9Hp~_WMci0@-|RMN{DJfpr_N(NfE<5#bZg2$r`IB2R3Emc0{P zWgFZ_6`vbeabcY1DE!~zz^1W(-6xVs))=iN`>4KLqxs@0DI@n2iRi;v9qhVxs|@L+ z5xkM3(^Hz$_^fEs-Fpk)^ZIaq3R}-hI7dgeOa}ss5ItVE=&^Bhj{{Jv{o49oP40#;TO6)MfTNzh%uLsi zVgb{iq|k{e)*L}HIB)-QMHC$S_GGP4avcmbMnDd_^BQY?{18WuJ5tkZX}nBj)t5$! z1Z9s($F1O7H5&{ICJNQNn^O0-f)x;(fl=9MHhpty^tL-WM=PZ}?~z0vWGik{G2q^$ znfGrF*7I;ZBIdus6()Fj@%DX#KADz zbG~{)20wz&knc9nI&agP`NKc*q>d?4Fu6< zuJ~4^!X~KMKO*B;ZaIFSi%JG}Ij=Ws{Gp+L%Zz{V5rV$ULoTlq z-F6^HiR|%#ZvIlMKG#nURqyc|>`j**es*sMs%jRjcS)G3;#Wt06D>33**H>cJ1oPE zVMRz=&u+qTFUfXgV4@(G`qdgZN|Os!6Cz|Pkz~#xgrn5Ng?pC zL&xJV9cgei@QNwwm+csVF~FW~pPWYw0#d5<$3mL@*?uF=*`^fR6Nm44O*)twsy{a< zCiSdIf*a>@kUy8Xn*q>E%~YOhL50q7y;YkQmQ~$2M~LJhw3%*4<$MPgw!b%Q1IX~$ zl|}|FAqPVW%1rA?y8~%f&GoXac1D8twM6rfahnJQ61+a7mV=>eobarhM(NYI-p#fu zE1z6s#fksC_Pf0H@`bfeg#it7f@+vgEdwf>M+~JW4$*GmYwtH?QZPT)$+Y$j+m|vy4+gj0RMT%IY#MaqRz-) zvcfkKPKFsI`304VsJ`bu(Z1kKk|`5D9XiZr`}uQs@u+>B*g;QdrYP}o@&0I~W2GW} z)d`na5&J=Uj1FU;7*bd@Qv|QE`cCiBr;4qnu6Vvh`N|2NI?(E$aMqj*qk7(%`7!0wpK=(6t<*J=VqPl8p2a@}I?-li1nm@v~ zj6C?YzMSm+X0lD-T3-9k-)_Hud;!D?-*;#(=}6)A%#Y3o+m(`!uJ#zq0-S#B5~eqE z#u39QIO~3{di?8KlW#M?PVx3uBD-qd=jT;%jdm!y&R=7$-nqc(y-18oa$qK1aR2hP zD*-MS9lL5x9DL5E`MSOL>Q;r@ca6w&jW8N}o*QT=G>n*zwxJ@`YRn`4urSAU`?;hP z0L#5sxPz~<@=yNc_P75an@7W29WZDw32`%6x6ad_xRauD*K+IzUF9n(rLN+qukF4j zy4aF?*d-r7s$@|y?|wsb*Zj&#vTCOQfy!MgzSmol;~C$jA{Dx#-e$NmoEG9Qr=Sl7 zt^$ajI)^!?@rvv?tnIp2N7m=>agCVaH@No6c*^Y^#>Kw9 z)u(-UI(7qwy~{tB3xpA0_Ie|M$h?VwcZ&4M(#>;uRd>GE?B6hI2x{J(Pw{Bsoy$3x zu21f`a}f$o4Fltqt!|Ec#fu-cbW%nwztv;+KYb4FzRuG|WeW4WP7~(cd>FSeizi>X zi?uoWTk+m;njJ#4^5BL>NpO{=ugsl0@VsrT32yd_?a91@N1{s+0dbatsARV#GNG}L z1}&G}DO{^Pj*+EfFETtFj1$)#d;N|EwcV9vL>C)sHST9R9HT2zTXQu93o(-h6N%hk z&uvv9LmUD6a`dHcQd+P$(#vu89bu!Y(B@v`acBacYv@?78LVHAMcf-(6L@Z2Ms)A! z(a2w|F&@6upLwRb#N6nFVUm?z8l{$z!(vA_Lc+v;T6O{=jGrDVrDW|D4d>*WfHkSo z>?$79)m<-?Yl6o)SA&C;n#!=o_)mue{Dud3s)Ld)S6?P3*bY5^X7D30>f?zus{F+s zr2W%QZMR1=oR>w~6t_J4W1>}ARv4fE*g%c>>1m_^wCu=z+k5OvOFOyn^6=wyj1E;> ziU%rbnqUEr0<_p0Bl@1h5>i`G*%lEj%OQRo% zkK01Ves-9M(aq~*c6tFgXLy%cR_5?v^u7J`bQl@kZt7r4%*T2x#r6$9y_xh}cpjnT zfsjM~Rii1F8~cZd7C68k-Of4`MTTj~kwfTXn~PyGOm6%p3S@UGzH8#CPm6_6M4x=6 zJK|gIjh`zy9yF!Iv6))F8_6J=+$Lce#@pl3*NLRDDrX$x0qkB4*S#3$#_f4zL_K2P z6-WcUU%lhp9lH~9SAX7?U2xZtFTYX&;*$jV5=|6x*~$p9$q9>YwJE#*(0@8qM13iK z`2e-YyjlF~T>{c?dR<=Nl*rQzXe-{DuoqPL;37oenKEvpyT)e*Px&&8q=*NJpb#ol zn2+;^nG16#he4aIKF;{SUVu@ouachp9gqGZ{m5yCvJI6Z{6hkV9ihGdq-%YgI5ZkPNfm#B+-V=)=>Z6|c~$W9 zX*K(>nA_uczp1uQ6u?0I2xjn&-FGw{hWBehm&Fv89vVB@8fZ3KKsO5vpJFGmaF(~u z)fxzrv$1u8Z`sb(E8{b9peLh~0kb1RPaE$4|6}u?rA3J4OQ=SSNvMop&>AlT0+CLnD zzj}n*;kKAQ`~RJPg=T_1ua3XMr70Dir^*cO_LFsA*SwCi3-$W`mPk5eNvXrp!VlS* zGv;GlFi+BfnSz{CsPa@ogcLOF5{Lfjp!ABxaMWyP-O`=%Uu@3nm;rWW2T!%pI|*GY zR=JN42(pT-E+CEDC?887n&mYDu^K>%A;Rz*ThPRJY+vd(=H_S3tqqB265SuhWHTs< z%&fut^xek+nah!$7GWQ>B|Xe6KcGx&CUuoZ=}~1%Wrx z0*xmFDbhf|+GgSaG)|3!^KR|8dE5HhhtGhcMXc!|9J(N z{XeM0j$!!yCsS;LiFDxyxOPXLUHOJp9!&&vt~BL5;vrheg&VoXw^(2^ol(88R0vlD zZJ$cFK|eC$dOkBNh7_RQcj_5LD(s{0pKvhMO9uZ8KkNO1C6dW`={CWI3>rSiOwY9g zr52Ive4L7md>Nv_38VD^Ow?FAv~uU=_?Y{>dGzLJ{L#F*+ok@(k{G-XoQuFdLbuni zjb}jUMoZsq&bGETw5+uj%)0K;W;^Eld+zrGK!f+z6zBZ$wu(o=#vf~(Z`j)#12Th%cj z5kXe^E?>tV=O*LKwnpbSzO7V95hV5Wl!Orz4CZRU2fI{Bb3`yKC^IT&RkT>s=YZE} zbB_3;#4bHb6t7XU&Co2ttFQ8h!5U1k|J#$^==-RG;lNT6HN~ zAJK%4DT!@YvX@8WUm*sK?Q5uKM{iF_jWAsj(se(|g%8ukd$shn->(B+cy;(6wF|^4 znVFUJR)-$b0%b=+%W<(V3;X4pIyJPi-cqoef0}a%Jv|A}kJzOe?U?Sq^th?TrH~=R z;mK%+j~?2_i}y%)U3{`X3Dq^d{%+(36@vbLW^NCYt$5{-<;F8-`5#Ki&1GPT|iU{qMyT{!dKv zePRg_hy76XgtkCZYyQ0*iU|L2Fm7GqS6OrQl z2OGb84@;H{Y26QZ=?Y#g+N$lh<=tLBya#%bO3*ZHVJ{7<3gu4GEyd>(^TkWC*>Awj zaxCCiqNKSh@2VUYl4H(Yr?{Oh`9F$Bf!ZTsabn5tE^UG{$n-e9N%9eUqDq1leYg+Xu}+KNbK);!J0yJoOe13UjJylxyPu6gI)a% zE%jj;V_z`1KTI{*?3R%r#O#3&eoLUL?nCd|ZbI6Nx0`X7svO2$uD)<7XjmqVe#$ES z*Em6ZJraig`x->7XSJC|-oq2|4Unj=(|nBvfqm z<-HFZ`2e!>Dj0}`bn5H}Ro(lQRC_L8xnrq!@i$S9*Lc|=ivbw}Xv(`jsMFU$Z?(_+ z$#A;ySzM=hSAO3QiyW)kZ<)zKrgx|p#YuDUyjT<&>jS2BippcCr|(ldJhFX>s^~3h z*^q}HUzh)4@K`Nzv+`-Iz(aQw=Z{I|{cPV5DxH4yZN1X_NZX${HoJgizuF(>?RNAQ z-IYM3emCa)ZnjCtbt=?m!mw5RO|^=rFIr}Y^h4i~(?E<@Td+3QLOl$<%30Y1Z)$F5 zdzjWuF6Y*Kzx0Fp-T||kF@3+RiHmol$HcHa=;Dq$2A2ajCRpJeQnJabcAB|o&^nq+ zWT~eY9Bnkoqe~3EKu*GTR(yTG`*0@Q$E>ZPbTX>331Swi@pR&3T_2F?iqQp&ik&hx z0ZJlFQS5XjT&ezu2cDioPsIb#_|g`*#EDuA{$qIVS8?<#@1bAH%&yQ{aoe$^47OLN z^(}+-bN7bu&s(;j#qE=+_58Trb1FQVpx#RyH(af{U~87*73Z*KGn?L9Voy|Q**lZ9 z+3!EkYXK272zRE=v%26*nIrU&kGeesCig}0aol=*j0W)rAj=2wv~aNa9PGXOL_;63 z7<=vI=vCBr4zzSZ-CYWfWcuAyw{OJC$EB$Z(s@F-!+u6uvqFcJhb9x~<`(XnZS@+v z9#`8F5meghb^EdU5#X5aqJZk+P>dG(dJWoC`MZ$YH~YB#u9?c(;tLiPSAZP?iv6SGxM#b}zN z^TO20eh}CP+&5J zwe61}PAhW8AN;ti*t{OZO?tG|6%E_(#ykF=QCiDC+42Xf4G9%t>8S zB?~P%;x|HpKjA*_#(^zto^e~z*m-uKPx#GpeOWZZh4f|2I!A@e*0@;=Qfd$R@!?(s zzxGu_$lZBvRVM!NpE?yfHUULuqrG$es)J?$!jQ`EuZz}RrLQdI<~vM71ZTLk?le(f zEvA86jz+<(L&-QTmxk+w)0UM2`FQ$2;JdgDYPyvNr`%E#z5CHihapeal{EOa2*I}a zVNz-1h*n0`UW72uSOS;4%EGtvD}brH&CqSDax#$-bdp~(D3R?Db4|}6K`Rg9l6#!6 zc{F4DA&w4aOwhfV!=p>98%&M*IDgz8Npj(P)0uisM-431EAZ<1HTPR>a&LmTO&Mc! zT=K_HvaO7oQ(Yk?Z3iE$P9y zKy9bV#KeUjbp4brj{^=LtV@h}homy7=GV`yZMrS{^hwv+4zjC~mx5yd2yi%fc^r1| zIPiCPSagZTRV+#Vgj>n!@XDOrsC;6qD96K~9w5B-P4me0V@71os80Nk>|d!qm%@$P z*YP0GSwZhy|jNZl8kc_U&=A&-W~jrWt-%Dt}_>}?tK7vkZ5~0EIfKw zg%XAaWJr!$;YcJM!ofqzJ9J>JyS!41-)KAXCqf1?vV)~q`90hD_w2Y;(P$G~@ClFt zv4HI7)zrjjQLQj;iVKZ6Xl3>-KD$-V`Uoz-^(9Q)Kx8(JWb9!_!rlcUk0=_e6=Qxl zASJL_m6!~hx?Fg@wQ_Uxu$J#cD0?`|cC=$~1how=wVU0XIP437^76I$xVqbH_`Nu) zG04)i@5rgkXQW~Yf#|OVT_Rf;S;4Jrp~5ec^xD1YuY7d4;>urk2qi6h;Q!b3Y(CMo zg1lJwICt*c$;llxcl@(!FcIT;PFIvh8#g2?p5T$O5#G>U1GM`G59LTNP`szA)BeW6 z?oAko&yG*{`3CeM>IRo3nn6qx`1Eb;6 zveiS{*|wON7#&)m-@Rr&|15Dk{Uo98Z_?&SMk_?U0EAl zGisyT+s+RghkVj;Qa2s^!nXV~CuVCz+bAXZcuAEFR61YtFH4rAa`DELm?S4BPoR!t z<3fYC9VZ-oz-1-*4##oxMO{0>yr2aT3+MEoq4-*^f!5)Nz4P3y8+%Sg+6c&^gHi6) zo{v8^>Mr5n)Kqrdry_Z%Hk6japghm~P)_57U z@31cO&3NCPA98b=xDeBFu(P~!d&9i}7iThoe0=nG;zFZ_)rs3)v^&Y9*<_F?+@#M) zE#EdBWI?U!pC!y;sci_|xnkkvJ3#&GYC#h{F(Ze_&)n)?mykQ9ceL7Nb{w)fVjRp_ zCF$Lzu2e0Miya9_x{SG}Xt6x!vWoA1v^UaaH`fwUx?SW}Db^Tvm22ROa)eU0iu7nu z#ZYD`CF#O_J?NFn@q9OO(gxFY>a%%+rQX86M!H}kH3GrGdAp%c=ek5)pKAQFGp+C% z-&n`dm^igKw4m4%hU~O-uF&+@@z~J|WxEUQj=<$JRg5ugJ9!tHl-ruG=^nCQ7NKQmRAT{w~3A;AX%7OwM?qwRV5qTGW`~oc)6{i z;SvY;uvOO%&5k4lax>-i!NYCAJQ-T%D0ZcG3epEev3THo^`4$zo0*l~BRC1w=?Zyr z(&X2`by-AoKNXd((lu(H;-XpNkIWDu|{k zn+YlsvKd_hD@=#a+^+I|)q-!-u*_-ffm`ylEthR_=mPUtiw=&qxI-iJr&(qP(vx&z zJ*OV6&HzG{)==i6_1zQw>RU&3(S(V5W-wM{x6+8fn+cg^UpV8Y|R%iVs8rp&EK3$ zOxQ9CgHUMmqH?z)_-ToyB}>JwWf{Wb(2ixIVIcsa3`n z!6VsPX*8jKKIKy7xBA@_x=q!7ZJ>BnWI4!Y+T`9w*C`A~%@!DSi0SdJu6@VBk)*fz zyj{b+Pl+mj&fV5F%{p3>t(?UW7Tb2|8vn?6Nj>=OH+aNOQk0zPH_x}bs#jVjxryF= z*)7ee9d}fbsLv(#(oS32`DYQ|o$kkG!WSWewV{F5lcm}tzm|Jbk1cP%9)i()c}eH% zy6T~7)N$NW11`=^zQLbh{fIkmsEkLt(`r)fuKCc*6b>mEDGiswO${#o-Lj@o`g;qt zsCN9o>}LXUqr2&1`1lNj^rsVro-K2qb68kdb$4mGHYn}U24nUH%j+KZM_jLs8!ut0 z4lW<__|6yo^pwfR@+ukC>V8t8Ink3GZmN;PZ)iAd)mxm?Co9$!nPA!BEb?05igwD$ z!gSYco%{J6y*na2Q{pBQ=G>#&RR&qFmTa}QHML0y$w?JI%W1R7I$d}1I8eVP*UvNg zX1VzS=M8ENpVWbSoHOyOF{97A=Z|koAnp5M_T_Mp< zb-s^-F1F41&Ta>z4%O?D2*J1Z0dyZyQA-X>SAM?lj;2(2y@m=yyhc4o+kV0=hd1DM zCN;>&<3oAYq~il21=DzGnO6+ZZx@gL^2(2}Xh>tlZuMn}e+#j_d7KBc-XymXc$Sn` z_*tBkVEDfCucY&1mDC2@QK$v6CVDoJwIw}SoN@3Az2wMb$8q!eH|qw^=gr83tCwqI zW9)RUk!t7V9?o5}qpQ*;fe8I@XB2Z7Gq4M&Ys;5ya?>2RGLfK5xW2gEQCc|LnY@v` zU&#S6Th+8~;v7s+$ENt0x&20461#uYLdEqS%)Cb|r_1_b=B0b9FH+a0GgIcDr1(Kf zKX^GPesbX}-&@uA8a8nqn)s@}u6e#`rp)!hzUD{GqMvnirJAe*9R#jrvUhH%RyhTX zjV_bfsrbG9B|S2!rzce{@pg5(Dua$g>D}>MD5dLNlPf#fht5Y0S&grwh50_`CCS3V zW1w4P|IE}Tc`7azBmSn4DB*fyB)-726`1kxC~Z7%`o+~3a_9QKbAP;WVdsHX5Mg6w zlI|~8nd5lD1|3Q4n`-gbrK1hZcP{MM`cx~E30{7N^@x)z_VZ&&wL9jjI?-{`#DSykGVifa|5`DVwx{$(e0Y{q|`2l(IPE5K89 zjrM*9cQMRf9xp}@&TUy7_ASA==sY9-lkNQ41kf7)Cyaxq{=Nw##(o4I3qO4<6#Cg% z$4~mAaJd@YzaIntlX`b9hWg*%x5LJkeW5J**Q2m-0%4xuuXgJe#(O9EVPWy%3upb; z=X~@e#(zre{mPV->3R7B#}%}bn{**1mGen`0Ku)g_s_e5AHA`Q-4E5p@tPM$pg$%= zf{m@SI&tqeEO}9SANz8DdzkoBx^N>*FzZnT|8(#At zW>24a9@~`ENxo3<%m-s07ybZ?7=OEpGvpf1V@0kWEujFEl`354}YmL#>K?IQ?ci^c`x~GFrq(^6$aPrD2P8#<4=&HXlaR7_q~tPnRME zvu1YK-eV2Mr2rQjGHE zI}@v#nC`(Fxk+Rg+p7H6w!~p{LbkfCW_<*>!eOw#R~qCnlCV|Qc8OAQMfq<_Clpd3t+ya-4ix&3&o zFnM!_kKnU-Ek1TvSPEu43e$>1Spr&{+c#fV|E_isaPhQGby|}wq5tO@sWbRRmIJ+% z#Ku>=@G-ODUXb*l*2HxFIZT&6W|!eDRjH8d7nN*wP4y|~q5QvBm*6tKB$+|3Dw{rQ zReY*;kTE|F_)C_|#r$@nCp4iNX@o$S9%EcahRe67#(!c&u@I>xt zezi^_#^|YEp(9WdIjI4;&d#K?g^wq6^v&OkXl?~8>XnPje$c3^Ldo(nIcCN^&t`mz z3zq@Bgdfe{J31T(3)}nL04~nh4=918YSv;e+OlusU|~7($Pr;7KsC4d|^f=|jxdoy-1X#vYSG zq4;tH_`pqxX`j6@#)k@%-2K}&plsMUI9S=ZI4%>>yG;yq=aOAp`FIC|$Cj^rx+kQ_ z09_x`I{iKr?Z4D-dBZ4T|2kMmae~*<^^o&H`;o}Szc&3-%@?&6msY%LN&Z?6c_~j% z=+IS=GQp;h`Two}uQznm`|rt53p|G<{*4t30E{5=uMxyhJJFke4gDtp^MuKRS;STb zfwTa7u^%ecYNJdjh{-O_iNk=eEC%j~{nqs}pMwt#R|i9`J3gHavADC*-QvT4dO0d0 zZWihC!_^+!`*-!pkjp=>?)8oh1|vnNaPdH)>ql=+J;5i>THy?->%e7^2>i#S{z6YG z8^&q*wVnDlT;Pa+x9jo=`8!NVp@&)f1;eg*w;2i(*BcJp+<;ouB61Vfuk zrpW$spjiy);Speizpk#d(>;9=AM9~v-2c`Pfr)3}iVO1Lzq3t@@|HnAnH`3X2{2A- z@l?X6e>uanX!KHwo%##ejMHc1gOGw9`M}V?$hk&)Lp#Y6xC^nr-Gu=mDJjn3AAA3G zx=3hyXOowTxZia?^gm{?4EzG@2@Gvizictv8WYRP8fv$6_3i(eJuIAe=wW^{g{970 znEZFP0%q{P+oIx_kn4W?bOOa;x~8PD0n!)2^Xc*b%I9Fvx9A<6RL6|ozYu9mesB*< z{4Nh8egUV`o4<_q3qBa0t2f3I@&4I1=(uJb;5A>@_;;XWdI+|RZt)!JGk6FCT1%4s?GTI-ymnCAHxE|PQ-ZNS+x|b3udyCr)fw)PB zVJ00%%lC&tvu$}stx0>^(cn%t3MYt4mb@DMs$Cwf{fr(OoIx6|2*&XmCkMZFFM1lk z>EdJk{=(tLI$*gvXE?>NQ+z!n$ zI0)u<6P@NjYe}I`XB)|(bIi60@pmR9GGwkX?kq2FM7pXM7-S_5?#jmr?Zu4A3fism z*3}XZ+Vh^&^PV)>WpDy0=$mm$saN2ZVy+Rpr2<0-xjpTSw?6LKf`)@TzH#b0KcMqp zVX=(Ayj6PxGj2e2gKjGaHxL4bm|1Kcd|-vMCcEUeAOFg%qKwFRw)dV})LCe?#SZZV zKw%+?Jp~qsubN?Cb`9I2qk!~<`WKbF=w-ea#$UCoAMK7D-s-uR=ep%0 z7@x;TfP0bla`8A`R@t+Pkbv8=Gd5ErJ&(?!n7kU=p$nZl^m`b$iwUW$wm4qyeTTh7 z5`{ut^F6)6hP|)hj9Z#7e)2=4>v4CsUW@JqtPXC6+mN#~bq3P2+9vp3a(_BfYDT`3 zH0fhvRmBxrBRy5a<}F zG6Ha&>%E+ZN921CEFx=mA@GjN)s!{RzL8?H^a=XIA6i9+Q85V>gd)?TR8|>3`4(a% zEqoNmG9JXSZVLF%w+f%R8)(!8;0&tu&tuy?v;>|Kl>VO0T3}Lb4NP7t7xW+$8oSr0 zQXsveK4Rg;!l^2-9XD^wnLXLN%s+i0NjXQIvvSQ@ckBmPF>rNZ_z$9zoR_-OMt)L8 zbAg(pd`A7ew$W_Xs@;pzvZ67{^N#JsPVAPEmIfc&$(?CuzW-%LztNL&>Eo#xefi6b z{tbFN4{&j|JW@CT^s1>#N{n}h$vSg6ca@!F%p+4ssB5<|L`^=^y|Ao{)ieQ(OTuFh z&X3+sdhNo2Ts?%?=G=B$61X?Fz-ZgiW#&sMEPSxfYbTNvIGRQihuMYY>#<&EE-(%4 zvcLJ1HK6l`PZ;1tC=45WE0*DH0EhhkPViob#mO}`8?TJe82iXYtC~nNH4*2Zkqh23 zL{!IYhmg&bvLNstMO(8$9#PF#M9o%%eT!ZM+DRIH`v3^K=^^UneKQnlbwc*Lv48+%BBUmrQZgPBgYFuP(E9+q- z?3VrpIy$VPH^?&^v_vUN*`Ch@2w(dBn}_@?OiSsZQ>b|WfSd-UU!VBW`8&`_fF?Nz9N$s@9wS81z5L6H(L?l%d5CNq-LlneaTt|g-pi(L3#FimH=}7i^88W&*cqLSth%b6 zQ>AfB)=@AwU3ZR*Ml$TX^US`>UVVZ)OQb6SAD7A80dP{`l3Nd`aLOnD;hcUZ`=3sN zx0ktBc|EOV)RhcV(eo|L>L0Y^oq*XzDOoeTq%$KM)PSxsv?I8^kihkN=*aeR4_4|5 zcJ`)g^`V*Nm7Fr?y0e>o^J}vdX$Gb~$XJ`m!_5}+DI-hw^dDR|y9J&!h^h(bT|dd# z7;mHE*%fr=^%`x@=Wq+&5c7J`F7K?15ky+*NW69nTb6SYblo*}ZtSncP?z_6FU*;d zpf-AhSxVt-@8Qq7a+|$|;rY}Tz^cs}N#pn*P2jAB0nfG14zdV-gp>}{S+2E)=zMjj zC^qU3ks^NB zwK8vpZwVR-ZoU?qZS9fIkJ*4#(lnBR+XGa?EsRGp?Qe9BP*Yp2V{9Tm)94Owm+B5} zLP|&7=u(~?pDLjOHUh*3G<>ysirW$==U30DJZoi%~zq@GR(JmXe1Z%b86zZoe-)(n zg9wY|ULFpn@QK9$j!*I$%IE1@yg}hQ4aY^Yot#LBY%8vp$pz}u#oBFD+=>TcIS|Zc zP6cx{?McVHKWpAgz?FS&ib~E?6#uT4x&-?4<QULrK^A&f&h@J<@^xM)n@1p#m4U{5WtCuO1h?KLZyD%$AG`Pp67w# zdQYwZH4=ay{);mFA->l?CiyJ)9%J7+$t-~6OFF2i z|Gm1~9@_l`z``D$C7_5F3_D$b)8?sTbI=Y}=@#Nq+;gflYk;{~9#@AtoxfciVR_k@ zqXO_GeDvn9&5JUF{B*&r)A*)Gt_Z@(Hg%_*`KI-^N96od&n8yi?i_HT+(>t(=;P13 z({`f{sEw)6Lg(H*Q)S67A=NQWwZddA;h8TaAm&Ud#Gzj_2w zWIVZo6_oTY{*p<99BOTFA_W zyH>R6_fYsi$kyw@xya*)&4PWv-A`u#fS|IhSxVU^=AKu~dFM`?sro7Rg zo*wj$K@$tWf@$Oa5uat~^20E{c#;mGK>xSZXV8!<;c3YAr&a!8^uXU2VFRJl$TD>4 zFM;9d7@P0q@r5|{G)Mq!4$7&a5b@{e^!NV^kq7EqBG)VHbEmwRNaElHz?uP%hm(Jj z83C{cyX6~C-ASC2dISwmTcP@dB9o4fPZAh`H-7=z82~CL3Vx|;{cT|DDSD^j3qXPS z2J^&v{%jch`^!V2E>>&^LyUkg51Z99P zanV7|65Xu$85-h8|GEMxpxyvVpUzWo)!(&Vj)7SqBGBEATF60};F2oPQ!ye8U#Roo z_B*MjP|c}t8bFB)L^6Tbe?h74V4aS8GXW{cB6f zJ3*!X)8*0KzQNwIwR1XXqWl0Y`A=(6L!TlVX#Ggf{g=a*0-zj}=PCNvo+t4HUwzYj zv%(m4bq@Lg!1^la=E2>`w$m<9B%kajO0In>lAuzUmOpT8V2QIoy-wl@itRx37fX2z zh_--#@qW>0=YYafC#Bl{veiV`CMPM&f4ID#fllx*_Dm@0`?CY6j6Ov)e7ex|*A=`2 zKuZ8-CtyZ@WmJapCG5jrd?(%cUj=HC)tlAff~^I3m7%f!UvwxFnJaD+hQab#q|{%z#tZp$^!SiFaw-Y<~|Sm+ocduv_xbJJiRjVpP$KO3^nfmt`44sbF>z|ANi)O8V2S7T3H{lstO#mn z1tIuh*4_n@ucPep!Y_bHK{4T78xQXRh!Hf!wo>k%wE|S@C&g^v>}h|REZc0~BHLkk zSAni{EEdm1u_K$XC%<{6s2tza^U6jPd&~L>J`rpzJ$ic)UN&moAj?HtHoZD-)e*G8D+~Nj3G`-17YHSRBSF+xbzerrmy1&2qWCGlFD7RX^HkHwa?n5myqH1H#nQb9cKWSe8<-yVWD;W>;lv`oZXZIAp2P@Fk1u6QUa)1iTwY00f= z%Et?=y+Fc2+o{~y#Zec(#LB<=ys0M9S9dID_1cBYM=$p9HDymO@flB3qGRV!vgpxN zork&=>Ov=d3;=5nNEsAGQOkf)<0Y{@rJMMMK!u;`wDYOprPTV0{i9ZR4w;Z zWj?*@GuDmd2G<-Mr~)skvYI0Eg;h%Vq(5+tDLxF2WF>^x>RZ{X0OOkxco!$E&-f_xD45`4Li-NfpA{0?7Q;QVKV@7kv|_sN z*e+nGr%^h#Ki^E9_GO9{k|u@3@Lt(D8nA)XUgoFdI>gQdghZ%!MN6J1v#^`}2xlM} z8mWT#vzX|V>hEttX17>tA_vkTamEy*4Dr|m(}F5ZgpZ!nu2t=RjCE>0hy6+P?m|A!iY1Tk zk`3=tV_hp$DZF%iUt~C@qe+{+B2BuKB6;8k#;*iQWE1VagMSs62Cn=@gs<`3k`k8t zQbKLlEJs{7>*TGMr%e$b#j!fr#Ac>t`R>5&*kj!Q=qM>&0?&DrMC`M^+<9-}=IWR= zQb97Iy+i+KEB7@CldU%x1jEEb>*d*qn#m-lmpe4)CBr* z&Y}3YX%N2ti)+Zi(e71%_g89rF$ufP8){xN60aFQaj-l%1hWLlwcUteRqM9kIDjan zIUi^LPHH;7Fw0Q1Do1(Dga#_*z|UUsg|ejlR&`TAzrG>LL?Q_k|q;eIXB2VUj=b7gVp$!}W?}*N1Edvt?{^kK z2NuD?6f~DkJ}bJJFFnV!U?|%w!X*b2OOYD?Msryca`WMol=Gg`!C}W7#f*9z_w-fD zS7H74_qqe_^T6KU03N69FBs4WOeVG$ycQU@4N{x%m)pR^WX8L48MD{8c}LY)@Eo2z zOI_Gwb+F)E8@#Pwb+8A-;G|t+H^$dxzPEFwd-r+%ea9i^HN7q=`0M$G#=SSWOG}n0 zI%t%Q0l=;`&>t8u{|=AlyAY*r_0gtk0h3=|z8k`R0|N-fCbw6(gWo>--ml}ZQT|my zKWf;HUl_4`VJrK{ykHkQ+S^hq-|u&xEukJ54!u?}>0UWO8F;7DZaL4@mbbL+Ca;+{`Ps)txgCof2R`ZI z=8BnNK`LFHiB*i94xOHGak%`vVIra2R83c_5$`)S#Zp=DDtX5rfPtmS>Mx!j3SLLfNQGd18;KqJSk zKB2A>Ygu3_$7N4(<)C#7W@#casbTD1MBYCJ9Uob?I>?3unc$|+$5YWA;Fkwo>?EYM zTXNIYH`HNK?_|^vN!$Vub`$fSE6)2~?E{Pg=la$~z>0oV(3B{lQC+i8GC6|iux}Qw zR4`$qdPNvx_0u1lIYTWzEq)Ln!jzSo_g?qC`e5PQT3z*8=#cQyojQpc5<7jdGx%6G z2V=48&^M5H1BejsbG>1TwPs@PguuvwmWwu$ZVviy;m+fFzI!x!vhBvsFM^1j$NJY} ztnej$--q7ftJY)baadZs^dv#+3rYJG0h<&VR|NA&@3ir7_&vL)-v=h%Ke@OoL`U|% z5^F&n)8?R-M6+suL>rHO4E9l8@amWzyWIonGIN_3`0T6Blc8!+>143-Xbhn@%G^=rP#Qw}in0=nc9oj*q723`QtJCq62;wBnAvnzXjnFTw;ZZIuISi@iGobU$2#t|C}yQWh_RBIIAk3 zrjxF$L2U@p?Z0$3d&KUo>+Oc=K@SlL%mv+Ze2XD)_14b4JnfqH3lani(}!v6#lSn% z%)qR8ZND$_uDaps;&8~p!H6|pU+@a_tA>3vbeo`{u=ehC*#2OH;;kypQgg%aY~|aV z#>1rIe`4KlFUf|?szwc*cr?Vwt>9<%#@cDqLJO!Qm5( zM`Y670zdu5ePOO@5$p~~IdaMZw_f3&6#`z!a>^h05$ zzN}}({)?SQWx4t>rn0p1L z#~1Bo`wULJ7Ic|*dAvAmtbtio-J}%p6-VZb*7-|%y1Gq6+1~teRYHzMw@w>J}WaHy>v4lDi_110=(2qzhQK3!3qS*SNZKU6uZLSF+l7liC%7iiMF2gwAQ`~jDv1|olkz5r=MA?H=Xq_htV38J zEHv3%F?lMe(s_P~561HnB+5dq_p69ur2olm4M%6O$IYhJ@@Hr$9@$qHrs_(sqA7o2 z#k7UPXhu`@`CZt{c6FpD+$<_Nh^Mz*X!lThxwI!$khXIelkYQxHcY9{T~89ZaNsED zyQAcu)vAl_Way-`nAOg2&}#sl?BFwK6GXC`XW z=F1JH(H8?%kIevrlYoODS3+O8{He}OCgpEQ! zr3>E+>*>|Exy*@M5V)U|HMU2{DQDN!zA;pS z@;aZwj}`l)j&T+RbM?&R`!+@sImK#Si`vdoU!7@O?x@&23C@h1SEt;$beS`tnp0~# z#gR~TaJ_V*#}psiwT2est8p4n@_6d?T*UUS5=Ev&|WA zo3vvWP&N0Sm*WdimB8Y-CpK8DU87V7DMRdc?{`nqDddW|1M?(#m9i?0sk)pn`Iw*Y zrB}Cj3f-b*7{v9mn&WbFqCXQi&PhKOEOdGF-q-fK!tq!4@;Jt_q-rok%S;>sj{;cr zhbzy#+8UeqB^*gO7LI(?@}p!R*r^$59}u}O!BW3!&FsY6L{i;*fu>gV(NZ>(`Ld<_ zHW3yL^ah;6+-PKA!o@zP0yp2D*TMCGe{&{e%5Dfqp-_?%a`TCNW1G^mDbgIY8 z0k}!)SO+_ASEX~snr#irUzi>m7?co z@mz*46Zg*Rm;(uHQ2b!RpZXDVPM*=x5(RIn>Yn>hySU}Ub&2OM@0x&JUhFQ|z2;-3 z+`lA*d_Uw{I)Q-X8JcVHxV+~zYo#2ULy1d@9(u~DaBS$YcZ(V`Ef|Dtjo1f0d_+P= z`x>9D$Rg=1F<26V)t{Slmp5;#W+??SFq~*bN^;jFCUBgaug$T6;(n;#vXJsCu`=|tgE_jo zRu}rH?y=du@?yzoStPlv-uo93$%PB_OFD5ytoS!<4xnqT8MO2mqMEhxCoTh^7opo8 zdw6mjNpi7i?Wn{j^l=W3vdt20daO$<^J}sohEAXD5<7n~)`%y`4K4Z!?h(e-!^K9A z5;W{TT(`wf(iF)q4jB*gPKXE=iVoF@)AEK(-%u1_Eg@82#?}~-4MBf?!x920(g?~i%2UnZ~uD;k2xND5)VD+k2_OlULJ|@)pvQlFM+Tlbwy8Z+c9*P z$nf*ZIiS0n=uX@9zX$M-q3?!>e+_*DTXoFB{7;o70LfgFeD4>l?D)H-#QsFX(0Rmv zxA0|juJdmik?4xW^N%JGZ+Xk=Q?cd&1BTh#1CeZ`c3juqNOjQRI84#635=ir6@d0p`l#`ViRw&=j>^sQdz5M8l)c3 za1yjoO{fq_PpW3gA*w7XpR4#O+Hra%^Pitd&n6U+zj2As9w9?_bJtIEEC2z`>sL2$ zxN^ZajJqM4&%*@SP9LO5*ZGCn8h&m-TmN+Q#T6;SLF;3(NIIH+fFg^SAnA(+HtfEC zDqkbM__Ajm_XJ)BdR*7ZIw2id9KzpfRzHA+S$5nLj^!I}xaj8CF1^zWd4`_m&d&;53cO@Yj}=Gp~}KFw3A4o&!l zSIM}**J93UW0(NgY{w1a1_;^^PVnNNy!YxQjQL?zay>Whp)Zd^@*LJ=PjlcO;J&Z* z=_4@HITD(<&ZG}fx<0O;H334k`{LlK|w&|8pVBUkOaVc_e%oq8sDxCW61Q*CrAa z_m2s!uc$@eEtH-~F(u@>PdpCg>3(I11xmpae%$u0J0)fNXO#cG6JcQZ^k)*AV`3@> zAQ-p`G6M7k|1OgWurlED1quH!t@9}26TdfFOB9JN>oL7dv>y__RQo}wsKFhGg| zu(b5i-D2v0C1f%BMe*Qme+Ev!N)}h~{ISLV^YQ0-&xx31f=n$9arD6ozzaz-6JmiE0oZTAX47{4?g*uj~B} zcLmtNe^Wt(;^YG%i2&qwD$Iu5M{(HN(M|W@)RT5%%==5Aq00iNTnFtllzc=GU_t>x zmS}We`CkfIhEY;P$@H_L3PFVmotdal#>Pd_12V90;QqV9p`T0!rtvIdLXtH^L;r6E zSGWv{rU$V)_Pq3?XkH3hrOBwU;P~YaD$Y3+WmFQaIa5QI@F<22#y?YR{gP|`!(CC+ z{&y8b0M!Qg{6VbWB@}R1QbJG^J$7_U(JI zjLlar4FAmja*y3R<=rCt`1XE4ZpzrVs1_jCtas-75Y=dE)a^hJ&1V?Uo15DpziQa@ zF4an7L|S)s6l^8D*jnBJ!s1pNXd_X-_g@yWIKDWcTM|gdlp#b%h^RxSB_c(65~Q0-K#mV1d` z2WV0i42=7|@xA6KJ62&ASwk=$GtkrJLM3%qb221jMM(0SX}76Nxa0L;tbrh|6Mr6p zR*p;^i=aA=&1vN)4eC~op{$i!!K>QLv05GK|ldrj4iCI=QBvPcNf<@I^Ge{8$@kckF{v^m0Y|z{u%fe|MmL z7U$HdMc)XJ17@5)=>=Zh92t!+pPT2@nd6>lF=YOcPzH!4wu_N_rjZN9>)6o%37u&N zHpse~G?rU6+WTyQe7D?zy*Nh{B49+30?_cA1wtD{1(bgi1T*iTf;82M)n>YsrEQiR zyH)06MpB0Ob=G_T?P``|QB-BU=FwVVs?}^y9EvlaebUfU z(F8Q0n~KBZ1)x$kqUk;tkl_e|efeW`&+z`{H^`%CHr*dJLHvpr?)8snXByyR*k4^Y zpee6X#Id)FUwM-&69IHduk7ysk{kIq$oyJ}4{9NCV#mSb9vVhF4jx3#Z@%VT! z;#WF&5HHEOogOUz*cV#0+i7ES`w?E&^6QTLA=Nm(Bb0y^_bLd*?f4rh3$K@me=|bB z*%vjKqq5!p1IIq+V)u%#KomL0q*nRzJD8&ojZkPraZ?cW^MaR#JWLI156pTN)8pu- z8vqm z#ri)0YbUz0{qD-sdUzxNX~CofO3J(E`fhpAU53o@EJ&2oiRGT;;mx!64g?kqp@w$1d`!@s)9!+5Lfy&+vG#pNSRsyWT_?1{Rmd1{|5_yos^+c5kFJ=NN%Fat@tySdwSHI z)CuRuPzcXuyz;on<3*Rg5lb`-q(+?YcBJQ<0JP~JB`s7?8d_2)M;LxfkM3CQixErd z5~Q@^F>WOB@f2>v9s!fafJGEqIjXA=tB8;V@&-0I{(nikgwm6>}?xR_G{=F)>*?8W=0wvb4 zK=+P$E3iIVeP%6bVtfqT!!84bt)(HWLRogjo!^iHO1(i6ftp+9IiElG8(sZB89?;! z3S5q73Y|s{fIrUolZK3Qf*X7Zd;b?|Ht@XF@fB|U*DUz|H}y9%q4(v@{j3xV#g&{a^GTv>|vH$No$p2YrfY z6tVOF-l<~$zDVeQfQAW}97BB?Uqx9eofujm6wGuQ8{D4x%VE<>{ZwZBlsf%0Wyb#x z+Ef3HbJ_maEuw1E|A}sqPbzC*xYU=!c%UA3R(+zBnG`n7z&WeIWJQZ$zfiR_- z_-)x;5=pE-@&Cuu3r{AE#E+g+Q5BhU+{eFHF2B~`COWuh^WoTzliEdVJkyw*bag%*IejY5yMe~nGB3_=0DD5hPF6x^%+wqu_0pC2uAVF_J0Hqg?pnjo z%S5kxJ^l3b376^k9sPs>tt^VIMzP&yPn>1rs7lCgGTU@Z_Y*w!p|wh*zS~xFhV)W} zdd`m!PL)jsv+gJnRTvV!&i*3LqDyJ_!Ua^Pc4dF<(O8A;%)Wj?p1JFj zHmM;PhpH@r((EYE1em0n`8+@AFg}t6HA}hX!+snvjQNvfX=)xT?D1OVY^OItUKRm` z&?A5qTb-wFy5&@C+!qeKg`hpawAxV11#wgop%wi(4f5j*I&nIl26^1GFM$R>y@Kg9 zZ_3>$MNe7z%&(P{X%?8jI~yhs8C)v3^VkNnCp{>lK)1Oul36w7TREBOjt>sO5|i`F z&>+ihqmFzNz&NYwvsG~Qd=zoJ$*gkacC@>G^F%b$53|+0uZYwxYWx-J>Vxg54m}rA z`|{e>s-&K8-7qgDJK-`Y)NLOSICSrJ(Z56-?5y?_U6L&O0z0zT1=t=h8;KFiaFvon z^TokT=h_LK0{&6k0riv)gnjcWKD5C#vn{5yv0h41w|-0Khfu;Jc23H^M6vX#8{l{6 z!Zer)+OklRh1DTZ*v>YxjZEE2H?*3I^_7U!C3Fi&XXrSsmJ^wjik<9J)WS6o#qwIu zcK@kVs=Wm%1zwo(Y<#VSl`4uDdkM@upUH_Q_&#={={#=Es$F@ky_x?4XCaqq!Ni@_ zsvfyHiiY!DhZAn{QCYbVL8C1|g?i3p&0YDevq)mHCyLrT+Y&Ld#Bw0G;%?PvTWV@s zzEXY$<14IpzWCBQ(6&JpKyOcHk4vD>HG+N~-_6`iMDDtOyko@ivkE4dA-v3G-)gh=b$W_Y+trfldrW`|YwX^DRV#xNK;jAI20IUt ztPg!`lpwTO8MsuB*`9dp+- zM}j|48XrSf;=hMaCTJ8~eNUMdJ zxw-bp@*?iVL35hLi8Z%M`Jz=hc#t}AiF=8%1fcPkjvo-Gz4uduONUO_ZIzOJfe{z4 zU@C&7z8>FeQ1Y~jc8;yKN96^&D@z1|Y?4BU3xyd>*-9>Re8UbDnezF?mi%%LY(vPIJ0ggzloX+4IQoh)X@ba45xd8o$$F){X5xF`)nU{9 z37QKt4GD9=@YHc4L2?^n=vNuvSVM4dmRU=r<1`0G_;W zxNEzj6Ff#CDP5TDwCnjHE%=*%RP!>8d*r-CU_@q^#-m4}wLNmYqT3hAWOt3ON674$ zW!EF)9Ff!XH6L2oUBJLt|I%@lG3T{8S#fyhkLMLf$T`*!?r|r1Ax=0~u2fcn@mB~i zf1d$GaBuL|fNgfae*+6Tv+9JuG!O>7CGZ58mS&ouAU`HQwLRToogGm!b`>pm`c&BB%Z;s~)6^4)|LaDjRoqJtS!Iq9zya%i~eA^o%>u4{QR1@FO;K;)}PG zb6?ybD}r1HM6x1ZlxZW5)ZdU1F1*=gq^F^W$4p=&kfK^gvQHYk4k<#TN{^6xfJR|B z-S*{|a#NNj<1WfAr1Q6%-6PcDcON+thMP28oFy*90w)uCwnhnu`0MF#~4u9=(Tj zYQVU+V%Bst7#$YQf^y$W-;xvB5Vl(Gk{GOH|9mu0vp;kQv8rXtZAua*n^zHzee&LY zi$USa(guZREii=zV9XpXq$Q(!L6}gqq-%hJ@BU z^94`3EBXx4UgQN1@vmYtB|AR{rYGxMpsQ;iH%zC}9aKc5 z0^-AbfAQd*SBJnn@>yeRc;<$VuQaEk!J~9Q92sgq4!^6y;&K+!Zjuh@jaIZ9=0+^e>V!M{#;upL_(BcQjZ+^xo3?rBa__LiL&{k^J zejS^gO0iRMQFVqiM^+!N{@cs7J=eBZQ(BZV)rQ*Tde!mlhEhxL76zE)=OgC&qXgI1 zjOEknR^GURjk4*_G|<@F?AZiO&_th^`C5Zz-P-fLSlKqzS|y%(*L7*@;HXwA7rS5h zH35hF<9Tj`z&u&`Mx^ud^?Z!4gO*_{sTfYqlJ?yp)BXI;-|*>1b#p5U3X9BaBje(f zFVzlS_k|oDRH`}aSZq$g#DUE}pE3+#%0~*Lo*bB)v5u$QO-TMxQ(blB{`kGSV#(~p z>oN)*y(Ou2L>jkO(WURCE#SR?EIf|TjfS|gmWCtlv_TiG9qpS|_E(oXb(^7dq z`*-J%$P{FPGcc3Ae07PX{0Lm8Ih6p+L<=N>tE?Iu&?3vo+erAo0Semnc&p8G3YAJ0 z<(kV@D&|vK#D{*!oCYD~LH8|OTy0Xv0FfyWQ0WO{^Z?T-ctrY}kFbVSfGOI#lC$F7>33q=bubir__9>G$ z%$;fOuVkJDqbH;bBg>;)`+M z7Z~5zaD+j+HoX3QE++n?ydGJUzdaL6cIEnqeLD^L)?zlq(7bKlnz#ftAojv1m#biI zX%k!np5OT}7o219%10>IePOXwbczJ-S9q3I#c@J#BRCuD<3s?Idub}An1la zI`qM}we9@}AkabJSjPNW3B(c<0aL9&Jgk1h*!-;c5#hrH7yNs*=EFJ@%4OLHs!@tD zDrpmgZzl-^wx0)u0>3sPJ7Djxqz=4LAV_U9u_TFU-%ad}k1Hp<^8g;K_xa;$u*Jc6 z08!a=yif`G5eBso*-^i$`H<`G8qG4F@c0HH3k_VAm8Nt0xjB1g4-Spn?E@Tlo_FH% zR=~Lbd&3Q-6Jx41@FTxL7KZ?jQjt%H%6Ah;f+IFh&mgbnH6)JDKDYR#QsCYkb~XgM zaWx?Aw-vnK=)D@A$?UnwWUM&)Y-gf!L$7)rDQ-Uo52=T9fhAJ$muU`#rI5k{73%aO z;3}lBVQ1BK>r`@eCHAG=LUM47`4K@W^fUL3_U#E3D%Zx@TRF+HTOkCY`^B>4P3mf8KuPfw3-9%S4|Sb$QC_zbujL z2S2?E#PB&(3~$<8AoG1l|HJ9NyT(Y13HN-d5U}yPY}N%Cbu9_^=%o@Kj7?aWD)R@Z z>Msx;ZH<7<68QJ)r)Ppxa|?r!YE=(%$~M0*q&(6$nks!er@8@_V)d0g?h*K75wD`Wk z>l}fafeq27xq#4c=8B|o`=G-T_}q*Y@?QJXmY@4xTQodty9_bDK+uay&3oiwg<>+r zYrvh$ALxa0|KJ{TGT$EG=F}dmiQliLn)9h{VqfK91)&0FyIR6$nH8`QCM{A+y&$l}2;|*9Na0jNj zG2akMesfoLL86_~4sf}a0U5jWS;2mKK1QTFtR1WUt;$N>lP4hOhH>X=iH%opA6dgQ@?T)43+K#xlp1|%N?Kqujw_xzfR^3SS#xew zv#(<|<988SBu`?L{@|9FsNdRo4U6cS0{m$A02#f-dSx=+=TqeYe{}#;y)^50OwSxy zB?UK?I3*pRsa{+Efz>4_ZDorPca(XG0Hkn9^DPFZ=%=&cjtGLcBDA=gz3z%&U6Sv? z@tU{XdEmN5M>ok8O>QM>Q5g~8ZY!kk&m)JI6Vg^Zo$0|l5!_rO0|Ufu{K4L5$##RQ zIUSqEY;$2OO_mb&_H#3DX9{J)({A7`PYlckf?3AeDc!fP=dxCvSBVw{&$(W)p1-UA zAr~Vd;qtxBpvpGp8*7_Vqsr8-D^J#UR_qLLdU953%)om)4J)`u3t0uo;T+NN$!pQP z3maedE7xkc;|PRrHS#(uTC40}t9(qeezPXFJHEOUqC|_!{D97ca&R?oX4Vn*jNE!R zW2&9)0qa+{oBnwk&CVIF`vlmOvlF`ME3HlR)U$8TZy7b**qvBhN`&Wkc*YTle!E%X zUh7;w??y!w`k;a0I-!BkTFA#bfOKTe!zo^a=NnvhgI8(fp{jGjkId^@PWW?uzE%i| zb3OU$N1zX45P-RmsPuS3DD|4_1Ji6-A>wxa`S2ND>hy@&P>s&n(t5wV@!^4w(jA}Y z6VpT%UZmyjIQ2In|-LC~>*k`4AvztA%u_ z#1?{;#wKtiQm0aDKz*u&#%1rEJu}g{nj^iW;+H(v7^l8EOYmuldEcxKZ#Fq>_9RnH zm&>Z;D6STv9yN2HcX)hmmS^pCvb>#Rt!S*qf-diqFvX0IcIRKkYB2WNv8<6@&lc${ zpB7CEvu(^+Z$^$=c!SB_9Br?-R_yQ2A#I#@O-K4<*>R!I!W?Tr^e;Re6qQ&nXVR`6 zvQZBnD1wcZJD&IDBz^3>f+6zZb3c4!y2={fFmpyI|IT9}*Jo~P)dXz}&W|IBQ(4d-!^x&Vc7*Cb%KS%b~wcwbZ-9t)!aq# z;V5Fawj9${C!KSkDSrsWx5BNO+^uG(qC+lvJqW{oVm@Rh-Usu`P&KP%&@DuHalu#F z0r2A4`Et-J-{*ugS3RIE`;oJ`%p?IEG|y{DXXtuxqR)bR=RdzZ0JTs$v9)cLmTxl| zCg7B-NT|EM-Rvz5ZnTa609!oVjOhNF!!?;M#%Q|y)UII#X)MRT|6DoXpvd*&Laf02 z{f084z?;1QZ8#PnHc$^%R)K_xi}Q89VCzjPo~&4xj%PiIonYxh z=bcIEKo=?o2ZG_@e#(Kq&x#^6He(Izh|1u#I7m$C_?Dex!V9e=p*xk`dyUy3<}p^ zb%Y>=dJYdp%wJHy6Y~^VFU%Y{V?5dSxUw6 zVFwZRzIG_9*JBTS<@t^ITP|n z6MBCc9y!y1jsv@I$=yv+O)iXcASd&zFF0RK7)<*7L{e}cJc$Uug!?i{knA#OM?`hv zt6FT$po~b}r}Rs>_Y0+XNBBQCn9#c)QBIw?K`pt@F=f6W%d3&YC^KJLtl%ji#`Fpd zMOX?iMDH}v%t=(n7uT#Bt3zQ_&UK@HJHg|_=Ri<%5ynl@9)$DwdqQ#A>3s8=I-9g&0<(ipmd?v?gi&@&Rm}9V$ z32xj|TxpS$KSF*;?uO7TJhY0w*;u6DtJDglIIHvzQn)S@^SI{2m<;1_d=?aeO|6a0 zFg)+PR_tHCdnD1y3X!=$aC2lyjH6oevN@-qy%?19T!+SXq;BM$q92b3wlX_0Jt;dr zxhr<=96ZD8hB@MbHsWqw^gJN`f`b9IF6bj8YCx4XONC7cYd@k{YNQV)Za;pRSxtZhm*e&4yjutrDq6hF ztFHZqH{;ASMrsdd4Ny*LL5`W>&}72}_Ry%>70k14pVfgn32{b>&~_o-6Sp@m z*{hbA7}Cwa`+SS;WW|!)PC=D1hhy_(nE3QxNU%U&zNOSr?8l*&mM}#uwnH=LY1(B6 zB14eZdRF1tr`5~vH!DTygy9IYJ?t^&_HJJybRrYl22Y3f!-GuLM z8yYzcRpoU=>ut$$TQ+Sg9F1PcExWlzXY;xthu<{nH5N?(d@$B#$+4!fDsG?ATgLRJ zii4%N%g!?>e2qQ^zhAY%q-IF?T_)9|i7S|%bE_JoyLU+Ey=3NT@>=ABN_U?I*bkZ+ z7j8U+Rv!2mTc$f+>wHt2>l@8Jf0V^|hir!;y!i8Thc<1C4g^6=f_wa2tls$DWu*a) zrz(K=PtD8~p-nhP#NZN=fV=;tlh$QOH|ie086?p5dwG!Xa_p7nXkZfeHBT!j>d3AR zr>895IIQk0NKH1Yyq49`eH)wTKK+n>@R6+a8JL{nP8ILcUXL-!;bAW)*lq8}%I^L| zMZA3AO~M724=}q9SAytZ;iKNqm8&YS^3vRx@l>59o!Kg7?J5Gp0|5}Gd?*-~BlZk1 z=vK5&HMoTwRi;Q)iFgor`6lO>-X)`9QX=lmgBgM_vrXLx8Y#w+?R?V?yuFg5hy+le}>JUfa;N){sxc@G?OcgR?IUgz4(V(_B!b>AHQ zjc=DtNjyb#VGI33lw}j~FLocpr)j4iVg}s@d{>i~2PgV9@`JvpW_)XSTMd@l5->1GcvljodUJ%FWE3LgmAk{-Z^|p+vIp3w&M}RD05`-c4Yp#_#wSo0prn+xDMT1 zZE!fN0J8OS@y7^xFeAs{-u^r|0EFSwS#@v*PBh%4LT=z@aD~HzT=4&5@4cg%>bh=m zi9rHLks<;DQA9uxM4I$~B1%^Tqy~|WbZJ6>fQq1?(tAD3=b#!oW1wjYt1>=JQxrD@!|(#*4tBio%#(CY?*#;W}ykv zEg3mg=D-(Z#xKH*jrYW;P~W;X#&gn0o-SPsI~zwXzO%wGosj+*AtG6Ncw~a?&I%6K zB4e0(IVWxDA?5y!sf&kg1nCg*fVa&w)1; z*;f9@wpt0)MPRy!6tOPnM(%p8u& zQX&O=bg!p{|5Djo66=`$)NEP!T2+K>7KoU40#6~IAJ-=3=O^{CK9?xQLDA(RjaQ))SM1ql(>)4*yKv>7p(jpS;b?$j|y0r7jAi zy2I_^fvB}i1Ng-T&zoPfbyuoJB;-6C4=poM>5-;!Wi1U8Qq55VeGznD!biv!!{^>N zct+8E{x0eqEu>H(Qg(A0JKmhbSmhD^nB^SyU_kCp1-XR9GhtsR$zC3tvg(ZPy7!Xi zGAva6Y5TcN*SMnU&+8THFXrUk+O-#e>OXm9++xJ_VQMXJtYeiFsSBRbG*L4*i~mH$ zQ1HIJdRARiuhD&HtihzIlf@Knz@(#1CfAeLzCGUDqQG0NAs3h|!7Z2J(1)FjD3MG| z+pFBur8d8$+6s;^^n!iJN5}!?wfY!6DTs@**DF}Y_fl7U)ES3*Yfx1BFQeBmRw{^Q zS*lg+;w2v*zWisZ5GE<1@g!9mu)p`IL-_}TuOHD};(}-qa{L&d_uBGJy7nGT=>=P7 z^Mc{-zIl9ie2OgJDZS-#L-U#%srR*9Vx-93(Fov^g1g$PA9k12RH_Z)T$g0|?Z%x= z&dpcIC?9PNxosPfC@AUIO+O~Q#Rn#Vd3hwAQjO02(Y&{L|6<56-yE|;fn%nE*C{Zx zoe~7#cf%S;AiR#Us$e#yMpZxKg00uX&(ADwuwze?OvtI&9uYAl8IT}hk<=f`=8A5g zxsqI3q^=owB=BByeUF%u!x^-}mE`IxC}=qOCb5;p&%Msfye^VI@|RYWb8yD%nizI3 z3J^EsVR7xfb8kLf8`PmRi{?D@2?h+oA6cXCwqV#l5PBs{*WQntJtY5Hgy<%_!vNFg z7S{KY>Sx271YxH@l&K$U;wqM(1?Faa{gIXtmR$cL;qIf0z#Z10_)}gy>b})|d_Kkw zlD0ayb%p>!bDAJa4g~-<5q`nx=y&w|{$oLIaFY~Ge#JW-dG{}+kJl?1Hw&r z={Y^p5PHA80B}QLrTlV7(dqZg!avn1kP@J-I)#K9^S{NttlBEvk_t)v$J2X(V_*gd z%17fq30@)1NPlq`R~TvupT0))mzNbHPT)>yApIf#+{xBM*6cxZUAt;pO8!XMQ{dCwtDw7uX)Z-z)G> zRoEXp1@w?-{u3*Z`KQm(ZG%S$=x17)+wzkRZhPlm|>C19s~%mNPG zfpS2n@_9sYId3|!0#~F}yIZ`Pr2ld`n+h=W$H4pfcKm+s;u3&NDs@3eji&XFe_L3m zz$DUMoM(W+4X7WlYY5nhk^L2?-9N^Pgg_~AuU#8|+fq8<*FR7R{$o9V@B)LXO4LXH z{h3E{o*_T&`R6&p?GvVxSr5Vg{4J2Q{I|bPCBTs%@B4!Pqx=5k?2HXvbN~AYfYvce zJj2rDbiAM00p#iX{U`E+f4@={3IbH;V9sa_{A*t#1ftHDJO5n6SD;APvK8&WKl9HF z{E>AvEPva_vU10>&S;GP`9RnQD!#u@r2qi|;9~C`(!z~fZzS8etnuL|9@QJu}CCds{e2joJJw-Ie-BwFzF#YCy`2S1Wp+s8;Xv$Dg6~|G5%#4 zojCN~#=k?TU*xE2SE|J%+S(8mf8axp*gnLxNnz0$3rr>094+VABHezSyTisFd$ZG( zYb}j;vUEyrK9w7q8#!5z#qYugFMA>{@n?`+L26qzexog+#*=UNjz{5Gky6H$g}MI|@xrPb-*HyJgP!u_i`{U{jq_4heJI-zXq)KogtpSZM6aLQ`MA zWWqfpZo*a>e@q=eBz}X6M9CtY@aNb4nAvrKbNyFy&hE1=k8oW>yMlS1f#}U1orcW9+BK8xvS}f>`)ZQwhMy_g zp{-i?deunY0FPLN6?UJcV9?%bfr`f=lDGQcdnKU5aLu_#Daz@-eQ0O$nXG$LHu~pN zmK(z4)z|%e8HdQsXc9=#;YwDSL>>5WsU@93fQ79P9w?Z@8R6V^55Xrkv<5$8_NmBy=+lPTi zAJ%b~j)F{@LeemGb`vd|b=G|RrY58=Wid3_xd-+p;jzVn(MJwX3uZpOjcU8Tu8G}# zU55SI5Ml=t(@e5BQ)U{+Q4oQa)V%Lrn-iXPi&YBI^>9L?gnm!=)op(4`Co&Td2 zv&tSP*d~b$%4l9~crMJ9Rp_n2C5@?lOB6L?+L{>_j+>e>V5-q3G0AW(lgK1AaRc)h ziQ8d730h)XLcC(sV19vLS%8!~*K2pNX9P_cepWH?8^TND#{;Q~`PPAG&u96GEgk*t zN4w#eGCF;}_mW4?gW7C%Oc%499AoWR5Qd#4ONQ$f^~_nK0zkr#iFF`qSbuCvJbC!$QmC|pXCZZ4w-RiGPSU+%{zy1uK z|60qwv0h2N%SgC>d8{E0J$q0h`_>gGZ5NI9=Qh#l8QBXismeLkyJ7R5wfS|6z~UGv zlrUf^7$<3t1U@`kCi$pG6o4CW6;$1q#^OD+#A2DyV0)b9R46)A$|TZXKF_nP+wT6u zQ;d`xR3`_S;hzt(p_+wyL(m+r6bVBQwH zjZDB!-9}qiEJd(U#W)VvV_L9o4eTl&medetH`@>eVTW$+f*ud1VDYi|XPdsyXm(;_-7xpFYe@EQ4z5S2 z*$=1qE?3u~pT4r)t?IK{_6Zhv!m_dx+}6iwQyq!e9=@ zW8;2FM)o?#M@h=FsJXp6KYEQO$k4k^Q;8Z`7>c(_#cVu3)&S~!e@k+R_{#lSg9(a? z!d0u1_k1NIuFknVw|-V`_oY0xdy2|Dlw9k3Der(0p;H$Nmulq&`}_To_C;7LaOx@G z7DLw+R?3-r6CQ2|rY7t9d1_VortOW1?O9FU2B}_xHz=OAflspbsmt}@rdm|Gz0>H- za3zEY_DK^>!AdQuF&R&G)ss8j;Sm|xrM;O&n7QmhDeOTrqAIM-OI<^&k=tFn&T^QHM#qa=4%rZh()mL%MEQ=8|*MHHR-$*MZBI}<8_&bKbSm>!}) zo11Qu1xI~k%`Sp}xjEhLsIE;Qv+Y_2fU3>KT37G8?e)<`053|)Z(lm&B_ko6A|4iY z+HHBHQEnGFd7?f5C(jwDryE=WoRq0H>$PQjm$j%Pb*Tmzv{O+U?r^gG)&vV z?@GWOE8-udh1Rh}BcwC?iv<-Tb8l`3Z_Jf=oOh zcK4Q+RhU-)V))}J0)0>T3<0R*7g~NseCF^NAjTsxtV8#n9y?Nb+pg+ zyXOO<@SeK&Eqf&Sk%#3~KpqllnZxoRrSSzfrT8YA)Wq0w%Ok5udF3>JB$lOn0_-e_ zO-TcTRz>GZJ7Q#Cwla}kW4ICv2ss+lNp-Atc^!te*!PY`fr}5kicZZ9t{J+#+Warr$Llv3_wa8FpwjV3`;nlV0zHk z9n?aa?Vyw4qrh`(AZp2Gl;I7^YuU3s(Qi}zAhYX{x7ghPC5GT+(8c~T1l2b0X#YnH zJqro=nnKTJ|2)=oiFrSPzXe{a?C7$Zgq{6Rz_8%qa+gv=%Av(k*jUN3Lm)s+S-cVeB8W)-9c@L z+`6Q?%o2@Q(iMfGsD4!$5BKgf-|QK(RR}Wm;OeBPN{?^Oh)q<#6ZD1*LPLL2j|Kg% z$By1?s^5KbFhgvz=Bu{W7Qt$q7wNU9aF!5+Ae8Jx9Vi4))bWT}nF8W%juHjljl8}p z)SH>+dlx$&j7V*D-^m(r%t0sIbs#3%YIE-kB=jwPS3X__22J%LN|;*wJlj~+5ByT% z_`~(=0)+H{T@B;?NhK?@{-A2|J(Hu_&FiCq54L;DzBunS+gFdl7UwK|*-z&HRBpmT zC;wi=w&aEIP|c4O*42D@wJzwrv+b1+UJ)%bMGn5D&g57He11v~$Kn-u#T#0JyHKow zk|L4gl;qliql@ijrh*yoaxWHNI)xkr(8im({zY(eMI@}P>2ntBqB5=8Vwdo+^Y(QU zlAJ@XgZU3r;Q<)t9ECrjh z1Qym!hvib7^RxS`?HJnvvxG z_=Q4v^4|Ox>YG+j8unW0dG&72rwgB<+&)$i7*rTIO8ZXcAt6~3Ml*R!+2Os zbfZn%uO`vvbU2_mvcKULw#ZH+fLQ#{Dwpi>-IsT!?BL8H!eUy$Zt~5$8Eyijk<((+ z?ql1t>xZEo4OYcR?-iK+$#EQoBK;$H-@Z3N_qp+H!SjsD{PZ_gEY>GG+NKrfUv0pX zmH3H@rNm7coMaLnLx5_R{43Y0v?_PWm(FZfT*Yh=d=3&;qyv;Di%Z{6izHd4-cx-C z)F(^35U{-4NQ4emaMjSZ$QKP-I9yIc+s?IM?iyG0XE$b(^ZFbU6tJs{_HikcqPuFk zaH4D1q=YFRPrJOAI8w}-;;NzmvhVUhokj8cQq%fZgMC@-yw^bz?1UgK${)c+wMZhC zB@NM)&7aG8a*q6faOLd)t+wPO7p0j$GMRy6M}_j}DCM@1Y}3v%86|`-W7!t@C21fQ1`KO3JLRmps3E6k0;)aq7TO7(!+?YZ`4ieWn@FK|Vcw*TpSTGGfm#RPELUQqe960^|3lsF$Fx?4R>$VL7heKUm19QY`dyzR0Bfa2)>uz`$+aE?pntK?p&fM@rbTypJ7hu8QOmV)HI$<4YY4 z)nhf%%@>>O^e+|<8ajy8L{vTMVzfa$J+JqCET0x)RD5k+op5Iu%WbcNl26z}1*{8q0lkx+VO;l})zJ5W`_6O6C#UvcdRi{ot~hkEg%njXmj zSOU4!#(8dsx{prR$5Y~5htV;SdCFxtu~EsWE(lpoez;UQQH+^krBDcKC#_C@Ia|Bv z4hAJj9Yrjev<;a?b6Tj56S3wIe*Fx5z2pv=(*52#<&+<^wVYImbhGX*=li{(0(zU0 zkiwT^EyDV<>Ep6Kx|`|da7mx;sI`0+0BlkA@?hKUED{;Y0kpPO&7-cbl(x z0(m$2oX>jkDT{l1&~|w#x<0now3QUzg>5_TgrMsx+@#{K;yrik3ng5(EQGC1$vlnt zX6ljj{2s!ezYkh6yU+N2>$57Id%%N)a%*VVvokw?iEc_gEGKeXh_*AhWz^1Q(jutg z(0JR__lXCvh%CTYC}*m&KrG-Sr+NRW7dS9$?ixQ1%bRm+m=lY5+d&sioSNf_@`==i zzo_M1FAJmU6q#eC<~GMNcI9Ae5=x}wZQc~3VSPX8*}x~yZ+wMW8QEZ)=#`?igr(WVfkuqL6LY{UPS0wPhy46UN1|J4FU{`=Pf+HlCku%_H7J9)43Z2 zpd{rJFud+hU^pHU{jeSHVw?OPZY;UXRyMte?i|%|PVQmS5_t9s-&=0s1FDx+B*+VR4KXiximQ!zv z>7A$}i8%rjqC|hXYQ6_Cl$n@kRY%pCu7W6h5dp_;3#J zRgLAsg1so`;lmoX`|G$c)$aFNziTA3c&=$FMjN2x?^ixgbm!)_NRcs<1u0RKuJ&{3 z?&%(uhq+zE$+0>gDwGN&5+V=e(Ie?ekpgK=Or+SC8CQ0j|y5;L#C*{=C9(6>;K zXrUrSZ@I7k&LLAJEEm9Y`Uo^^ra4rYw%_P1Kj+P(qS*uL)Iu#2^KjOl<}%)9Y_}X# zHIA$Y-V-~(+n0*SgW3KfmW$ML9#-~^nH|6VUff)NcZAUz#zE1_#m!3^)AvLf0v4?} zldK}XS?OAztuC=G?hw6L(%1AuyxTV%j;PBvmZ*^%>Pi3Ud{*J2P!oE6c%MaR870-) zG8?j!7P?cDP~so7`it2-)s9#UojM0NMao&kt3*;QDzVJ7=7dhS+zd(OKHPU^oX zX6p~bbmZt;N=C*=oV3s=#G);Ar4?4(z>TYZj0IrcxD%+u4h@30P{gu2?WQ*nGux zue5gOH1hJ5W0p^eRy3PF|$77$5}?kuMtb7Y%7vSe#ws9z;ETZvkf;Kf6f_36VN@@!5r&a>nxe zkn@nyMl_dM&9E$n$fF2_Rpfl-o?#nsAz%8BLOb9G7zy$pgFdIn`wz%7L5XriQ&K z-612(p36S(fueuwzW9n|HSq2Q1P{|~)Z?|)HxU{^uO%nu0dV8xT%Gaaxvgz2Iz!eq z*5_;VRYN^kg76I?Mypc&pexK=Kk^Oy>ggOBs$kGdQ{yO8Q-s+)k{!3(Qyb!2k3LJb z5fM|I-rp9S&n`$F30>E8WosS8jy@H6UqB72m%+*Y-E;tY+J7o6q~Gxljz_ctU}cn7 zYdwt*O^GmbZW;#}#z-RJDM_$Y%=lK>y^lx4MEpQ*IOcvInl=kP+ncv>qMh*T3_+DKdp8{p z;~e*hO$M;mv*cZc{?gg*ba30^z6}<}vfD#}3&y}FM58l}7*aP%PDizC!+57gJj5C| zs%CB$cnd~~Z=-_;M`tEG+@iM^VJeqc;u3>v5n1Nu&oC^?PuexlBdi{n_2nm(VV86U zw}8@l3AM!H_$^Tj6i_K`Y2np2K_%xY&?OVbU@tV4;< z*4b^bOrg6#6P-_zCp>^1zs z)Ez+x(tAJm;z%SViY;9Cat2Gd*BTJ#jW=&o4esmN>)#Zt>oMKipvP=Icslgx<^D3N zONrp3#1?_W#0t=_ABq5Ahv+U%I9r~_x~mj+oJ*`|mE`k?u5=h^h-!^lkA zHyNR8z7M1km}~D5lo4ttY;!T)pV;PW57gE8<1lJ*g?%P%f)S)oc5e9fWjaVDfApA2AK<4zqmJ^K#Cuo+{W^Muqu6++GKA-q3s z(d$p>9jND49ulQc{hc90lux91f#fX-q4&_1WG_?Dt;*qY%gClCUUW#B)a%CiLF;tW z$~lP<_w{~{wE}KC5fn_x=W)-T&4*iA;p;Z+%KpKf7c|`-H)s>OKYg{=mQ@eqFzkt# zT|TYd2jdoKfyVDY)^xq-3sa*y=2y4Q<6p@i{HlK^~_8!7;G_odWJk z<}A31gpL$UQ$f~R6hr~uMx6MV$nN_yk?r_Ff*BoJ+vzS z`h%LL5x8aLA1aJBle@LtAlwZ?1z2&cJ*f`s9Y9NmC3v;^nf(^_bbueB!`U@$K3_5udq47UG2~H9t$~Cq7<8O+XQ=nJJdY+KQ zYj{3c1q`YP2(Aq&{l{2gCnBSLC!~kg zpGlAZg1sNb2}BZr&Mm?(y4|vP2D%HMd^eTTtBU-Eluwu>9`!jz>Kzhr3;%wAw+;}r z>Yvh+TjgCP|2_gQ*d=_I3)Hv*PdA>Fhd0=)|9&M0(;-4c?G#XaU630{F6HEu01c@aBXmG;v>`N=^9u?Ya4 zlX1QlKM6>3ULLPr<0N1S&rc#lmhgWS8HBGAXpv`j2|blauxELOHF|npMGJq6b~K=* z5h!3(b_9^?{NMW!uO1`$Bl6M!Fx|J&oec^#Rvw(z@{zg4cV`63k{8>~VSxPyOlxh7DPC}**t-R%NORh! z?-^=Uu=m~4k2D0hWpEEdbG!QC6^^Klo0O0!8ew)HSi$8#PMDTCl(3;O9^bixmoElD zs-$IU-x70xqh?8w5ltO3}svqh;io#d`gLutvii9~_IS%oQl7Bte86&D96k!hk|1y=X z#}jPfj|UgZo3~5=Z9X4YR&HgaXj{spY^uMP zO&4<$=*A0h-9DsfnW0(K9O?y)r1B`q2z>n%cj89KhNNw1eC>|&yAS^(Cd*22fM(QvP@g7a`8TZtN6}p}PGM9oy6RU!QwdVf$-9C4dc{{An`^eRJca6pV zUSM5JUR6~*ly`Y$eiX1=)W8EMXRvjdsQz@5?##%g$dz_hl_-Fg9(gWR8LVa16}rgp z?!;1nzNrvN9Vk&7d$cJ$Y~NP8qJ6N{XBuO6{;>hU`an)~Bq3wY2qKQ^HPN!+ zbnfs-4y(&B`w=A*3^9pd?~zaGGe53 zIy*XGF2Q#B@I9dE7+C-sz3XyVVq&%dWx);J%AItvN&xnXLLTmQKHiS^II53vJ4#Zn zfi%0#pazuQx?=c(Q5iR<;kRmZB~synnEi{r_2}GBue|3xWU(m0I8&ThG$x5I?x}|ErGoz8wh2$Rnz)YrAPa9BE0DBaTNe)K(j8nT;di*I5gW%3 zRMa)k{?IV?srIH*An_rT6Q5d3K4Rpz0*Kw$AnC_#kp(7^ESUxgyzJ}6W*;xkxvfrV zg@Ng|L^__KX0JA`0)nqz-s@`U+ATsi>HSyVeJyP+eB2bAPUqqgvdk(g7dx1a_blV( zf03tW92y!F?og#VUteCV8V$>hIFxr7c$2u4q-|_=BQ<;(?h%PW1(iG#fj`{9>SLuG z^yAU?@7ZH*L;4)27YF$en^KFJsQaQ;nHJUBdT#1iBY5`IrRms`SFQ#NR!dWwyyI?5 z17DMRdUkcJ%lpZci*CnA6~I3xtHfJt6euY3RJMEb#BP;HltvFMJM~KGLqE8D8YLv5 z%kvbl9dg4e^Q5o8yC8Wbt-@(;z7pj)5Dsl-U~ z(N6IE`zsv2hyJY{5umS4-3*p99|#)PwGgl4BKa2!9A}flpFJWuE_GIA+y6!S9mmS=5C3%{$#n6SgX~18^Qjf zdEmluJ?ckmE@r1E+BD9MPsFJ3T-t%wfoiWZn;%#&iq&d16h=N zFPem0$2u>vY-{ga7kUc8fYphH-LQqh3y#VyE53+&=BTv^#t$GPDNX>u9=R4`_V@`@ zzvTy)L&5Nh!!d;t6&FC3azTj#avNwA%0J_rUNV;hbvU9{5oxfZKpk8zXzjNwU2MDh zsNYhQcXvD|_DSEKyc3Q%(mq@TAxBmRY&w9Z^GX9|ml-}&wggd+m9d7U72|H1Cv}@W zb_4t-2iD3G1u9|m8OcIF?l><!#_! zwVd+@UsY#o=mUxz6Kam{9&9bub`s$m^?G9zVl9dt$@(6fn_p^Q6kAw?CV$}s^dC?= zLo`#}!iG=x-7uTOGrFPTJ;u9&2Yb>>Wnb2tEEKz+N&=+%2B5a>iRX+>E+o4s-f7CW zKEMgr6D#3gt`g?S2)qvx!kyxekCWD2ic2RY)en|DR1*iFqW%i>@okYlJ7PmCYh_+d z0Gt8B$8Ik+d+y|CGnfVL;LLBXeWu&1+M2&rVd$jL4U+21>9A`zm_N!Zxq2Q_vD7up z_S1bG*}b86>HNjP))xn9fF!rlBQkX_Asp>TUA_KuQux3ko3|IoIEx86bt+kZNc~|~ z)!GzAA%$su()u2W+S@UwWnC^E7`(Zb{bf)(TyMsm(#$tL8Tyu3OPS-%CUTLynbT`f z!lHCv{(J#Bz*)0BpBdz*uPm3r9#NBA?04K)O=0vqxBD`=G`vS25m`XhzWI)M->b}S z$Eu6RD!$4|uwg@da4}rvwJpiLqHhacFyeD&-D>){F$9!0+F9H*z*y`9twk{$)zQwncF&^sWYYZ+yABcZUwal)*LV z(OlH}MQ^rz8$d`8<`}oTCQ*E>L798$_M1g@1*k9VMDx9=_n|YLiCNJj*$dRyB!%Ue zoV8nnaH;US($#Cu+%Y7SQ4Oz8x6_8fThCT3k%S+8^^LbZPeny6V<$~?2-Un?3&=ri z*Js3>#eY1{S0<&!aT`m`WpD3z;&_pR0nvtsy(LyXMp4onl)8>BH`Yh%-ph&b|GeGL zVnDtabM7es-s*HL{-Ab9&RA~!ULHOM6WxfU)K@VxUh5uMj^uAGyCIGi0?P;C%x@U> zY{Y4V**+{ez@}p)?*)pLSM*uFkJV#@#J2^E`6Lv-rVZo$sXm@T@g%lRa&4TnRVZ4U zJ&&YXG1{bcS*)gV`|GXv05~oIcBHZeAqAt-fzE)~%zGb#@xp84MhLjMKdbCz-7cADDXvLS%tBSoKrM+t>#vP?p~gNSfp#$4o^TirNa|wxr&6TVR-UUFKU74R2_k>+VaE{4>Vlp3I?JBEI|m5d?XZaeQY?c zexUg32j8sLFxa0U^xc4m=P6KdS~tm9O=6!y_A5*bKIf za$eh3l*v8epPvW^pzSR#FjJIRQbDXQh_?ID$aAefk4)>Ksk~nuIImEIdd&6m)0=I+ z8y1sfCJ!n)M`JjXod$=ZC`Tu!+?hM;1}<-W`-8~JIQ-AY1a>ou|>@0 zL<#xjsy{TL7c7`KEOvPbmE77leBEy+E>B(SCKz*c@<&oQ@~M_PkL=gPcx(|$l2%i4 zn1k%%6Ph|T-2g5@&P_JMwK2H~r5v@^E|VfvGbAkRto9S*U?m~jj;hK{ zU6)IMg-;V}Jc+MXys);&C332dQ&8c!e!3J^0J9W;pFje8pMg)Pr`Hx)hSCC^d%QC) z&}8>bdNl2PCkT!$1R*s@N{3PJ+;VoVX~IiAv54f|&61J9=0Xe5B$z!Hp^gon)# zn%@u<;uhIuOUb(R>H|GA+YdX`naj7GoUKKQ2TQENEqcC^Lqf!m_Dh<-4j7mF)2e1| zR|uB2n)?+Ice7RR0Zv9|Jgt?aqMyWTlGq!hs^YvJik^SoD+eTx*CL%#4oS41-1T0M z@8)DB!1c%|;;}Gm>dklf%>D^Jvp+J!S-;@Sb+&+ReuNbY$gYFai0~SG*Xg0|f}{0D zg;gctvqHGiD3&K2Hkxz0T5P#AOJwgyUC>lkdiqrz{-e)zSGtOU#DH}{lE{4FyU&j|*>Uc3)7M+&ps6j`rn>EsF%+hu?Z9VX(T059K36Oy zP-1Jc0K;ran`~Mp)63qe3AH6WI5cf!3%6B%T%`S;0>M-HF7&MUSpZ`}a{rNFbMxF; zyNdU0t2Y9B8!w)hgsgqHpze0QL6^dN6>vlt2J9@Zm-G$-nE{RFpzw&YfbZsyvkSFr z0RPAlk7}JEt&~{+V$ZuHAbnV{ND+%MlF;Mo+)5JN2+J`(y)Bk%*-d`{(}aKUD73W0 z^ER|QJHKZ6E(|wd>m|HE;ld0udTDW`As0r=0j?O|y zrWj+E{Kq`DxDa~N=1p#{^D7xc`R~#l^tzdLZ7-35~ z#cKaM>c_;{Ys5HZgGI^qhFT!M*t3$N0Txltka({|%`NBsqC4Zv-Eie>)nJ(IfKpFH zQkZtF@z26!oRFH!4KO=GwYR6g>(Umd#l5x&Jc$2h;Eu$^i$e+-nyzz;hW^qusm#%< zql#LV3~1VYU~?`CfWD6DQSdEN^taJ&{z=gh)!@1rdKi>;8jvq!l6V}Lt8-#+)LbKleu=aFWZ7AfN*3_CDg6$gce?|=_eWBiyT9x^go)oyBxK&tQhT@>mwZRr4Fn~OX0QNXX z0VAH z)4lH3AsgcQ9%aVVTwr*ZN8K9Fl2otw_CfR56F>n=0J(LNeX9JyTYC*3FZxEHPO09Tdig!|s!sMMJx z)iEux$K~J1qbx{)T&$@eTF)vWT+gw_7wC%_Zxx=cxK{A>#eBr+tpR@eH%%`_fu4Hx z=+o&_XB?HDd@x3-SW2;c@z}zcd?nm|ECbz{LKj{r1&@!YvGcV6-d2m-BQWvEP{q zye?z{JM85B^DbkirBl(iax0Y9y4r#JGLrRcygi$$^`ZZg8c@eM7qlJ#>ILDlE}ZEL z&Os(KxBSCy4+eXWyU^Vm5Fw+^)n&`?QA*sl$vb+A+F7$KsIL|l95~pPYMo7JVjF1&YZ^QG&&Q^)}E;?qH zl_%DOOrBN;0Mbmkf@jU#b<9R>@TAlnuBB9fGOtBrz<@_m11JZM<8eTl$@n`w+}zy?glrN^>iOAPBK_E@GK&*3y@C4jy(sNa zlWi)PKh24$yg{~wwp9kv2F@tOtlsZKa2wr`h1ZL(Y^Q_uHqSvqW@Rc0yD?6F zS^eJbG(bN<+fWG*YV)B#ubsDl_gJU>6!wc+n?hJa=Jo3hP}L^zIO?|J>UoRRJi}$p z=Zj>A!r30i-*PirfbEiOm`=FKr@i+afI>>2=D|h@XDsNk!->YvHW#eAEIhUcNyFb+ zS!y!Q*tw)+Wlh-U19;m#Si>4HvTN4VIAQgN!z6OBTHs8 zb^#pOWOOtU17I3_ntFD514A8AL7I<@6fv8!m}6rU5rmm>0x#!Y?M>iY#FePzPQX3q zTlBL^q{!5_&l$0n-`|b2R4jX%?P*V|zBre~`P(*Ta4bsy0B~VI$JPbS(hxDbm+nr5 zn+wvKck4bCbZCvN;x1t;2t)lc*qFDuT5ik?2ddA^1DtwB%VbaP`Etx=m*P9my=ozc9vJkfOdF~R%MPGRCdPU2?)E%KB$>JU_V^4rEM8OUyZevJS?o;rTbBm9i^S? z?Winx6inQGc75NyK#(IAC0b-&P`KMpPGgFW_?d8%K`c1iG>JA`c&-1sTCWVZW0LgT zo|ASxfOTSGmRY*Zl?N3N<~lY*MJ@532eTJ`ND&f%T0X+cwzzQX_b5?4Lj&{qz101v z+e#T1t`Hv$xBv3a=ZH|?(X@NP)8)*>K*iw>u0ha!RkZN zA$NhJV56e=?;O!L0fu3X?HFgz68i&Z*V|+TI^jn}QW#Uw4{s1C={T?9%>!T))T#Hc zB)}VUTQ#9(2V%GVaEwq?+%cD%9PYTV!6Ls)5>4Ul2w>}%VAVnU`mB_b zm|A_HC=oy5u&y5u{+QkVE5`}*qr3(5I$}nBwj4CYwuXHlpDDVt#25}}wglec`9y|p z`ygE$NfLS%2}J@9?|&5}d^&MivHO!2O}_IVt@p4$Op*R~h|g=y7n8xt|1h1nbUa67 z$87r_G~0hf4*041kI13vKO%=ek=TC?04K!!e?$)dmxvtxqYwL!KI}wPh5U~`>{w*^ zzo-xUpAl&N&sjs|`b+;exr_m=4~U9q|Jb@}j~?lzezpn4sVe0cAJI()(%M8nDan?ouZXCwDzWhHgGc=ZiHjL&i z%-ws1xkw-u8K=sNOT2plGg4Jv>nqBTI~1E7=|=ckn>e3A2m_Gy=3VKb=0H#B@k>Dipx3fpK*YQF()r(+^byot&SwI!*BlJ{0gg z!nv&VJ)?V^P^9IaCmS4#vp(>MmaIeR9$A~T?6LN>-CdNAF8crh|H9(R9%@+k&2FAz=Ds%2WG^_hm5t4Q>yF=R4P%0zq_}^}CR|Xu;>EcD$dIorxFP8$DW+bPY zr$F_=&FkW5CqL@PwqFU+hHrVsEs$)|ej|!JK(8cFmCoHCwP{Au3Z>d)=J@IvXK3J5nTI zi8v)SiVsqQI5Vmr#mqYZmFG_WYe|IpeFfnT#90&7OPPg^cW zWM8nqZ9q=7Gc}PCRYi*g^!Mjvfu0|Xfdcoe+Oc9BFBkn*D|YqU3z$HZ+=>SH7WXse z?PzTr?q`+_KFaSteLegu-xG%_?9@XW5$+g1^Ge5GJ?<&=$C7ev+@EFdo~G(*fVdBC zZVIbX>9TG4BiUd@7XvO%|inR1S$sF-3-p9##$a!S7 zKt&CAZa?7EDWeeDo#aqN34x&Z%9<3TSMt}#M|3p>@huuFDxWSz%z$5#7I8Miajv1tNRUBpR#~$0q9Upc6I)#z za?uC&fO^Lg^L)L-_x)^zNx`NpUg5mPg7-3`#k=QS0PKq*yDkEZdvwTPqrN=2bUEpH zNvQ^Y>QaRP3&jKD^L=I`Ci@T7ZA^?fltIPUu{%o$4QMWvG=HoKV6>;60~l1=75^Ns zPRBzWM>vQBls78-fF=q)iW07NQx3pE-~#NRHK~MiBbOPMdt%a+^qNCz-Y*YYTAHwV zn{ZL0C+6V0Q^NEL{yUOl>;AT;MNDH~g9hR(JpiZF;lkfXnGq^Rc$@P~B}eizYqqG^ zef>|ufz7XRZy8w$=s_o6`Q2fY0Y0qoszMC{X8UFEcKIe=-1*`h#AHNJpT@MnOj&1e z8}qy*jSKfj7&+kL-Jx~+Y(AXHcA#dzz4cmvUI_$F)76JF+6EhK!*aYNkkAg z%64~%QP{1{Hxw-3mg($p2PIQdaJsQ;OHSJ~>Ut4G5@KKZ3*!?gKo2^H;z$=bw(i5C zfNlnh?opQq&*Dnr_lL!o#ftDZ69=AH;QjufyM-UDhQ{Y&PGQS53v4gy?#`#wrehFA zCQL+e1S6jV27CXiFxBP5V?UD$z}AC5{K7?s7i@^=n=5=?J>Q^?`@8I)xUFd5FKt)Y zmt0K(_D(i!rN>PlEFlf|bF}8I9RdJouHDPV9FPXge1|~kn%J>(kuXl|2IF>lK=TSi zSGwG?9H;gaw~$(VPl2S@%X(TqC*zu8eyqw5h%ffNK_@4fcRR;$3bnt(w8^D}N!TJP`*_j=~NNCyEjHBaBl+a}^5b770Aw|JUAkMm4o{?Mes`1R;u41)?++=_*JKBBFv6 zQ4y(91q7sblAsjnNRu8B1*HV3QbLtp1*A8n_ufLu-3gv^)cbw!9pj8~|GoK#lasbd|7)`?W#)Yd%aw<2Bg1cO^CX1VBK-_krPcg)n{y zNH_vQ(f_rf$4#BDdyaT+YkDi;<)Ku%C*|MlkS)5cl}vR38;O8piq9b7?e2>XYiZ{R zjRRA7C8TylEI)8rUM+!x1KL}(Mcm($ZlkP~{BAk3cQ104XlpWlJwEt+?EsH1Cpy=9lE`miInAfHl1dc^vu}Y4Nu^h9g&0Zwr1b4 z+Bg;I1pR}(wYK<~Sl6j(37;6C@Z6ZNIo|0Scf2sjd{!}SYX=IM4GN?tYOks=__hM< z;p3Q^=7P9P!z&}t?&sqC72~Ya-^Yqdr(F`jZoJ$X?ZhbhIA>8UINg=VtuF5tV%GqQ zs7Vrod(ycsh=`_|b0Z!1=LsyK0r^@b+hpP2+Tl~`WBk{4!Dc=yS-{?>N7HH%u- zPqMBSD%w6Bm&3fa%Q_wRr7w*gkT1gx;)$SXdevw0d5E>+4~HHixmWijutnZ};b=JG zwL$lYk%fpI%}1ap7vok*Tzdmdu?_T`E(*>-TBvLLLeqGsSjNXWO3z)Q&t7_s{OyPV zBrdbupIK^|$?3esyl3$ahp)V8o8v%SXURgC_M)Kx*4lNGHSF+3fcnz7^Sewv4)fs; z9DqhX6$Z)H<|Upduen?UI|=a_ad%t2?70Xmk4?|WOYYO(cTfV(d}&^7x;5JHf+xvy zIB%)9S|M(a5ru9DJ6s(PydWRgUmf<)rHHGgvsy*OZccrAJr_Z|XPS6)HyYeg|wP@;tsE2*<0h>*`<7D%qTd~D9eshr&9-Ol{wk^}} zPWu8S>IGczF8z$WEcm*=J(vEy;=5!iHx*pNf|2*$aV*~j-Nbm=ho11iH$ekB^Bq*w zrC7wdw1${8v0m)!!(!tShGP1bN-<7rN-idoB5kZ&d%|l^-yrqtD_mW^FCijGHaz5$ zUNE_R(Hl<)k;crrr0$G@!KDV!j(2zL!n4+kB;1wN+I*1c{Y1_kO0V2D>~;RK^^cq3 z$C{;f>W6ZGRgAq{c9VJa)AT(w8JY7JNo0+&S6P6o=#fW2HDs@tp^_f^Hmr%Kw|gwu z8%a`!9xe~7N^r0L9iUt`He})5X>hmfZKv zaiRhmS>x0svRwIB84lkifs#`I*6TF?-nIiJF{!+l9LE;kWbG!bq1@TLtws4bx}>At(H4oU0D~th<8IW#Zn~=9ncV@dlveM`-2{PZVX6Un(z1e2r%$Y|xM8Nb zdd-UHyQ89_7|LJomQ_1yH9gRJ%59OkBc*9F-XT1Z{$X|Uh0{220#78fh{gj(vO0rabELbBg+R2*kBvXS{mJP?napzVz3}_ccvEaLqeY zsg&c&v>oa?i*S1}9GioWx`n1PI?tHmbxc}f<}i&OBPL^)$9o1`NNop0(Qx z=60B*J1w0gd?%}9O7rRjI-&X8#w4wsDXt65Ho)rsvf0cGaeIAYKsoBTB29som0Nqw zo1~OylS_4tY!Xk63X67Edd6P|hsx)SE`Vz)}HchrZgO38M1Q=Z%k@g()}u{Tq@+ZeUHx zR)uF*;w+j-w+1g@-Q3DA@)wWa-syCSo_sV6+yThhChbtZPaA?AnUr~@7sEp_8LJAH zsFiZt%o}Kpzp7Z9k)vxRlfYcqq-^_bf^j^GPncs(dFNw;v}N|*^n~r3N?>&w|J;E- zRf*0gd|z7CSe$BD3&A%%TH4lzDIQn^LCzhU65Q+D5^w@mGe&=K0CM zl^*$74R<rGz0|hTDzuU!vNRG?56aJ}k$NSx90_G)-%Z!{z@7B=)PAcwHp>lP z!QkoEz_kp!iAe)lu_9pL!;0wm*vF#o@{}N9u9>qQUPvLxt9}1Ux5=;r;Jm!*f~sO& zhXT9us-uRs82M@ck|h^7jpX=~k|4Z1DH0#sL+eA$d{G5I=W=y<^zB03nOWRK6e|~| zY&mUxVW7v#v#Pz2sc0GBaPl`&UMW#&HFyHU?OKGycWH^=j92xC_W^LlCmEk3|w z0p4TnySensz*tOoEsq7ts%CP(-W&riZCCF~Ujum= zH*3=-!UXZjS)E_n(s%1iq$t<8w*67%8SPtIR#?SgsTJxN8HDFBH@3O%EDck;jD0*R zS6N2Z)2Tw*F$p2LoVK@#tX|mrq`-Qij&e{ese$HM~bE*b;0ZT_8DoeJe zYb0Ikbs3a?gAwU{e*Zfai7b8lRZfEAgL{Kt(L*I&H`tpc7I99g^@`T}DvlDHvsMxr zpH^*c@e{)%jE`PA4(HP?DT~JEW0>PcB^+(W7}br}MiNb@o|9CT?qoXRF=b|WU+B=2 zp3EN6v6FipR_vQ!;7f3?~%~i;&xdmr+6Kq$JuSY4hz+`p$W0`*NiGS@uI~m9;O|+LZVHzS z0e6A%#98O(CLrc3C5-krN?+h{r>w&bmBp}~#rC8?OB&3r61zqvnD;;2_BT~sw!az$ z-BW_Ac4QiJfB}F;-8;(33;SFbh$~kfNW)trE~$BLx-D6UF1E9k)jddlzCN=)jOT9H zr0{jpRR}rF-8MXqZA7aGWtHx)I40@CRi9*E_wXNmOVQR4L^&GFuG)%(Tg7rQJ^i3Z$U`Z}$ud?^dIhmW5s+M+Hlula$aQO9J^3 zTiem(lonZAhC8;lyJHRu8oknmr$pA}}q~685Nea-ho10AF$0Vo`0|#o<)`;tHun4+|&1L<3-)>yCf_0Mp8&KnHLn7kW8*WA z_1$70tX1J@bUkG?gUMOK&xswWG($1_nF8+6F^on(OZuWc7CZ1X&a!SZ`I>7&-m-5P= zHL=@8+t+^EYC5(h9nI@xY*Ol?^LVJBhd!%#%8@1}rR|q695{BXQ^- z)iAB^G81`j^fG}5rcdn^%5P~fU;$ET{%+^*TBQruAI6H_x~Fbu0m8hoO^wJ4HqZch zjp{$wQ^IwWnV@YI*gQky?f1eM)Yx@OHa1smH%6=?wq&+@`3IceE;a3M4H^(INwH1( zBCu~PS;uqTCbi+1=!n&n#O?`dh_cj}d(qyM}qr0m?r zfcC2RQ%#`=UMG}{;B_vrxaa9m9CU8=YfbV^3vVXTjL3Up6Sj)03uA+Vp_IU@_WV~xxn6o>haM5Zt zoOQw7RIdsE(G%Zgtfft5Zpm&T?5ZR-Ip`$P)9=nvLN0K%*bwRG1%c2~q<;9ugRnS! z9wPWh%a%-}je;JRI8y)3hg28F&F%ey=;#(6_Pw=lTMH&*p2ev6Engc)&(^`6Rcune zObj%`6q9fzs*`r7Nas?Cabm{7TKK#>12BcD*9JG+n^x_P91gc_=GQ(U(@9eJTZtP&j^^#w_PFda`)Q@4(|s~a88|mAH{RJc zuT8puK9b-unV}y*fT_b_Z$J%Hkdqh0rW2z~n}w6Utk+o>S5YLHeX7pebczD*SQyfZ zV!QHieJRF6xmY>TV?l1$Oxb$G?tRzRf@NWk2K{=Z_vj`2XNX-&a-14Ud}y^Iz_xK> ztAEE93|<;`ilRk z%+YD8rO*|XQj@u(o|lxq9zS&*3^!hAHak#i#*oU^xT<9QL$c<2`m=gR`Y(8 zxV)5VXUO}60x?1w65%~Lb^?*NLOcFrt*u_l0X;xY3gR1a>baBC5K^kJ^>pVz!DtUl z+t{p`Ys9m*;^(#~t$Ydnx?57`0d8^2A-pu?YL^D}o>|C{W2B;hIW5`dUdx%6+x2oF z5T0g*l_+I5Py;o@hnAHyM9(f_f4dZ2Buy!}(u6VoF>YI_Nrqs*AHZ<@B^Ae7o+DNntZN}xzlbTOzuXlVn zO;Twmnslxyh{=3z_eZ*!?T)2NU+cQy+9#yeq7zCdOYg@~$Q3Qb{i2omecL)0n<~V_ zdqgR$e`f^eo8EN8!UYq6I|U{M=`gt8k!uo&;km@-Y~ppv^DAe1=I4w(+kvH~#-@<= z`>Rj!?%bfEHz=W>x?*-`Gou&jKMtJ$9G(0a`nQC>RPL!9gbg%Y( z5!m~7e@7XvO7BSHPo&z%QAXro*BM-Y1y|h?x5R<-!YfYcgiH4w_p2jap3ji6M+)2v zBd6Vc&2#dA)mfn&km5^MG^ydhG2M-x`iV zTw{17mP)rr2lYc9@6HMDyDjDME``S}AwIQp(`zZiD|pF)^o@UhIrZs!f-gW7`w2lx zwG#4zf0S5D^EL&p-_=^8mhk-Mv|-X5ZzkiloF^g>ijX>Wpt$R0ryS|AiMzkJ;+ZKg zF-6#_f7-p*X0_d;uuiuDfvucGvtOzc!tTd}78c>k)~18mW5AX(n8p<@MWw6EARW{%Zn zH06n8s)(Jo-5fFQKD`vSmqvnOt46SlrZg>vX|cQBqBZ7O7}xV?)Xk~!NuQWsKmGnn z+pAoR3p~+dU(RJ>Q$58=N1!BWaY-4+S!=UvC7?+CvF+QNf$5Q0Tz}om=-}uOnYbT| z4v^INKm=NT`*TEcT>Z#~(0{J86;;6*^KdQRXZDBVT zuWAGWD&7?gF9vz_7YS&aIgvpiOwR$zI50GMFJSj7kja!UMOKu$S-(nqKbe)9r+N3? z+t=?(G;#UQMidJT6p1lF9wii-vJh-jRs;Opxd!;TR_WL5E9XW5Pc-e6;Q7E)42E{4Q!t@+%SdG5-5g85%L6n?CpP z+QOZ|t^SfJZ6QL;ECF<#Z-=oMyG?mGNUc*r<&qjR1b^xSz)MK?nm-AKDI#^~T{_^O z?S}+f83^-*<^_JJ^VMKroC*pBVlDuec8-Z&HJv8>PzB_*St76SX(%~r^7B%@ePPi9 z!cV%v5QzCgu<5=l<>A0Itv4r1r3rb4ustM@WpJ)R$MXr?aX1c5nX$6TB92Ul4hEZ! zWz^!)K@yUwCe&X4V>0!Bn7|_>vI~QA{h#j=W*8t&-8aSr+U|=3(--oc3_LmH7JmKm z<-(|`?DLu4!3MXjwZC*umMMUZoQU%W2FAs?J^9Rh2nvIZ(mw|nsOY1+EsH;Z0U84b zSC}Nq9%W9+WLVH0N{-io|I<-PPn7PDBLko`RwS)ji*?GXu7^7y8)U#R&O1q%M zvA@rk*BC1$Br{D@fMn(-TP9{hgZc)a$X(Zs+4FkJiDxr2snTt##Z0`(W@lX+ggF%I ztGok@I-seXgUrsj)-5Eq-{R^|F)8u! zhkqjsSs(_;_ty&KcRicwDGX8Cca6;hJ{W!eT+qhGhOm3X8)2tmcQf}xR#B`x310Mm5&qWe5}P2iY{7wXX_s3y6IfDK)m-a*b^^g*hXC@}%mkh^ z&G-;Q)A6d>=QjReLsDK9EpKf03BO(uf0b`_L$ou;-b|Z{FgHg)3%~&hg7Rxvd|`L^ zu$)36#*hXT0lE14irS<*uJ{=F(@Uzkgi^-~AcNZBPiGTLke7Nhpvh(36zA`SjH-h> zV^f(b`EsEgvFkEo>={-0DH-biWKm4{`C@RX&5) z#kLFaye3XOv_1zZLxYl*Z#Qiax+~+Fa8ZxHCrStv_0qa=4|8h3E3=;!0k@vJD>wTT z-T{Chv&95`$!Q`@Wzi*|;v(M)#l^0PepI}W9XGqj^x1bA8HO~}Y?tb3Zl|TTR=hZ0 zf{N!S>MiAXtOC?(J=GGUWA@{?(lq!74OQJT8lGB2_-9 z0DGe9PUFtHFC`!1b|4Zh_96Hd8b6ys$|(F!4^F|^l`6aa#cO>;F^kYN9GwoTOoiC% zErlP$1#Jhfv(;93>wGiX-3Bwbi>OU1@4!SSky{mT%Mhti3p!w{9BPE2cxUJqI6nEo zECG{L;~zfv!r@$J1`09jS?S@rSXYJ-RQ^lo!_UvcE%ljCl`4mG?pzLV)GS&h_z`A^X0YU@^98GrlP^W?L`T>ay zXZxBcf-;uo(SWvAAH$9p+?cGijsGdTPK6KY@Iz8vWz~`~8n89k5Ez_nwv@f$FmZ5c zgsP1p^mS6v`^j#r)6aDd6^)Ra@8Hru0IeU4#5)eK`)z6sxEJ&Ljw@RK%Y{g4$_?d0(hHL=OT}@JH?_w(sH#sAzT! zKRr12xfKj?o`Jh@k4DlxGkAHG3{$bIs5Je_#TXrszNRK2vHm_@4Ra_X%%otO>=W0K`}qCP%1UhXWsTA zV&7k1oXSWIY6#@)mD@y3ze*x|-M~?Ss9k@3U4X%X5A;(Ev4YvSPk_L)cXK8FnkN8& zfJF~)k-((JF$0qdeQWzZ-SGrTYFhLn8K7jLGbR-^1Uw{S6~URzk}*9;@&KJ05IZd* znwQdvZ(V~dE6(%5|2#=-+4l&B2@1XbRD-A|tJ-k=N#+#6v!W#gSNK<(M?gXV>moe% zi1?T-F(KgB^+#)8&Y^!w6?S7nNn?SKDHa$a5F}V<3o?Lcu>_d{`hx1B5+o;40b2ZW z*rEVT5KyU?8q^m__5RT(!joSpm;Hf^#Fra0g!XuOV17577yo%P!jQ=T0z?qA$l-{I zjQP6xACfjWnN64|0#7x#*J}Tb(C*wR4VkEjkFqmKOIYN8p6JYT1mwm)Q|1Uj+rXi0IVvjE-lZPWF_yvib@A>J~E+LX`J$B#eiA=BMI(U<6=35$M$Kuu~wrSPK&* zW*5JVefnL)k?_-?QUih^=o}h?9SJJNG_=ZZ@J}7%JhT_H|A)PhgZr&50L){G6w%2i zIDV`>5d)P(N%?7rD){s3t^nZUBxRwWj!O{gLXB8tMWC!cx2{k&wfo;FK%DCnJOIlC zBzNi^k@?3XVP}Q@FcW%!(-Oqo98VN8(LW#m@Q0-R)GEk-@@+r0tDX0W{VD<~Rtu}Q z7a9HWk75DYt_=`CZs~wdKM^|}B(fLom*(bHLC%tYXgJhQ?htPZc!`GK!VeQ%qrS@& z7Wapaz5Z!0eE->A+L3T#+3a6p4$-Bf1b)?6B>V^1xdx~c7`(M7U|1v_^g8DsW`YJ0 z7n2;1i#Z%IEdf8yUj9SULPvWE6V+bN1QxnrT5L`jiR%oJu*6uAYoGoFyC5mLgc!&R zX23xN&PX>RU~{V7X~{euQun)t9|74w4)Mluf@lbBham}v?Vp$ooQp%Sm-mN{ZT)Zd z!U8>vJpLu-#5mVf2rrZUs=mI`H44KhlWsutY{Xw`S`lk9~x_L;?Q35{LImM4beuXY!w&o<1qy z9Ar8EHDD9N3Y!m*7oEA9efH8U9TLe{v47pV(7kOEik9!$mH~i~hvtTZgvOdHC4>YCFdu zc_ZC`^Z1we^Z%bPGJ*d~82?Kce}TRK62|{M!1^y?^ndhU!g$CJ{vnL~p%Ae7#y`;? zsl{vJ+M1tXEI`X{`oq6!P%9z$5l3}imuubz%83ea=*k0!GcZ8~gZ~u4M6waWrvD*M zJ>=bwfs~^3MQE{Ltw>;@IS@z9Ync=BabX`m{2jCrU&BugJ9C58r#?a+i!Sc;g*`ng^HCJlFgS?1Ei z^84RipDQQiB(z9_xPoLuGkd7+g=;mmb<{Ep@|LXbNMflj4Tm2q$Vh&~K zvCZ=rSHQBGRp(-tV~_ds%dn3syhW1q)C8FrpN$nX3hX6pz-uX9bJXLR@3d z2u15XkeO9Y`Q{#2nHNJP(FG^@LtNq@ak0ls#cZ1|m)gp9*g-kY6Zk`fxErjT7|h?MPgT~Ih>SC zSD-z&p^0>r;of}wm^hR~R?{dUxJ((U95#7id79!cKCiwif$+)Kt+y3gq zazqK2b8AxIVQ3QBbj-vAi3`;Q&w~kR7|_mjDozlpc%RRQPv{{zEz6UhJbvwqou&ep z*xw%S4b}WjrH-I_$(prBg^hrFo(;`1uJ4Qw2!_r%oV|h;VQ*k{<-+PL*(A;KnXF+=eRFJGOO!7gV#t&-(FFsTf zJcQ87%&k8kd#9AE+J`2rl9Zxfh>~IMY-cw^oV&i~oWZWlv199>7|BI04zwhn{dG3$ z(~!Xe?Vav3mQ~2&Vz$Z7ao65x68j92@^WJe&iE_XJ7`1Ga>ItFuL|K#R64y6^jyo2 zLn9W3C!FvJ3gm;yS8WHRC*!2$(MeW<`f#k7d4F&JD^S@fD#<6bdy>m`6-y+Zw99VD z5z;-j(XAup6x&kJ{jG|OzTB2n`ep_XS2Q*Rvr(YJs%YXe3>vy(|JaS|d;$mT+(1yL z-ow^=9mxej^My56W@bmq%h%@ydVI(MILZ%EM_UR$@I$En}BbA{oJ5o-& z?NPBricfumm6;LvKJ`2)gax#d>psyt2?URYcIvVp~C`gFnjS z_G>gODMt+jucJeHVRFlXFW++SkdYSxM^gcs84^nOZ?hyv`v zb?lmW-#b2Y2`a=`!IG!Y`00R*qkZDpX#*1Ev--q3_}_@?Cx?#nSV}ILcz_c**a~KZ z7U!*dv+^pMy+oojMEO&;fK5jgn$t8o!{@URFZx2OZ=?n3{Fb0axue>ZoeoUGqF%7c zG5Ta>`ySO%$5q`-jf+U2gfx2cTyrJ9O!#PR<<1G^ynwi9RgSn{Xfxoz8cskK@)#v* zMrkT{Ha)IIN0nt`lOx&yb=oKQS?^HPIw=+&$~xbFt%`uk-{JYoS!n-N zfsLEqj{JG06?SBA;(YlpZhSdW&PpPCvG81qs4g&$z8CY*hg#izN7+7ROx-d5CtCqH zC0&&NWG0fN;y=W!j{lnsI63^w|-&~Usvam1^ z!2g-sUVget>HBf$7h4G``*}GA-;1`bX={A(tmn@D?V^dleTe#zfNRfxLzSQ7#Vc%h z2M!&`q@L;Zz1UIB_;5a6(@g8Cgc@-j&fhf1Tot|6>R!orP2&ZBb38AJY)0Xw6m|BT zn<{TA45sF94>b0@Jo_66smg&8oF=KPju)S`f6OgK2{FKCl8LYDXq~q8A-e$SdnNzt z=#g+sNJMv-z~}ot8b-m9NZ6*%jnse=wm|_K_Wl_cYzu}4(refW1@(*?t&OfF@sF(d- zzq!n%dbg)+`i8uQ$?z*A`aM51#aD<@8e}FYZ?h|Ne_lprP4Xr(*TgQ=Hvz<9TktRh3)#d+uM0E_-oD zT;eJ7$LV=!#_xr%#tW)Z{BE}*Bl6TVF?=5)k3;Y6w?_1O2-xI))T80~XD$hU-P1yk zOCuUiJ=pcXVCxR@@)wM@4dv%NqSKof!I2B>)BbhtT~YiS8zp%*vu3xkM?hY8-(4us z5p~56^}aKRu15XFQ&3{y;%{|J-O4uc9%q5IB{#<{zlQtw&V_t-Gtd+h`gM?LQmFcU zTs?i)3!sem!`jZv6-Ox^`DNsaGH@5(UyY(3(ciqMC#l90zRe&D58c)}DJknZEqiS8 z4?rThhH?+@h=M{RCL`K2A=D>a4aNBWZUtyB?e!59yeJKTC$1PjSKtAgQ(~0AKH#T@ zgv0o?L0+GY)b9&aUi)ap$ny)6gmZ(Zr+F5xoPcsJ1V5AhRX#*{$^uJcj!vIG@z1Lh x|3akzb-w@p;&%r6Us?TEOMW`Ke`OzgGA!oi8)fa2c1M8EEqUeZS=aQv{tqKEa9RKW literal 0 HcmV?d00001 diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index e131e2446b..8c370a4aef 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -194,7 +194,7 @@ export function LoginOTP({
    - {t("noCodeReceived")} + {t("verify.noCodeReceived")}
    @@ -244,7 +244,7 @@ export function LoginOTP({ })} > {loading && } - {t("submit")} + {t("verify.submit")}
    diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index dc194d6530..bca61bcd95 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -4,7 +4,6 @@ import { resetPassword, sendPassword } from "@/lib/server/password"; import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; @@ -60,6 +59,7 @@ export function PasswordForm({ password: { password: values.password }, }), authRequestId, + forceMfa: loginSettings?.forceMfa, }).catch(() => { setLoading(false); setError("Could not verify password"); @@ -87,14 +87,16 @@ export function PasswordForm({ setError("Could not reset password"); }); + setLoading(false); + if (response && "error" in response) { setError(response.error); + return; } setInfo("Password was reset. Please check your email."); - setLoading(false); const params = new URLSearchParams({ loginName: loginName, }); @@ -110,141 +112,6 @@ export function PasswordForm({ return router.push("/password/set?" + params); } - async function submitPasswordAndContinue( - value: Inputs, - ): Promise { - const submitted = await submitPassword(value); - setInfo(""); - // if user has mfa -> /otp/[method] or /u2f - // if mfa is forced and user has no mfa -> /mfa/set - // if no passwordless -> /passkey/set - - // exclude password and passwordless - if ( - !submitted || - !submitted.authMethods || - !submitted.factors?.user?.loginName - ) { - return; - } - - const availableSecondFactors = submitted?.authMethods?.filter( - (m: AuthenticationMethodType) => - m !== AuthenticationMethodType.PASSWORD && - m !== AuthenticationMethodType.PASSKEY, - ); - - if (availableSecondFactors?.length == 1) { - const params = new URLSearchParams({ - loginName: submitted.factors?.user.loginName, - }); - - if (authRequestId) { - params.append("authRequestId", authRequestId); - } - - if (organization) { - params.append("organization", organization); - } - - const factor = availableSecondFactors[0]; - // if passwordless is other method, but user selected password as alternative, perform a login - if (factor === AuthenticationMethodType.TOTP) { - return router.push(`/otp/time-based?` + params); - } else if (factor === AuthenticationMethodType.OTP_SMS) { - return router.push(`/otp/sms?` + params); - } else if (factor === AuthenticationMethodType.OTP_EMAIL) { - return router.push(`/otp/email?` + params); - } else if (factor === AuthenticationMethodType.U2F) { - return router.push(`/u2f?` + params); - } - } else if (availableSecondFactors?.length >= 1) { - const params = new URLSearchParams({ - loginName: submitted.factors.user.loginName, - }); - - if (authRequestId) { - params.append("authRequestId", authRequestId); - } - - if (organization) { - params.append("organization", organization); - } - - return router.push(`/mfa?` + params); - } else if (loginSettings?.forceMfa && !availableSecondFactors.length) { - const params = new URLSearchParams({ - loginName: submitted.factors.user.loginName, - force: "true", // this defines if the mfa is forced in the settings - checkAfter: "true", // this defines if the check is directly made after the setup - }); - - if (authRequestId) { - params.append("authRequestId", authRequestId); - } - - if (organization) { - params.append("organization", organization); - } - - // TODO: provide a way to setup passkeys on mfa page? - return router.push(`/mfa/set?` + params); - } - // TODO: implement passkey setup - - // else if ( - // submitted.factors && - // !submitted.factors.webAuthN && // if session was not verified with a passkey - // promptPasswordless && // if explicitly prompted due policy - // !isAlternative // escaped if password was used as an alternative method - // ) { - // const params = new URLSearchParams({ - // loginName: submitted.factors.user.loginName, - // prompt: "true", - // }); - - // if (authRequestId) { - // params.append("authRequestId", authRequestId); - // } - - // if (organization) { - // params.append("organization", organization); - // } - - // return router.push(`/passkey/set?` + params); - // } - else if (authRequestId && submitted.sessionId) { - const params = new URLSearchParams({ - sessionId: submitted.sessionId, - authRequest: authRequestId, - }); - - if (organization) { - params.append("organization", organization); - } - - return router.push(`/login?` + params); - } - - // without OIDC flow - const params = new URLSearchParams( - authRequestId - ? { - loginName: submitted.factors.user.loginName, - authRequestId, - } - : { - loginName: submitted.factors.user.loginName, - }, - ); - - if (organization) { - params.append("organization", organization); - } - - return router.push(`/signedin?` + params); - } - return (
    @@ -295,7 +162,7 @@ export function PasswordForm({ className="self-end" variant={ButtonVariants.Primary} disabled={loading || !formState.isValid} - onClick={handleSubmit(submitPasswordAndContinue)} + onClick={handleSubmit(submitPassword)} > {loading && } {t("verify.submit")} diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 51e3f1325e..ead010484d 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -6,9 +6,10 @@ import { symbolValidator, upperCaseValidator, } from "@/helpers/validators"; -import { changePassword } from "@/lib/server/password"; +import { changePassword, sendPassword } from "@/lib/server/password"; +import { create } from "@zitadel/client"; +import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; -import { SetPasswordResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; @@ -61,7 +62,7 @@ export function SetPasswordForm({ async function submitRegister(values: Inputs) { setLoading(true); - const response = await changePassword({ + const changeResponse = await changePassword({ userId: userId, password: values.password, code: values.code, @@ -69,21 +70,17 @@ export function SetPasswordForm({ setError("Could not register user"); }); - if (response && "error" in response) { - setError(response.error); + if (changeResponse && "error" in changeResponse) { + setError(changeResponse.error); } setLoading(false); - if (!response) { + if (!changeResponse) { setError("Could not register user"); return; } - const userResponse = response as SetPasswordResponse & { - sessionId: string; - }; - const params = new URLSearchParams({}); if (loginName) { @@ -93,22 +90,45 @@ export function SetPasswordForm({ params.append("organization", organization); } - // skip verification for now as it is an app based flow - // return router.push(`/verify?` + params); + const passwordResponse = await sendPassword({ + loginName, + organization, + checks: create(ChecksSchema, { + password: { password: values.password }, + }), + authRequestId, + }).catch(() => { + setLoading(false); + setError("Could not verify password"); + return; + }); - // check for mfa force to continue with mfa setup + setLoading(false); - if (authRequestId && userResponse.sessionId) { - if (authRequestId) { - params.append("authRequest", authRequestId); - } - return router.push(`/login?` + params); - } else { - if (authRequestId) { - params.append("authRequestId", authRequestId); - } - return router.push(`/signedin?` + params); + if ( + passwordResponse && + "error" in passwordResponse && + passwordResponse.error + ) { + setError(passwordResponse.error); } + + // // skip verification for now as it is an app based flow + // // return router.push(`/verify?` + params); + + // // check for mfa force to continue with mfa setup + + // if (authRequestId && changeResponse.sessionId) { + // if (authRequestId) { + // params.append("authRequest", authRequestId); + // } + // return router.push(`/login?` + params); + // } else { + // if (authRequestId) { + // params.append("authRequestId", authRequestId); + // } + // return router.push(`/signedin?` + params); + // } } const { errors } = formState; diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index faa0bbeb04..3db467b0f3 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -16,6 +16,9 @@ import { Checks, ChecksSchema, } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { User, UserState } from "@zitadel/proto/zitadel/user/v2/user_pb"; +import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { redirect } from "next/navigation"; import { getSessionCookieByLoginName } from "../cookies"; type ResetPasswordCommand = { @@ -46,6 +49,7 @@ export type UpdateSessionCommand = { organization?: string; checks: Checks; authRequestId?: string; + forceMfa?: boolean; }; export async function sendPassword(command: UpdateSessionCommand) { @@ -57,13 +61,18 @@ export async function sendPassword(command: UpdateSessionCommand) { }); let session; + let user: User; if (!sessionCookie) { const users = await listUsers({ loginName: command.loginName, organizationId: command.organization, }); + console.log(users); + if (users.details?.totalResult == BigInt(1) && users.result[0].userId) { + user = users.result[0]; + const checks = create(ChecksSchema, { user: { search: { case: "userId", value: users.result[0].userId } }, password: { password: command.checks.password?.password }, @@ -85,6 +94,18 @@ export async function sendPassword(command: UpdateSessionCommand) { undefined, command.authRequestId, ); + + if (!session?.factors?.user?.id) { + return { error: "Could not create session for user" }; + } + + const userResponse = await getUserByID(session?.factors?.user?.id); + + if (!userResponse.user) { + return { error: "Could not find user" }; + } + + user = userResponse.user; } if (!session?.factors?.user?.id || !sessionCookie) { @@ -102,12 +123,151 @@ export async function sendPassword(command: UpdateSessionCommand) { } } - return { + const submitted = { sessionId: session.id, factors: session.factors, challenges: session.challenges, authMethods, + userState: user.state, }; + + if ( + !submitted || + !submitted.authMethods || + !submitted.factors?.user?.loginName + ) { + return { error: "Could not verify password!" }; + } + + const availableSecondFactors = submitted?.authMethods?.filter( + (m: AuthenticationMethodType) => + m !== AuthenticationMethodType.PASSWORD && + m !== AuthenticationMethodType.PASSKEY, + ); + + if (availableSecondFactors?.length == 1) { + const params = new URLSearchParams({ + loginName: submitted.factors?.user.loginName, + }); + + if (command.authRequestId) { + params.append("authRequestId", command.authRequestId); + } + + if (command.organization) { + params.append("organization", command.organization); + } + + const factor = availableSecondFactors[0]; + // if passwordless is other method, but user selected password as alternative, perform a login + if (factor === AuthenticationMethodType.TOTP) { + return redirect(`/otp/time-based?` + params); + } else if (factor === AuthenticationMethodType.OTP_SMS) { + return redirect(`/otp/sms?` + params); + } else if (factor === AuthenticationMethodType.OTP_EMAIL) { + return redirect(`/otp/email?` + params); + } else if (factor === AuthenticationMethodType.U2F) { + return redirect(`/u2f?` + params); + } + } else if (availableSecondFactors?.length >= 1) { + const params = new URLSearchParams({ + loginName: submitted.factors.user.loginName, + }); + + if (command.authRequestId) { + params.append("authRequestId", command.authRequestId); + } + + if (command.organization) { + params.append("organization", command.organization); + } + + return redirect(`/mfa?` + params); + } else if (submitted.userState === UserState.INITIAL) { + const params = new URLSearchParams({ + loginName: submitted.factors.user.loginName, + }); + + if (command.authRequestId) { + params.append("authRequestId", command.authRequestId); + } + + if (command.organization) { + params.append("organization", command.organization); + } + + return redirect(`/password/change?` + params); + } else if (command.forceMfa && !availableSecondFactors.length) { + const params = new URLSearchParams({ + loginName: submitted.factors.user.loginName, + force: "true", // this defines if the mfa is forced in the settings + checkAfter: "true", // this defines if the check is directly made after the setup + }); + + if (command.authRequestId) { + params.append("authRequestId", command.authRequestId); + } + + if (command.organization) { + params.append("organization", command.organization); + } + + // TODO: provide a way to setup passkeys on mfa page? + return redirect(`/mfa/set?` + params); + } + // TODO: implement passkey setup + + // else if ( + // submitted.factors && + // !submitted.factors.webAuthN && // if session was not verified with a passkey + // promptPasswordless && // if explicitly prompted due policy + // !isAlternative // escaped if password was used as an alternative method + // ) { + // const params = new URLSearchParams({ + // loginName: submitted.factors.user.loginName, + // prompt: "true", + // }); + + // if (authRequestId) { + // params.append("authRequestId", authRequestId); + // } + + // if (organization) { + // params.append("organization", organization); + // } + + // return router.push(`/passkey/set?` + params); + // } + else if (command.authRequestId && submitted.sessionId) { + const params = new URLSearchParams({ + sessionId: submitted.sessionId, + authRequest: command.authRequestId, + }); + + if (command.organization) { + params.append("organization", command.organization); + } + + return redirect(`/login?` + params); + } + + // without OIDC flow + const params = new URLSearchParams( + command.authRequestId + ? { + loginName: submitted.factors.user.loginName, + authRequestId: command.authRequestId, + } + : { + loginName: submitted.factors.user.loginName, + }, + ); + + if (command.organization) { + params.append("organization", command.organization); + } + + return redirect(`/signedin?` + params); } export async function changePassword(command: { From 3c0f24367965aa3a6d9e940773f2740a97980bfa Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 17 Oct 2024 10:09:05 +0200 Subject: [PATCH 294/640] cleanup flow diagram --- apps/login/readme.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 14a9e386c5..e65bd64bdc 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -32,10 +32,7 @@ This diagram shows the available pages and flows. otp --> B[signedin] mfa--> u2f u2f -->B[signedin] - register --> passkey-add - register --> password-set - password-set --> B[signedin] - passkey-add --> B[signedin] + register -- password/passkey --> B[signedin] password --> B[signedin] password-- forceMFA -->mfaset mfaset --> u2fset From 3e9d406db22979713acba6ed8932d5f54098c851 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 17 Oct 2024 10:14:14 +0200 Subject: [PATCH 295/640] flowdiagram --- README.md | 11 +++++++---- apps/login/readme.md | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6916ed2bd0..a9b346d7ab 100644 --- a/README.md +++ b/README.md @@ -115,14 +115,17 @@ You can already use the current state, and extend it with your needs. passkey --> B[signedin] password -- hasMFA --> mfa password -- allowPasskeys --> passkey-add + password -- reset --> password-set + email -- reset --> password-set + password-set --> B[signedin] + password-cahnge --> B[signedin] + password -- userstate=initial --> password-change + mfa --> otp otp --> B[signedin] mfa--> u2f u2f -->B[signedin] - register --> passkey-add - register --> password-set - password-set --> B[signedin] - passkey-add --> B[signedin] + register -- password/passkey --> B[signedin] password --> B[signedin] password-- forceMFA -->mfaset mfaset --> u2fset diff --git a/apps/login/readme.md b/apps/login/readme.md index e65bd64bdc..2ec49b3857 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -26,6 +26,8 @@ This diagram shows the available pages and flows. password -- allowPasskeys --> passkey-add password -- reset --> password-set email -- reset --> password-set + password-set --> B[signedin] + password-cahnge --> B[signedin] password -- userstate=initial --> password-change mfa --> otp From 7837d61a1c6032e0ba5f94746b6d711707a656e8 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 17 Oct 2024 10:15:03 +0200 Subject: [PATCH 296/640] typo --- README.md | 2 +- apps/login/readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a9b346d7ab..ec33bf51ff 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ You can already use the current state, and extend it with your needs. password -- reset --> password-set email -- reset --> password-set password-set --> B[signedin] - password-cahnge --> B[signedin] + password-change --> B[signedin] password -- userstate=initial --> password-change mfa --> otp diff --git a/apps/login/readme.md b/apps/login/readme.md index 2ec49b3857..190fcf6002 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -27,7 +27,7 @@ This diagram shows the available pages and flows. password -- reset --> password-set email -- reset --> password-set password-set --> B[signedin] - password-cahnge --> B[signedin] + password-change --> B[signedin] password -- userstate=initial --> password-change mfa --> otp From 7d871f39548d083d91c27485c9f0d575b465a632 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 17 Oct 2024 10:19:13 +0200 Subject: [PATCH 297/640] login after password change --- .../src/components/change-password-form.tsx | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/apps/login/src/components/change-password-form.tsx b/apps/login/src/components/change-password-form.tsx index d54210c1f3..ccdcd79281 100644 --- a/apps/login/src/components/change-password-form.tsx +++ b/apps/login/src/components/change-password-form.tsx @@ -7,6 +7,9 @@ import { upperCaseValidator, } from "@/helpers/validators"; import { setMyPassword } from "@/lib/self"; +import { sendPassword } from "@/lib/server/password"; +import { create } from "@zitadel/client"; +import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; @@ -58,7 +61,7 @@ export function ChangePasswordForm({ async function submitChange(values: Inputs) { setLoading(true); - const response = await setMyPassword({ + const changeResponse = await setMyPassword({ sessionId: sessionId, password: values.password, }).catch(() => { @@ -67,36 +70,40 @@ export function ChangePasswordForm({ setLoading(false); - if (response && "error" in response) { - setError(response.error); + if (changeResponse && "error" in changeResponse) { + setError(changeResponse.error); return; } - if (!response) { + if (!changeResponse) { setError("Could not change password"); return; } - const params = new URLSearchParams({}); + const passwordResponse = await sendPassword({ + loginName, + organization, + checks: create(ChecksSchema, { + password: { password: values.password }, + }), + authRequestId, + }).catch(() => { + setLoading(false); + setError("Could not verify password"); + return; + }); - if (loginName) { - params.append("loginName", loginName); - } - if (organization) { - params.append("organization", organization); + setLoading(false); + + if ( + passwordResponse && + "error" in passwordResponse && + passwordResponse.error + ) { + setError(passwordResponse.error); } - if (authRequestId && sessionId) { - if (authRequestId) { - params.append("authRequest", authRequestId); - } - return router.push(`/login?` + params); - } else { - if (authRequestId) { - params.append("authRequestId", authRequestId); - } - return router.push(`/signedin?` + params); - } + return; } const { errors } = formState; From e10a54d4ae6898205dc92040cfcc05b6093ba017 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 17 Oct 2024 16:35:30 +0200 Subject: [PATCH 298/640] use urlTemplate for emails, fix error i18n --- apps/login/src/app/(login)/mfa/page.tsx | 5 ++--- apps/login/src/app/(login)/mfa/set/page.tsx | 7 +++--- .../src/app/(login)/otp/[method]/page.tsx | 3 ++- .../src/app/(login)/otp/[method]/set/page.tsx | 3 ++- apps/login/src/app/(login)/passkey/page.tsx | 5 ++--- .../src/app/(login)/passkey/set/page.tsx | 3 ++- .../src/app/(login)/password/change/page.tsx | 5 +++-- apps/login/src/app/(login)/password/page.tsx | 3 ++- .../src/app/(login)/password/set/page.tsx | 5 +++-- apps/login/src/app/(login)/u2f/page.tsx | 5 ++--- apps/login/src/app/(login)/u2f/set/page.tsx | 3 ++- apps/login/src/app/(login)/verify/page.tsx | 3 ++- apps/login/src/components/password-form.tsx | 2 +- apps/login/src/lib/server/password.ts | 5 ++++- apps/login/src/lib/zitadel.ts | 22 +++++++++++++------ 15 files changed, 47 insertions(+), 32 deletions(-) diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index 38b3e57cf9..f61aa4d988 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -19,6 +19,7 @@ export default async function Page({ }) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "mfa" }); + const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, authRequestId, organization, sessionId } = searchParams; @@ -84,9 +85,7 @@ export default async function Page({ > )} - {!(loginName || sessionId) && ( - {t("error:unknownContext")} - )} + {!(loginName || sessionId) && {tError("unknownContext")}} {sessionFactors ? ( )} - {!(loginName || sessionId) && ( - {t("error:unknownContext")} - )} + {!(loginName || sessionId) && {tError("unknownContext")}} - {!valid && {t("error.sessionExpired")}} + {!valid && {tError("sessionExpired")}} {isSessionValid(sessionWithData).valid && loginSettings && diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index d1071dbcad..7109d58f11 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -15,6 +15,7 @@ export default async function Page({ }) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "otp" }); + const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, authRequestId, sessionId, organization, code, submit } = searchParams; @@ -44,7 +45,7 @@ export default async function Page({ {!session && (
    - {t("error:unknownContext")} + {tError("unknownContext")}
    )} diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index 7a9c28cbbd..b08a1facde 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -25,6 +25,7 @@ export default async function Page({ }) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "otp" }); + const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, organization, sessionId, authRequestId, checkAfter } = searchParams; @@ -105,7 +106,7 @@ export default async function Page({

    {t("set.title")}

    {!session && (
    - {t("error:unknownContext")} + {tError("unknownContext")}
    )} diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index 22a195673b..fc0f701e80 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -14,6 +14,7 @@ export default async function Page({ }) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "passkey" }); + const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, altPassword, authRequestId, organization, sessionId } = searchParams; @@ -51,9 +52,7 @@ export default async function Page({ )}

    {t("verify.description")}

    - {!(loginName || sessionId) && ( - {t("error:unknownContext")} - )} + {!(loginName || sessionId) && {tError("unknownContext")}} {(loginName || sessionId) && ( - {t("error:unknownContext")} + {tError("unknownContext")}
    )} diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx index cf8a970cda..006d2a15b5 100644 --- a/apps/login/src/app/(login)/password/change/page.tsx +++ b/apps/login/src/app/(login)/password/change/page.tsx @@ -17,6 +17,7 @@ export default async function Page({ }) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "password" }); + const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, organization, authRequestId, code } = searchParams; @@ -46,7 +47,7 @@ export default async function Page({ {(!sessionFactors || !loginName) && !loginSettings?.ignoreUnknownUsernames && (
    - {t("error:unknownContext")} + {tError("unknownContext")}
    )} @@ -71,7 +72,7 @@ export default async function Page({ /> ) : (
    - {t("error:failedLoading")} + {tError("failedLoading")}
    )}
    diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 43cb969dc3..1f752850e6 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -14,6 +14,7 @@ export default async function Page({ }) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "password" }); + const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, organization, authRequestId, alt } = searchParams; @@ -44,7 +45,7 @@ export default async function Page({ {(!sessionFactors || !loginName) && !loginSettings?.ignoreUnknownUsernames && (
    - {t("error:unknownContext")} + {tError("unknownContext")}
    )} diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index e99f79ef92..ac61442b3b 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -17,6 +17,7 @@ export default async function Page({ }) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "password" }); + const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, organization, authRequestId, code } = searchParams; @@ -44,7 +45,7 @@ export default async function Page({ {(!sessionFactors || !loginName) && !loginSettings?.ignoreUnknownUsernames && (
    - {t("error:unknownContext")} + {tError("unknownContext")}
    )} @@ -72,7 +73,7 @@ export default async function Page({ /> ) : (
    - {t("error:failedLoading")} + {tError("failedLoading")}
    )}
    diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index 60a661f60c..36fef6d786 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -14,6 +14,7 @@ export default async function Page({ }) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "u2f" }); + const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, authRequestId, sessionId, organization } = searchParams; @@ -50,9 +51,7 @@ export default async function Page({ )}

    {t("verify.description")}

    - {!(loginName || sessionId) && ( - {t("error:unknownContext")} - )} + {!(loginName || sessionId) && {tError("unknownContext")}} {(loginName || sessionId) && ( - {t("error:unknownContext")} + {tError("unknownContext")}
    )} diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 6ee00a30f8..07e00e7a08 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -8,6 +8,7 @@ import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ searchParams }: { searchParams: any }) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "verify" }); + const tError = await getTranslations({ locale, namespace: "error" }); const { userId, @@ -31,7 +32,7 @@ export default async function Page({ searchParams }: { searchParams: any }) { {!userId && (
    - {t("error:unknownContext")} + {tError("unknownContext")}
    )} diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index bca61bcd95..98b0ec853f 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -85,13 +85,13 @@ export function PasswordForm({ organization, }).catch(() => { setError("Could not reset password"); + return; }); setLoading(false); if (response && "error" in response) { setError(response.error); - return; } diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 3db467b0f3..c1467e6b39 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -18,6 +18,7 @@ import { } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { User, UserState } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { headers } from "next/headers"; import { redirect } from "next/navigation"; import { getSessionCookieByLoginName } from "../cookies"; @@ -27,6 +28,8 @@ type ResetPasswordCommand = { }; export async function resetPassword(command: ResetPasswordCommand) { + const host = headers().get("host"); + const users = await listUsers({ loginName: command.loginName, organizationId: command.organization, @@ -41,7 +44,7 @@ export async function resetPassword(command: ResetPasswordCommand) { } const userId = users.result[0].userId; - return passwordReset(userId); + return passwordReset(userId, host); } export type UpdateSessionCommand = { diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 846552ddda..9d815d7b40 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -492,16 +492,24 @@ export function createUser( * @param userId the id of the user where the email should be set * @returns the newly set email */ -export async function passwordReset(userId: string) { +export async function passwordReset(userId: string, host: string | null) { return userService.passwordReset( { userId, - medium: { - case: "sendLink", - value: { - notificationType: NotificationType.Email, - }, - }, + medium: host + ? { + case: "sendLink", + value: { + notificationType: NotificationType.Email, + urlTemplate: `https://${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}`, + }, + } + : { + case: "sendLink", + value: { + notificationType: NotificationType.Email, + }, + }, }, {}, ); From 1134b9c4b16892e6346653e18ea099103a5e9130 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Thu, 17 Oct 2024 16:42:49 +0200 Subject: [PATCH 299/640] develop against local zitadel --- README.md | 52 +++++++++++++++++++++-------- acceptance/pat/.gitignore | 2 +- acceptance/pat/.gitkeep | 0 acceptance/pat/zitadel-admin-sa.pat | 1 - acceptance/zitadel.yaml | 6 +--- package.json | 4 ++- 6 files changed, 43 insertions(+), 22 deletions(-) delete mode 100644 acceptance/pat/.gitkeep delete mode 100644 acceptance/pat/zitadel-admin-sa.pat diff --git a/README.md b/README.md index 6916ed2bd0..e5b0f1eda7 100644 --- a/README.md +++ b/README.md @@ -175,32 +175,56 @@ To run the application make sure to install the dependencies with pnpm install ``` -then setup the environment for the login application which needs a `.env.local` in `/apps/login`. -Go to your instance and create a service user for the application having the `IAM_OWNER` manager role. -This user is required to have access to create users on your primary organization and reading policy data so it can be -restricted to your personal use case but we'll stick with `IAM_OWNER` for convenience. Create a PAT and copy the value to -paste it under the `ZITADEL_SERVICE_USER_TOKEN` key. -The file should look as follows: - -``` -ZITADEL_API_URL=[yourinstanceurl] -ZITADEL_ORG_ID=[yourprimaryorg] -ZITADEL_SERVICE_USER_TOKEN=[yourserviceuserpersonalaccesstoken] -``` - then generate the GRPC stubs with ```sh pnpm generate ``` -and then run it with +To run the application against a local ZITADEL instance, run the following command: + +```sh +pnpm run-zitadel +``` + +This sets up ZITADEL using docker compose and writes the configuration to the file `apps/login/.env.local`. + +
    +Alternatively, use another environment +You can develop against any ZITADEL instance in which you have sufficient rights to execute the following steps. +Just create or overwrite the file `apps/login/.env.local` yourself. +Add your instances base URL to the file at the key `ZITADEL_API_URL`. +Go to your instance and create a service user for the login application. +The login application creates users on your primary organization and reads policy data. +For the sake of simplicity, just make the service user an instance member with the role `IAM_OWNER`. +Create a PAT and copy it to the file `apps/login/.env.local` using the key `ZITADEL_SERVICE_USER_TOKEN`. +Also add the users ID to the file using the key `ZITADEL_SERVICE_USER_ID`. + +The file should look similar to this: + +``` +ZITADEL_API_URL=https://zitadel-tlx3du.us1.zitadel.cloud +ZITADEL_SERVICE_USER_ID=289106423158521850 +ZITADEL_SERVICE_USER_TOKEN=1S6w48thfWFI2klgfwkCnhXJLf9FQ457E-_3H74ePQxfO3Af0Tm4V5Xi-ji7urIl_xbn-Rk +``` +
    + +Start the login application in dev mode: ```sh pnpm dev ``` Open the login application with your favorite browser at `localhost:3000`. +Change the source code and see the changes live in your browser. + +Make sure the application still behaves as expected by running all tests + +```sh +pnpm test +``` + +To satisfy your unique workflow requirements, check out the package.json in the root directory for more detailed scripts. ### Deploy to Vercel diff --git a/acceptance/pat/.gitignore b/acceptance/pat/.gitignore index 7c9f54d04c..f59ec20aab 100644 --- a/acceptance/pat/.gitignore +++ b/acceptance/pat/.gitignore @@ -1 +1 @@ -zitadel-admin-sa.json +* \ No newline at end of file diff --git a/acceptance/pat/.gitkeep b/acceptance/pat/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/acceptance/pat/zitadel-admin-sa.pat b/acceptance/pat/zitadel-admin-sa.pat deleted file mode 100644 index 9ac8de6447..0000000000 --- a/acceptance/pat/zitadel-admin-sa.pat +++ /dev/null @@ -1 +0,0 @@ -fEJWwOJ3lFAn-COq0QxdXz_xCGrmp8Kj2l4i-xGWbh1UM2OtNwNz3_MblwOf_Lsd13B8ORk diff --git a/acceptance/zitadel.yaml b/acceptance/zitadel.yaml index 1fe754dcd9..407f424fb4 100644 --- a/acceptance/zitadel.yaml +++ b/acceptance/zitadel.yaml @@ -6,7 +6,7 @@ FirstInstance: FirstName: ZITADEL LastName: Admin Password: Password1! - PasswordChangeRequired: true + PasswordChangeRequired: false PreferredLanguage: en Machine: Machine: @@ -39,7 +39,3 @@ Logstore: Access: Stdout: Enabled: true - -DefaultInstance: - LoginPolicy: - MfaInitSkipLifetime: 0h \ No newline at end of file diff --git a/package.json b/package.json index 3fc480546a..95a38ef542 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "start": "turbo run start", "test:unit": "turbo run test:unit -- --passWithNoTests", "test:integration": "turbo run test:integration", + "test:acceptance": "pnpm exec playwright test", "test:watch": "turbo run test:watch", "dev": "turbo run dev --no-cache --continue", "lint": "turbo run lint", @@ -17,7 +18,8 @@ "format": "prettier --check \"**/*.{ts,tsx,md}\"", "changeset": "changeset", "version-packages": "changeset version", - "release": "turbo run build --filter=login^... && changeset publish" + "release": "turbo run build --filter=login^... && changeset publish", + "run-zitadel": "docker compose -f ./acceptance/docker-compose.yaml run setup" }, "pnpm": { "overrides": { From 08a7cca6254243e2daf6021d6a807a206b77104c Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 17 Oct 2024 17:25:56 +0200 Subject: [PATCH 300/640] cleaner override --- apps/login/src/lib/zitadel.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 9d815d7b40..a7bb9e2f7a 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -28,7 +28,10 @@ import { } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { PasswordComplexitySettingsSchema } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb"; -import { NotificationType } from "@zitadel/proto/zitadel/user/v2/password_pb"; +import { + NotificationType, + SendPasswordResetLink, +} from "@zitadel/proto/zitadel/user/v2/password_pb"; import { SearchQuery, SearchQuerySchema, @@ -493,23 +496,24 @@ export function createUser( * @returns the newly set email */ export async function passwordReset(userId: string, host: string | null) { + let medium: Partial = { + notificationType: NotificationType.Email, + }; + + if (host) { + medium = { + ...medium, + urlTemplate: `https://${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}`, + }; + } + return userService.passwordReset( { userId, - medium: host - ? { - case: "sendLink", - value: { - notificationType: NotificationType.Email, - urlTemplate: `https://${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}`, - }, - } - : { - case: "sendLink", - value: { - notificationType: NotificationType.Email, - }, - }, + medium: { + case: "sendLink", + value: medium, + }, }, {}, ); From d1ed95d33bbb74e66412bd4a9a207641a66bf520 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 17 Oct 2024 17:27:21 +0200 Subject: [PATCH 301/640] schema --- apps/login/src/lib/zitadel.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index a7bb9e2f7a..e6ff00f4dc 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -30,7 +30,7 @@ import { PasswordComplexitySettingsSchema } from "@zitadel/proto/zitadel/setting import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { NotificationType, - SendPasswordResetLink, + SendPasswordResetLinkSchema, } from "@zitadel/proto/zitadel/user/v2/password_pb"; import { SearchQuery, @@ -496,9 +496,9 @@ export function createUser( * @returns the newly set email */ export async function passwordReset(userId: string, host: string | null) { - let medium: Partial = { + let medium = create(SendPasswordResetLinkSchema, { notificationType: NotificationType.Email, - }; + }); if (host) { medium = { From ce79f8f170ad2fbb3ae082b9642c6ddfab355794 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 18 Oct 2024 11:29:59 +0200 Subject: [PATCH 302/640] userId as alternative to support email --- .../src/app/(login)/password/set/page.tsx | 65 +++++++++++++------ apps/login/src/components/login-otp.tsx | 61 ++++++++--------- .../src/components/set-password-form.tsx | 3 - 3 files changed, 76 insertions(+), 53 deletions(-) diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index ac61442b3b..8e0a8a6993 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -7,7 +7,10 @@ import { getBrandingSettings, getLoginSettings, getPasswordComplexitySettings, + getUserByID, } from "@/lib/zitadel"; +import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ @@ -19,54 +22,74 @@ export default async function Page({ const t = await getTranslations({ locale, namespace: "password" }); const tError = await getTranslations({ locale, namespace: "error" }); - const { loginName, organization, authRequestId, code } = searchParams; + const { userId, loginName, organization, authRequestId, code } = searchParams; // also allow no session to be found (ignoreUnkownUsername) - const sessionFactors = await loadMostRecentSession({ - loginName, - organization, - }); + let session: Session | undefined; + if (loginName) { + session = await loadMostRecentSession({ + loginName, + organization, + }); + } const branding = await getBrandingSettings(organization); const passwordComplexity = await getPasswordComplexitySettings( - sessionFactors?.factors?.user?.organizationId, + session?.factors?.user?.organizationId, ); const loginSettings = await getLoginSettings(organization); + let user: User | undefined; + let displayName: string | undefined; + if (userId) { + const userResponse = await getUserByID(userId); + user = userResponse.user; + + if (user?.type.case === "human") { + displayName = (user.type.value as HumanUser).profile?.displayName; + } + } + return (
    -

    {sessionFactors?.factors?.user?.displayName ?? t("set.title")}

    +

    {session?.factors?.user?.displayName ?? t("set.title")}

    {t("set.description")}

    {/* show error only if usernames should be shown to be unknown */} - {(!sessionFactors || !loginName) && - !loginSettings?.ignoreUnknownUsernames && ( -
    - {tError("unknownContext")} -
    - )} + {loginName && !session && !loginSettings?.ignoreUnknownUsernames && ( +
    + {tError("unknownContext")} +
    + )} - {sessionFactors && ( + {session ? ( - )} + ) : user ? ( + + ) : null} {t("set.codeSent")} {passwordComplexity && - loginName && - sessionFactors?.factors?.user?.id ? ( + (loginName ?? user?.preferredLoginName) && + (userId ?? session?.factors?.user?.id) ? ( { - setError(error.message ?? "Could not verify OTP code"); + }).catch(() => { + setError("Could not verify OTP code"); setLoading(false); + return; }); setLoading(false); @@ -151,39 +152,41 @@ export function LoginOTP({ function setCodeAndContinue(values: Inputs, organization?: string) { return submitCode(values, organization).then((response) => { - if (authRequestId && response && response.sessionId) { - const params = new URLSearchParams({ - sessionId: response.sessionId, - authRequest: authRequestId, - }); + if (response) { + if (authRequestId && response && response.sessionId) { + const params = new URLSearchParams({ + sessionId: response.sessionId, + authRequest: authRequestId, + }); - if (organization) { - params.append("organization", organization); - } + if (organization) { + params.append("organization", organization); + } - if (authRequestId) { - params.append("authRequest", authRequestId); - } + if (authRequestId) { + params.append("authRequest", authRequestId); + } - if (sessionId) { - params.append("sessionId", sessionId); - } + if (sessionId) { + params.append("sessionId", sessionId); + } - return router.push(`/login?` + params); - } else { - const params = new URLSearchParams(); - if (response?.factors?.user?.loginName) { - params.append("loginName", response.factors.user.loginName); - } - if (authRequestId) { - params.append("authRequestId", authRequestId); - } + return router.push(`/login?` + params); + } else { + const params = new URLSearchParams(); + if (response?.factors?.user?.loginName) { + params.append("loginName", response.factors.user.loginName); + } + if (authRequestId) { + params.append("authRequestId", authRequestId); + } - if (organization) { - params.append("organization", organization); - } + if (organization) { + params.append("organization", organization); + } - return router.push(`/signedin?` + params); + return router.push(`/signedin?` + params); + } } }); } diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index ead010484d..d32fabcd5a 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -11,7 +11,6 @@ import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { useTranslations } from "next-intl"; -import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; import { Alert } from "./alert"; @@ -58,8 +57,6 @@ export function SetPasswordForm({ const [loading, setLoading] = useState(false); const [error, setError] = useState(""); - const router = useRouter(); - async function submitRegister(values: Inputs) { setLoading(true); const changeResponse = await changePassword({ From 4606077ca0748a6c2ec1a7dfcb46df6fdbe78d94 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 18 Oct 2024 13:34:07 +0200 Subject: [PATCH 303/640] fix autocomplete for otp code --- apps/login/src/components/login-otp.tsx | 1 + apps/login/src/components/set-password-form.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 01cabedc2e..3d0d190045 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -225,6 +225,7 @@ export function LoginOTP({ type="text" {...register("code", { required: "This field is required" })} label="Code" + autoComplete="one-time-code" />
    diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index d32fabcd5a..60aa85f028 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -161,6 +161,7 @@ export function SetPasswordForm({ required: "This field is required", })} label="Code" + autoComplete="one-time-code" error={errors.code?.message as string} />
    From 94e2d31f5b706c854ff8eb12a015f1dd67e8f0e8 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 18 Oct 2024 15:42:38 +0200 Subject: [PATCH 304/640] invite and invite success pages --- apps/login/locales/de.json | 13 ++ apps/login/locales/en.json | 13 ++ apps/login/locales/es.json | 13 ++ apps/login/locales/it.json | 13 ++ apps/login/src/app/(login)/invite/page.tsx | 54 +++++++ .../src/app/(login)/invite/success/page.tsx | 71 +++++++++ apps/login/src/app/(login)/register/page.tsx | 10 +- apps/login/src/components/invite-form.tsx | 140 ++++++++++++++++++ .../register-form-without-password.tsx | 4 +- apps/login/src/components/user-avatar.tsx | 2 +- apps/login/src/lib/server/invite.ts | 41 +++++ apps/login/src/lib/zitadel.ts | 54 +++++++ apps/login/src/styles/globals.scss | 2 +- 13 files changed, 423 insertions(+), 7 deletions(-) create mode 100644 apps/login/src/app/(login)/invite/page.tsx create mode 100644 apps/login/src/app/(login)/invite/success/page.tsx create mode 100644 apps/login/src/components/invite-form.tsx create mode 100644 apps/login/src/lib/server/invite.ts diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 75771c1ac9..48df445d13 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -135,6 +135,19 @@ "submit": "Weiter" } }, + "invite": { + "title": "Benutzer einladen", + "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.", + "submit": "Einladen", + "success": { + "title": "Einladung erfolgreich", + "description": "Der Benutzer wurde erfolgreich eingeladen.", + "verified": "Der Benutzer wurde eingeladen und hat seine E-Mail bereits verifiziert.", + "notVerifiedYet": "Der Benutzer wurde eingeladen. Er erhält eine E-Mail mit weiteren Anweisungen.", + "submit": "Weiteren Benutzer einladen" + } + }, "signedin": { "title": "Willkommen {user}!", "description": "Sie sind angemeldet." diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 2481d047ac..8daf09faa3 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -135,6 +135,19 @@ "submit": "Continue" } }, + "invite": { + "title": "Invite User", + "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.", + "submit": "Continue", + "success": { + "title": "Invite User", + "description": "The email has successfully been sent.", + "verified": "The user has been invited and has already verified his email.", + "notVerifiedYet": "The user has been invited. They will receive an email with further instructions.", + "submit": "Invite another user" + } + }, "signedin": { "title": "Welcome {user}!", "description": "You are signed in." diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index 2643f763c9..f0fb832c80 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -135,6 +135,19 @@ "submit": "Continuar" } }, + "invite": { + "title": "Invitar usuario", + "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.", + "submit": "Invitar usuario", + "success": { + "title": "¡Usuario invitado!", + "description": "El usuario ha sido invitado.", + "verified": "El usuario ha sido invitado y ya ha verificado su correo electrónico.", + "notVerifiedYet": "El usuario ha sido invitado. Recibirá un correo electrónico con más instrucciones.", + "submit": "Invitar a otro usuario" + } + }, "signedin": { "title": "¡Bienvenido {user}!", "description": "Has iniciado sesión." diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index d13863ff3c..aef1042a0d 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -135,6 +135,19 @@ "submit": "Continua" } }, + "invite": { + "title": "Invita Utente", + "description": "Inserisci l'indirizzo email dell'utente che desideri invitare.", + "info": "L'utente riceverà un'email con ulteriori istruzioni.", + "submit": "Invita Utente", + "success": { + "title": "Invito inviato", + "description": "L'utente è stato invitato con successo.", + "verified": "L'utente è stato invitato e ha già verificato la sua email.", + "notVerifiedYet": "L'utente è stato invitato. Riceverà un'email con ulteriori istruzioni.", + "submit": "Invita un altro utente" + } + }, "signedin": { "title": "Benvenuto {user}!", "description": "Sei connesso." diff --git a/apps/login/src/app/(login)/invite/page.tsx b/apps/login/src/app/(login)/invite/page.tsx new file mode 100644 index 0000000000..2bf115a8d3 --- /dev/null +++ b/apps/login/src/app/(login)/invite/page.tsx @@ -0,0 +1,54 @@ +import { Alert, AlertType } from "@/components/alert"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { InviteForm } from "@/components/invite-form"; +import { + getBrandingSettings, + getDefaultOrg, + getPasswordComplexitySettings, +} from "@/lib/zitadel"; +import { getLocale, getTranslations } from "next-intl/server"; + +export default async function Page({ + searchParams, +}: { + searchParams: Record; +}) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "invite" }); + + let { firstname, lastname, email, organization } = searchParams; + + if (!organization) { + const org = await getDefaultOrg(); + if (!org) { + throw new Error("No default organization found"); + } + + organization = org.id; + } + + const passwordComplexitySettings = + await getPasswordComplexitySettings(organization); + + const branding = await getBrandingSettings(organization); + + return ( + +
    +

    {t("title")}

    +

    {t("description")}

    + + {t("info")} + + {passwordComplexitySettings && ( + + )} +
    +
    + ); +} diff --git a/apps/login/src/app/(login)/invite/success/page.tsx b/apps/login/src/app/(login)/invite/success/page.tsx new file mode 100644 index 0000000000..2cb564580e --- /dev/null +++ b/apps/login/src/app/(login)/invite/success/page.tsx @@ -0,0 +1,71 @@ +import { Alert, AlertType } from "@/components/alert"; +import { Button, ButtonVariants } from "@/components/button"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { UserAvatar } from "@/components/user-avatar"; +import { getBrandingSettings, getDefaultOrg, getUserByID } from "@/lib/zitadel"; +import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; +import { getLocale, getTranslations } from "next-intl/server"; +import Link from "next/link"; + +export default async function Page({ + searchParams, +}: { + searchParams: Record; +}) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "invite" }); + + let { userId, organization } = searchParams; + + if (!organization) { + const org = await getDefaultOrg(); + if (!org) { + throw new Error("No default organization found"); + } + + organization = org.id; + } + + const branding = await getBrandingSettings(organization); + + 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; + } + } + } + + return ( + +
    +

    {t("success.title")}

    +

    {t("success.description")}

    + {user && ( + + )} + {human?.email?.isVerified ? ( + {t("success.verified")} + ) : ( + {t("success.notVerifiedYet")} + )} +
    + + + + +
    +
    +
    + ); +} diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index ad84e81b31..6ade9103e1 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -3,6 +3,7 @@ import { RegisterFormWithoutPassword } from "@/components/register-form-without- import { SetRegisterPasswordForm } from "@/components/set-register-password-form"; import { getBrandingSettings, + getDefaultOrg, getLegalAndSupportSettings, getPasswordComplexitySettings, } from "@/lib/zitadel"; @@ -16,11 +17,16 @@ export default async function Page({ const locale = getLocale(); const t = await getTranslations({ locale, namespace: "register" }); - const { firstname, lastname, email, organization, authRequestId } = + let { firstname, lastname, email, organization, authRequestId } = searchParams; if (!organization) { - // TODO: get default organization + const org = await getDefaultOrg(); + if (!org) { + throw new Error("No default organization found"); + } + + organization = org.id; } const setPassword = !!(firstname && lastname && email); diff --git a/apps/login/src/components/invite-form.tsx b/apps/login/src/components/invite-form.tsx new file mode 100644 index 0000000000..d7b0aadd6f --- /dev/null +++ b/apps/login/src/components/invite-form.tsx @@ -0,0 +1,140 @@ +"use client"; + +import { inviteUser } from "@/lib/server/invite"; +import { useTranslations } from "next-intl"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { FieldValues, useForm } from "react-hook-form"; +import { Alert } from "./alert"; +import { BackButton } from "./back-button"; +import { Button, ButtonVariants } from "./button"; +import { TextInput } from "./input"; +import { Spinner } from "./spinner"; + +type Inputs = + | { + firstname: string; + lastname: string; + email: string; + } + | FieldValues; + +type Props = { + firstname?: string; + lastname?: string; + email?: string; + organization?: string; +}; + +export function InviteForm({ + email, + firstname, + lastname, + organization, +}: Props) { + const t = useTranslations("register"); + + const { register, handleSubmit, formState } = useForm({ + mode: "onBlur", + defaultValues: { + email: email ?? "", + firstName: firstname ?? "", + lastname: lastname ?? "", + }, + }); + + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + const router = useRouter(); + + async function submitAndContinue(values: Inputs) { + setLoading(true); + const response = await inviteUser({ + email: values.email, + firstName: values.firstname, + lastName: values.lastname, + organization: organization, + }).catch(() => { + setError("Could not create invitation Code"); + setLoading(false); + }); + + setLoading(false); + + if (response && typeof response === "object" && "error" in response) { + setError(response.error); + return; + } + + if (!response) { + setError("Could not create invitation Code"); + } + + const params = new URLSearchParams({}); + + if (response) { + params.append("userId", response); + } + + return router.push(`/invite/success?` + params); + } + + const { errors } = formState; + + return ( + +
    +
    + +
    +
    + +
    +
    + +
    +
    + + {error && ( +
    + {error} +
    + )} + +
    + + +
    + + ); +} diff --git a/apps/login/src/components/register-form-without-password.tsx b/apps/login/src/components/register-form-without-password.tsx index df1cad2caa..774feb64d0 100644 --- a/apps/login/src/components/register-form-without-password.tsx +++ b/apps/login/src/components/register-form-without-password.tsx @@ -162,9 +162,7 @@ export function RegisterFormWithoutPassword({ /> )} -

    - {t("selectMethod")} -

    +

    {t("selectMethod")}

    - + {loginName} diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts new file mode 100644 index 0000000000..0ef1abe092 --- /dev/null +++ b/apps/login/src/lib/server/invite.ts @@ -0,0 +1,41 @@ +"use server"; + +import { addHumanUser, createInviteCode } from "@/lib/zitadel"; +import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; + +type InviteUserCommand = { + email: string; + firstName: string; + lastName: string; + password?: string; + organization?: string; + authRequestId?: string; +}; + +export type RegisterUserResponse = { + userId: string; + sessionId: string; + factors: Factors | undefined; +}; + +export async function inviteUser(command: InviteUserCommand) { + const human = await addHumanUser({ + email: command.email, + firstName: command.firstName, + lastName: command.lastName, + password: command.password ? command.password : undefined, + organization: command.organization, + }); + + if (!human) { + return { error: "Could not create user" }; + } + + const codeResponse = await createInviteCode(human.userId); + + if (!codeResponse || !human) { + return { error: "Could not create invite code" }; + } + + return human.userId; +} diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index e6ff00f4dc..9bcff82559 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -36,6 +36,7 @@ import { SearchQuery, SearchQuerySchema, } from "@zitadel/proto/zitadel/user/v2/query_pb"; +import { SendInviteCodeSchema } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { unstable_cache } from "next/cache"; import { PROVIDER_MAPPING } from "./idp"; @@ -300,6 +301,41 @@ export async function getUserByID(userId: string) { return userService.getUserByID({ userId }, {}); } +export async function verifyInviteCode( + userId: string, + verificationCode: string, +) { + return userService.verifyInviteCode({ userId, verificationCode }, {}); +} + +export async function resendInviteCode(userId: string) { + return userService.resendInviteCode({ userId }, {}); +} + +export async function createInviteCode(userId: string, host?: string) { + let medium = create(SendInviteCodeSchema, { + applicationName: "Typescript Login", + }); + + if (host) { + medium = { + ...medium, + urlTemplate: `https://${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}`, + }; + } + + return userService.createInviteCode( + { + userId, + verification: { + case: "sendCode", + value: medium, + }, + }, + {}, + ); +} + export async function listUsers({ loginName, userName, @@ -370,6 +406,24 @@ export async function listUsers({ return userService.listUsers({ queries: queries }); } +export async function getDefaultOrg() { + return orgService + .listOrganizations( + { + queries: [ + { + query: { + case: "defaultQuery", + value: {}, + }, + }, + ], + }, + {}, + ) + .then((resp) => resp.result[0]); +} + export async function getOrgsByDomain(domain: string) { return orgService.listOrganizations( { diff --git a/apps/login/src/styles/globals.scss b/apps/login/src/styles/globals.scss index 0cfe0fd7f2..89bb258c70 100755 --- a/apps/login/src/styles/globals.scss +++ b/apps/login/src/styles/globals.scss @@ -12,7 +12,7 @@ } .ztdl-p { - @apply text-sm text-text-light-secondary-500 dark:text-text-dark-secondary-500; + @apply text-sm text-center text-text-light-secondary-500 dark:text-text-dark-secondary-500 text-center; } } From 18a1262cb55ef79f6519f62838c3bc34560fc967 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Fri, 18 Oct 2024 15:45:40 +0200 Subject: [PATCH 305/640] urlTemplate --- apps/login/locales/en.json | 2 +- apps/login/src/lib/server/invite.ts | 5 ++++- apps/login/src/lib/zitadel.ts | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 8daf09faa3..60c10a077f 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -141,7 +141,7 @@ "info": "The user will receive an email with further instructions.", "submit": "Continue", "success": { - "title": "Invite User", + "title": "User invited", "description": "The email has successfully been sent.", "verified": "The user has been invited and has already verified his email.", "notVerifiedYet": "The user has been invited. They will receive an email with further instructions.", diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts index 0ef1abe092..14b9e8ef9e 100644 --- a/apps/login/src/lib/server/invite.ts +++ b/apps/login/src/lib/server/invite.ts @@ -2,6 +2,7 @@ import { addHumanUser, createInviteCode } from "@/lib/zitadel"; import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { headers } from "next/headers"; type InviteUserCommand = { email: string; @@ -19,6 +20,8 @@ export type RegisterUserResponse = { }; export async function inviteUser(command: InviteUserCommand) { + const host = headers().get("host"); + const human = await addHumanUser({ email: command.email, firstName: command.firstName, @@ -31,7 +34,7 @@ export async function inviteUser(command: InviteUserCommand) { return { error: "Could not create user" }; } - const codeResponse = await createInviteCode(human.userId); + const codeResponse = await createInviteCode(human.userId, host); if (!codeResponse || !human) { return { error: "Could not create invite code" }; diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 9bcff82559..6eda092329 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -312,7 +312,7 @@ export async function resendInviteCode(userId: string) { return userService.resendInviteCode({ userId }, {}); } -export async function createInviteCode(userId: string, host?: string) { +export async function createInviteCode(userId: string, host: string | null) { let medium = create(SendInviteCodeSchema, { applicationName: "Typescript Login", }); From 1ca16f9d9a064771d34db6864d0b725debed34ba Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Fri, 18 Oct 2024 23:01:31 +0200 Subject: [PATCH 306/640] run acceptance --- .github/workflows/test.yml | 104 ++++++++++++++++++ .../workflows/unit-and-integration-tests.yml | 56 ---------- acceptance/docker-compose.yaml | 2 +- 3 files changed, 105 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .github/workflows/unit-and-integration-tests.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..786f990eea --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,104 @@ +name: Quality + +on: pull_request + +jobs: + quality: + env: + ZITADEL_IMAGE: ghcr.io/zitadel/zitadel:v2.63.4 + POSTGRES_IMAGE: postgres:17.0-alpine3.19 + + name: Ensure Quality + + runs-on: ubuntu-latest + + timeout-minutes: 30 + + permissions: + contents: "read" + + strategy: + fail-fast: false + matrix: + command: + - format --check + - lint + - test:unit + - test:integration + - test:acceptance + + steps: + - name: Checkout Repo + uses: actions/checkout@v4.1.6 + + - name: Setup Node.js 20.x + uses: actions/setup-node@v4.0.2 + with: + node-version: 20.x + + - name: Setup pnpm + uses: pnpm/action-setup@v4.0.0 + + - name: Get pnpm store directory + id: pnpm-cache + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v4.0.2 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install Dependencies + run: CYPRESS_INSTALL_BINARY=0 pnpm install + + # We build already here so we can fail fast if the build fails + - name: Run ZITADEL + run: pnpm build + if: ${{ matrix.command }} == "test:acceptance" + + - run: | + echo "CYPRESS_VERSION=$(pnpm list -r | grep cypress | cut -d ' ' -f 2)" >> $GITHUB_ENV + echo "PLAYWRIGHT_VERSION=$(npx playwright --version | cut -d ' ' -f 2)" >> $GITHUB_ENV + + - uses: actions/cache@v4.0.2 + name: Setup Cypress binary cache + with: + path: ~/.cache/Cypress + key: ${{ runner.os }}-cypress-binary-${{ env.CYPRESS_VERSION }} + restore-keys: | + ${{ runner.os }}-cypress-binary- + if: ${{ matrix.command }} == "test:integration" + + - name: Install Cypress Browsers + run: pnpm install + if: ${{ matrix.command }} == "test:integration" + + - uses: actions/cache@v4.0.2 + name: Setup Playwright binary cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-binary-${{ env.PLAYWRIGHT_VERSION }} + restore-keys: | + ${{ runner.os }}-playwright-binary- + if: ${{ matrix.command }} == "test:acceptance" + + - name: Install Playwright Browsers + run: sudo pnpm exec playwright install --with-deps + if: ${{ matrix.command }} == "test:acceptance" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + if: ${{ matrix.command }} == "test:acceptance" + + - name: Run ZITADEL + run: pnpm run-zitadel + if: ${{ matrix.command }} == "test:acceptance" + + - name: Check + id: check + run: pnpm ${{ matrix.command }} diff --git a/.github/workflows/unit-and-integration-tests.yml b/.github/workflows/unit-and-integration-tests.yml deleted file mode 100644 index ecdc443ffa..0000000000 --- a/.github/workflows/unit-and-integration-tests.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Unit And Integration Tests -on: pull_request -jobs: - quality: - name: Ensure Quality - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: "read" - strategy: - fail-fast: false - matrix: - command: - - format --check - - lint - - test:unit - - test:integration - steps: - - name: Checkout Repo - uses: actions/checkout@v4.1.6 - - name: Setup Node.js 20.x - uses: actions/setup-node@v4.0.2 - with: - node-version: 20.x - - name: Setup pnpm - uses: pnpm/action-setup@v4.0.0 - - uses: pnpm/action-setup@v4.0.0 - name: Install pnpm - id: pnpm-install - with: - run_install: false - - name: Get pnpm store directory - id: pnpm-cache - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - uses: actions/cache@v4.0.2 - name: Setup pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - uses: actions/cache@v4.0.2 - name: Setup Cypress binary cache - with: - path: ~/.cache/Cypress - key: ${{ runner.os }}-cypress-binary-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-cypress-binary- - if: ${{ matrix.command }} == "test:integration" - - name: Install Dependencies - run: pnpm install - - name: Check - id: check - run: pnpm ${{ matrix.command }} diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index afc6d4d6a8..59839e0017 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -14,7 +14,7 @@ services: db: restart: 'always' - image: 'postgres:latest' + image: "${POSTGRES_IMAGE:-postgres:latest}" environment: - POSTGRES_USER=zitadel - PGUSER=zitadel From 7b43cac249ea0f872841b5d7c5bf246b8365251f Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Fri, 18 Oct 2024 23:06:19 +0200 Subject: [PATCH 307/640] remove gh workflow --- .github/workflows/acceptance-tests.yml | 64 -------------------------- 1 file changed, 64 deletions(-) delete mode 100644 .github/workflows/acceptance-tests.yml diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml deleted file mode 100644 index f0faa073c5..0000000000 --- a/.github/workflows/acceptance-tests.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Acceptance Tests -on: pull_request -jobs: - acceptance-tests: - timeout-minutes: 60 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - name: Run Docker Compose and Install Dependencies in Parallel - env: - ZITADEL_DEV_UID: root - run: | - echo "Start docker-compose in the background" - docker compose -f ./acceptance/docker-compose.yaml run setup & - DOCKER_COMPOSE_PID=$! - echo "Docker Compose PID: $DOCKER_COMPOSE_PID" - - echo "Install app dependencies in the foreground" - npm install -g pnpm && pnpm install - - echo "Install Test Browsers in the background" - pnpm exec playwright install --with-deps & - INSTALL_BROWSERS_PID=$! - echo "Install browsers PID: $INSTALL_BROWSERS_PID" - - echo "Generate gRPC stubs in the background" - pnpm generate & - GENERATE_PID=$! - echo "Generate stubs PID: $GENERATE_PID" - - # Wait for docker-compose - wait $DOCKER_COMPOSE_PID - if [ $? -ne 0 ]; then - echo "Docker Compose failed" - exit 1 - fi - - # Wait for Playwright browser installation - wait $INSTALL_BROWSERS_PID - if [ $? -ne 0 ]; then - echo "Playwright browser installation failed" - exit 1 - fi - - # Wait for gRPC stubs generation - wait $GENERATE_PID - if [ $? -ne 0 ]; then - echo "gRPC stubs generation failed" - exit 1 - fi - - echo "Generate and create a production build in the foreground" - pnpm build - - name: Run Playwright Tests - run: pnpm exec playwright test - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 From e58e8c55b4585352ec3323ff78b963d71c172ba3 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Fri, 18 Oct 2024 23:07:31 +0200 Subject: [PATCH 308/640] generate before build --- packages/zitadel-client/turbo.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/zitadel-client/turbo.json b/packages/zitadel-client/turbo.json index 52e8c763f0..ea36369013 100644 --- a/packages/zitadel-client/turbo.json +++ b/packages/zitadel-client/turbo.json @@ -6,6 +6,9 @@ "build": { "outputs": [ "dist/**" + ], + "dependsOn": [ + "@zitadel/proto#generate" ] } } From 087e81547222175106409802e75fe05240cb5542 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Fri, 18 Oct 2024 23:16:22 +0200 Subject: [PATCH 309/640] fix conditions --- .github/workflows/test.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 786f990eea..6e1a7a10d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,9 +57,9 @@ jobs: run: CYPRESS_INSTALL_BINARY=0 pnpm install # We build already here so we can fail fast if the build fails - - name: Run ZITADEL + - name: Build Production run: pnpm build - if: ${{ matrix.command }} == "test:acceptance" + if: ${{ matrix.command == 'test:acceptance' }} - run: | echo "CYPRESS_VERSION=$(pnpm list -r | grep cypress | cut -d ' ' -f 2)" >> $GITHUB_ENV @@ -72,11 +72,11 @@ jobs: key: ${{ runner.os }}-cypress-binary-${{ env.CYPRESS_VERSION }} restore-keys: | ${{ runner.os }}-cypress-binary- - if: ${{ matrix.command }} == "test:integration" + if: ${{ matrix.command == 'test:integration' }} - name: Install Cypress Browsers run: pnpm install - if: ${{ matrix.command }} == "test:integration" + if: ${{ matrix.command == 'test:integration' }} - uses: actions/cache@v4.0.2 name: Setup Playwright binary cache @@ -85,19 +85,19 @@ jobs: key: ${{ runner.os }}-playwright-binary-${{ env.PLAYWRIGHT_VERSION }} restore-keys: | ${{ runner.os }}-playwright-binary- - if: ${{ matrix.command }} == "test:acceptance" + if: ${{ matrix.command == 'test:acceptance' }} - name: Install Playwright Browsers run: sudo pnpm exec playwright install --with-deps - if: ${{ matrix.command }} == "test:acceptance" + if: ${{ matrix.command == 'test:acceptance' }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - if: ${{ matrix.command }} == "test:acceptance" + if: ${{ matrix.command == 'test:acceptance' }} - name: Run ZITADEL run: pnpm run-zitadel - if: ${{ matrix.command }} == "test:acceptance" + if: ${{ matrix.command == 'test:acceptance' }} - name: Check id: check From 90f13b210e29fd4644b25fb0e1773d7b7e2e2784 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Fri, 18 Oct 2024 23:23:08 +0200 Subject: [PATCH 310/640] build before start --- .github/workflows/test.yml | 13 +++++------- apps/login/package.json | 41 +++++++++++++++++++------------------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6e1a7a10d4..456f4917ce 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -56,14 +56,8 @@ jobs: - name: Install Dependencies run: CYPRESS_INSTALL_BINARY=0 pnpm install - # We build already here so we can fail fast if the build fails - - name: Build Production - run: pnpm build - if: ${{ matrix.command == 'test:acceptance' }} - - - run: | - echo "CYPRESS_VERSION=$(pnpm list -r | grep cypress | cut -d ' ' -f 2)" >> $GITHUB_ENV - echo "PLAYWRIGHT_VERSION=$(npx playwright --version | cut -d ' ' -f 2)" >> $GITHUB_ENV + - run: echo "CYPRESS_VERSION=$(pnpm list -r | grep cypress | cut -d ' ' -f 2)" >> $GITHUB_ENV + if: ${{ matrix.command == 'test:integration' }} - uses: actions/cache@v4.0.2 name: Setup Cypress binary cache @@ -78,6 +72,9 @@ jobs: run: pnpm install if: ${{ matrix.command == 'test:integration' }} + - run: echo "PLAYWRIGHT_VERSION=$(npx playwright --version | cut -d ' ' -f 2)" >> $GITHUB_ENV + if: ${{ matrix.command == 'test:acceptance' }} + - uses: actions/cache@v4.0.2 name: Setup Playwright binary cache with: diff --git a/apps/login/package.json b/apps/login/package.json index 8863d361e7..8399143216 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -1,7 +1,6 @@ { "name": "@zitadel/login", "private": true, - "type": "module", "scripts": { "dev": "next dev", "test": "concurrently --timings --kill-others-on-fail 'npm:test:unit' 'npm:test:integration'", @@ -22,6 +21,7 @@ "lint:fix": "prettier --write .", "lint-staged": "lint-staged", "build": "next build", + "prestart": "pnpm build", "start": "next start", "clean": "pnpm mock:destroy && rm -rf .turbo && rm -rf node_modules && rm -rf .next" }, @@ -32,19 +32,19 @@ "*": "prettier --write --ignore-unknown" }, "dependencies": { - "@headlessui/react": "^2.1.9", + "@headlessui/react": "^1.7.18", "@heroicons/react": "2.1.3", "@tailwindcss/forms": "0.5.7", "@vercel/analytics": "^1.2.2", "@zitadel/client": "workspace:*", + "@zitadel/next": "workspace:*", "@zitadel/node": "workspace:*", "@zitadel/proto": "workspace:*", + "@zitadel/react": "workspace:*", "clsx": "1.2.1", "copy-to-clipboard": "^3.3.3", - "deepmerge": "^4.3.1", "moment": "^2.29.4", - "next": "14.2.14", - "next-intl": "^3.20.0", + "next": "14.2.5", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", "qrcode.react": "^3.1.0", @@ -55,12 +55,12 @@ "tinycolor2": "1.4.2" }, "devDependencies": { - "@bufbuild/buf": "^1.41.0", + "@bufbuild/buf": "^1.36.0", "@testing-library/jest-dom": "^6.4.5", - "@testing-library/react": "^16.0.1", + "@testing-library/react": "^16.0.0", "@types/ms": "0.7.34", - "@types/node": "22.5.5", - "@types/react": "18.3.7", + "@types/node": "22.1.0", + "@types/react": "18.3.3", "@types/react-dom": "18.3.0", "@types/tinycolor2": "1.4.3", "@types/uuid": "^10.0.0", @@ -68,23 +68,22 @@ "@zitadel/prettier-config": "workspace:*", "@zitadel/tsconfig": "workspace:*", "autoprefixer": "10.4.20", - "concurrently": "^9.0.1", - "cypress": "^13.14.2", + "concurrently": "^8.1.0", + "cypress": "^13.13.2", "del-cli": "5.1.0", "env-cmd": "^10.1.0", "eslint-config-zitadel": "workspace:*", "grpc-tools": "1.12.4", - "jsdom": "^25.0.0", - "lint-staged": "15.2.10", + "lint-staged": "15.2.8", "make-dir-cli": "4.0.0", - "nodemon": "^3.1.5", - "postcss": "8.4.47", - "prettier-plugin-tailwindcss": "0.6.6", - "sass": "^1.79.1", - "start-server-and-test": "^2.0.8", - "tailwindcss": "3.4.13", - "ts-proto": "^2.2.0", - "typescript": "^5.6.2", + "nodemon": "^3.1.4", + "postcss": "8.4.41", + "prettier-plugin-tailwindcss": "0.6.5", + "sass": "^1.77.1", + "start-server-and-test": "^2.0.0", + "tailwindcss": "3.4.9", + "ts-proto": "^1.139.0", + "typescript": "^5.4.5", "zitadel-tailwind-config": "workspace:*" } } From 46fb2ea259e106e2b3a31d938021be7c66723719 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Fri, 18 Oct 2024 23:44:32 +0200 Subject: [PATCH 311/640] remove workspace packages --- apps/login/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/login/package.json b/apps/login/package.json index 8399143216..16479b6441 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -37,10 +37,8 @@ "@tailwindcss/forms": "0.5.7", "@vercel/analytics": "^1.2.2", "@zitadel/client": "workspace:*", - "@zitadel/next": "workspace:*", "@zitadel/node": "workspace:*", "@zitadel/proto": "workspace:*", - "@zitadel/react": "workspace:*", "clsx": "1.2.1", "copy-to-clipboard": "^3.3.3", "moment": "^2.29.4", From 083bf5a0a565b6fb1a6e62310fff0e96e8634a30 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Sat, 19 Oct 2024 00:09:29 +0200 Subject: [PATCH 312/640] clean dependencies --- apps/login/package.json | 38 +-- pnpm-lock.yaml | 48 +-- tests-examples/demo-todo-app.spec.ts | 437 --------------------------- 3 files changed, 45 insertions(+), 478 deletions(-) delete mode 100644 tests-examples/demo-todo-app.spec.ts diff --git a/apps/login/package.json b/apps/login/package.json index 16479b6441..9617300a2e 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -1,6 +1,7 @@ { "name": "@zitadel/login", "private": true, + "type": "module", "scripts": { "dev": "next dev", "test": "concurrently --timings --kill-others-on-fail 'npm:test:unit' 'npm:test:integration'", @@ -32,7 +33,7 @@ "*": "prettier --write --ignore-unknown" }, "dependencies": { - "@headlessui/react": "^1.7.18", + "@headlessui/react": "^2.1.9", "@heroicons/react": "2.1.3", "@tailwindcss/forms": "0.5.7", "@vercel/analytics": "^1.2.2", @@ -41,8 +42,10 @@ "@zitadel/proto": "workspace:*", "clsx": "1.2.1", "copy-to-clipboard": "^3.3.3", + "deepmerge": "^4.3.1", "moment": "^2.29.4", - "next": "14.2.5", + "next": "14.2.14", + "next-intl": "^3.20.0", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", "qrcode.react": "^3.1.0", @@ -53,12 +56,12 @@ "tinycolor2": "1.4.2" }, "devDependencies": { - "@bufbuild/buf": "^1.36.0", + "@bufbuild/buf": "^1.41.0", "@testing-library/jest-dom": "^6.4.5", - "@testing-library/react": "^16.0.0", + "@testing-library/react": "^16.0.1", "@types/ms": "0.7.34", - "@types/node": "22.1.0", - "@types/react": "18.3.3", + "@types/node": "22.5.5", + "@types/react": "18.3.7", "@types/react-dom": "18.3.0", "@types/tinycolor2": "1.4.3", "@types/uuid": "^10.0.0", @@ -66,22 +69,23 @@ "@zitadel/prettier-config": "workspace:*", "@zitadel/tsconfig": "workspace:*", "autoprefixer": "10.4.20", - "concurrently": "^8.1.0", - "cypress": "^13.13.2", + "concurrently": "^9.0.1", + "cypress": "^13.14.2", "del-cli": "5.1.0", "env-cmd": "^10.1.0", "eslint-config-zitadel": "workspace:*", "grpc-tools": "1.12.4", - "lint-staged": "15.2.8", + "jsdom": "^25.0.0", + "lint-staged": "15.2.10", "make-dir-cli": "4.0.0", - "nodemon": "^3.1.4", - "postcss": "8.4.41", - "prettier-plugin-tailwindcss": "0.6.5", - "sass": "^1.77.1", - "start-server-and-test": "^2.0.0", - "tailwindcss": "3.4.9", - "ts-proto": "^1.139.0", - "typescript": "^5.4.5", + "nodemon": "^3.1.5", + "postcss": "8.4.47", + "prettier-plugin-tailwindcss": "0.6.6", + "sass": "^1.79.1", + "start-server-and-test": "^2.0.8", + "tailwindcss": "3.4.13", + "ts-proto": "^2.2.0", + "typescript": "^5.6.2", "zitadel-tailwind-config": "workspace:*" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ec76ca2d2..8afc164297 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,10 +19,10 @@ importers: version: 1.48.1 '@types/node': specifier: ^22.7.5 - version: 22.7.5 + version: 22.7.6 '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.3.1(vite@5.4.6(@types/node@22.7.5)(sass@1.79.1)) + version: 4.3.1(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1)) '@zitadel/prettier-config': specifier: workspace:* version: link:packages/zitadel-prettier-config @@ -49,10 +49,10 @@ importers: version: 5.6.2 vite-tsconfig-paths: specifier: ^5.0.1 - version: 5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.7.5)(sass@1.79.1)) + version: 5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1)) vitest: specifier: ^2.1.1 - version: 2.1.1(@types/node@22.7.5)(jsdom@25.0.0)(sass@1.79.1) + version: 2.1.1(@types/node@22.7.6)(jsdom@25.0.0)(sass@1.79.1) apps/login: dependencies: @@ -1281,8 +1281,8 @@ packages: '@types/node@22.5.5': resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} - '@types/node@22.7.5': - resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} + '@types/node@22.7.6': + resolution: {integrity: sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -5622,7 +5622,7 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/node@22.7.5': + '@types/node@22.7.6': dependencies: undici-types: 6.19.8 @@ -5651,7 +5651,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.5.5 + '@types/node': 22.7.6 optional: true '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2)': @@ -5705,14 +5705,14 @@ snapshots: '@vercel/git-hooks@1.0.0': {} - '@vitejs/plugin-react@4.3.1(vite@5.4.6(@types/node@22.7.5)(sass@1.79.1))': + '@vitejs/plugin-react@4.3.1(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1))': dependencies: '@babel/core': 7.25.2 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.6(@types/node@22.7.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) transitivePeerDependencies: - supports-color @@ -5723,13 +5723,13 @@ snapshots: chai: 5.1.1 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.7.5)(sass@1.79.1))': + '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1))': dependencies: '@vitest/spy': 2.1.1 estree-walker: 3.0.3 magic-string: 0.30.11 optionalDependencies: - vite: 5.4.6(@types/node@22.7.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) '@vitest/pretty-format@2.1.1': dependencies: @@ -8212,7 +8212,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.5.5 + '@types/node': 22.7.6 long: 5.2.3 proxy-from-env@1.0.0: {} @@ -9066,12 +9066,12 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - vite-node@2.1.1(@types/node@22.7.5)(sass@1.79.1): + vite-node@2.1.1(@types/node@22.7.6)(sass@1.79.1): dependencies: cac: 6.7.14 debug: 4.3.7(supports-color@5.5.0) pathe: 1.1.2 - vite: 5.4.6(@types/node@22.7.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) transitivePeerDependencies: - '@types/node' - less @@ -9083,31 +9083,31 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.7.5)(sass@1.79.1)): + vite-tsconfig-paths@5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1)): dependencies: debug: 4.3.6 globrex: 0.1.2 tsconfck: 3.1.1(typescript@5.6.2) optionalDependencies: - vite: 5.4.6(@types/node@22.7.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) transitivePeerDependencies: - supports-color - typescript - vite@5.4.6(@types/node@22.7.5)(sass@1.79.1): + vite@5.4.6(@types/node@22.7.6)(sass@1.79.1): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.21.3 optionalDependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 fsevents: 2.3.3 sass: 1.79.1 - vitest@2.1.1(@types/node@22.7.5)(jsdom@25.0.0)(sass@1.79.1): + vitest@2.1.1(@types/node@22.7.6)(jsdom@25.0.0)(sass@1.79.1): dependencies: '@vitest/expect': 2.1.1 - '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.7.5)(sass@1.79.1)) + '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1)) '@vitest/pretty-format': 2.1.1 '@vitest/runner': 2.1.1 '@vitest/snapshot': 2.1.1 @@ -9122,11 +9122,11 @@ snapshots: tinyexec: 0.3.0 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.6(@types/node@22.7.5)(sass@1.79.1) - vite-node: 2.1.1(@types/node@22.7.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) + vite-node: 2.1.1(@types/node@22.7.6)(sass@1.79.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 jsdom: 25.0.0 transitivePeerDependencies: - less diff --git a/tests-examples/demo-todo-app.spec.ts b/tests-examples/demo-todo-app.spec.ts deleted file mode 100644 index 8641cb5f5d..0000000000 --- a/tests-examples/demo-todo-app.spec.ts +++ /dev/null @@ -1,437 +0,0 @@ -import { test, expect, type Page } from '@playwright/test'; - -test.beforeEach(async ({ page }) => { - await page.goto('https://demo.playwright.dev/todomvc'); -}); - -const TODO_ITEMS = [ - 'buy some cheese', - 'feed the cat', - 'book a doctors appointment' -] as const; - -test.describe('New Todo', () => { - test('should allow me to add todo items', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create 1st todo. - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - // Make sure the list only has one todo item. - await expect(page.getByTestId('todo-title')).toHaveText([ - TODO_ITEMS[0] - ]); - - // Create 2nd todo. - await newTodo.fill(TODO_ITEMS[1]); - await newTodo.press('Enter'); - - // Make sure the list now has two todo items. - await expect(page.getByTestId('todo-title')).toHaveText([ - TODO_ITEMS[0], - TODO_ITEMS[1] - ]); - - await checkNumberOfTodosInLocalStorage(page, 2); - }); - - test('should clear text input field when an item is added', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create one todo item. - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - // Check that input is empty. - await expect(newTodo).toBeEmpty(); - await checkNumberOfTodosInLocalStorage(page, 1); - }); - - test('should append new items to the bottom of the list', async ({ page }) => { - // Create 3 items. - await createDefaultTodos(page); - - // create a todo count locator - const todoCount = page.getByTestId('todo-count') - - // Check test using different methods. - await expect(page.getByText('3 items left')).toBeVisible(); - await expect(todoCount).toHaveText('3 items left'); - await expect(todoCount).toContainText('3'); - await expect(todoCount).toHaveText(/3/); - - // Check all items in one call. - await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); - await checkNumberOfTodosInLocalStorage(page, 3); - }); -}); - -test.describe('Mark all as completed', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test.afterEach(async ({ page }) => { - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should allow me to mark all items as completed', async ({ page }) => { - // Complete all todos. - await page.getByLabel('Mark all as complete').check(); - - // Ensure all todos have 'completed' class. - await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - }); - - test('should allow me to clear the complete state of all items', async ({ page }) => { - const toggleAll = page.getByLabel('Mark all as complete'); - // Check and then immediately uncheck. - await toggleAll.check(); - await toggleAll.uncheck(); - - // Should be no completed classes. - await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); - }); - - test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { - const toggleAll = page.getByLabel('Mark all as complete'); - await toggleAll.check(); - await expect(toggleAll).toBeChecked(); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - - // Uncheck first todo. - const firstTodo = page.getByTestId('todo-item').nth(0); - await firstTodo.getByRole('checkbox').uncheck(); - - // Reuse toggleAll locator and make sure its not checked. - await expect(toggleAll).not.toBeChecked(); - - await firstTodo.getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - - // Assert the toggle all is checked again. - await expect(toggleAll).toBeChecked(); - }); -}); - -test.describe('Item', () => { - - test('should allow me to mark items as complete', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create two items. - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - // Check first item. - const firstTodo = page.getByTestId('todo-item').nth(0); - await firstTodo.getByRole('checkbox').check(); - await expect(firstTodo).toHaveClass('completed'); - - // Check second item. - const secondTodo = page.getByTestId('todo-item').nth(1); - await expect(secondTodo).not.toHaveClass('completed'); - await secondTodo.getByRole('checkbox').check(); - - // Assert completed class. - await expect(firstTodo).toHaveClass('completed'); - await expect(secondTodo).toHaveClass('completed'); - }); - - test('should allow me to un-mark items as complete', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create two items. - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - const firstTodo = page.getByTestId('todo-item').nth(0); - const secondTodo = page.getByTestId('todo-item').nth(1); - const firstTodoCheckbox = firstTodo.getByRole('checkbox'); - - await firstTodoCheckbox.check(); - await expect(firstTodo).toHaveClass('completed'); - await expect(secondTodo).not.toHaveClass('completed'); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - await firstTodoCheckbox.uncheck(); - await expect(firstTodo).not.toHaveClass('completed'); - await expect(secondTodo).not.toHaveClass('completed'); - await checkNumberOfCompletedTodosInLocalStorage(page, 0); - }); - - test('should allow me to edit an item', async ({ page }) => { - await createDefaultTodos(page); - - const todoItems = page.getByTestId('todo-item'); - const secondTodo = todoItems.nth(1); - await secondTodo.dblclick(); - await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); - await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); - - // Explicitly assert the new text value. - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2] - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); -}); - -test.describe('Editing', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should hide other controls when editing', async ({ page }) => { - const todoItem = page.getByTestId('todo-item').nth(1); - await todoItem.dblclick(); - await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); - await expect(todoItem.locator('label', { - hasText: TODO_ITEMS[1], - })).not.toBeVisible(); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should save edits on blur', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2], - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); - - test('should trim entered text', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2], - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); - - test('should remove the item if an empty text string was entered', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - TODO_ITEMS[2], - ]); - }); - - test('should cancel edits on escape', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); - await expect(todoItems).toHaveText(TODO_ITEMS); - }); -}); - -test.describe('Counter', () => { - test('should display the current number of todo items', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // create a todo count locator - const todoCount = page.getByTestId('todo-count') - - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - await expect(todoCount).toContainText('1'); - - await newTodo.fill(TODO_ITEMS[1]); - await newTodo.press('Enter'); - await expect(todoCount).toContainText('2'); - - await checkNumberOfTodosInLocalStorage(page, 2); - }); -}); - -test.describe('Clear completed button', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - }); - - test('should display the correct text', async ({ page }) => { - await page.locator('.todo-list li .toggle').first().check(); - await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); - }); - - test('should remove completed items when clicked', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).getByRole('checkbox').check(); - await page.getByRole('button', { name: 'Clear completed' }).click(); - await expect(todoItems).toHaveCount(2); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); - }); - - test('should be hidden when there are no items that are completed', async ({ page }) => { - await page.locator('.todo-list li .toggle').first().check(); - await page.getByRole('button', { name: 'Clear completed' }).click(); - await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); - }); -}); - -test.describe('Persistence', () => { - test('should persist its data', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - const todoItems = page.getByTestId('todo-item'); - const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); - await firstTodoCheck.check(); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); - await expect(firstTodoCheck).toBeChecked(); - await expect(todoItems).toHaveClass(['completed', '']); - - // Ensure there is 1 completed item. - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - // Now reload. - await page.reload(); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); - await expect(firstTodoCheck).toBeChecked(); - await expect(todoItems).toHaveClass(['completed', '']); - }); -}); - -test.describe('Routing', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - // make sure the app had a chance to save updated todos in storage - // before navigating to a new view, otherwise the items can get lost :( - // in some frameworks like Durandal - await checkTodosInLocalStorage(page, TODO_ITEMS[0]); - }); - - test('should allow me to display active items', async ({ page }) => { - const todoItem = page.getByTestId('todo-item'); - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Active' }).click(); - await expect(todoItem).toHaveCount(2); - await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); - }); - - test('should respect the back button', async ({ page }) => { - const todoItem = page.getByTestId('todo-item'); - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - await test.step('Showing all items', async () => { - await page.getByRole('link', { name: 'All' }).click(); - await expect(todoItem).toHaveCount(3); - }); - - await test.step('Showing active items', async () => { - await page.getByRole('link', { name: 'Active' }).click(); - }); - - await test.step('Showing completed items', async () => { - await page.getByRole('link', { name: 'Completed' }).click(); - }); - - await expect(todoItem).toHaveCount(1); - await page.goBack(); - await expect(todoItem).toHaveCount(2); - await page.goBack(); - await expect(todoItem).toHaveCount(3); - }); - - test('should allow me to display completed items', async ({ page }) => { - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Completed' }).click(); - await expect(page.getByTestId('todo-item')).toHaveCount(1); - }); - - test('should allow me to display all items', async ({ page }) => { - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Active' }).click(); - await page.getByRole('link', { name: 'Completed' }).click(); - await page.getByRole('link', { name: 'All' }).click(); - await expect(page.getByTestId('todo-item')).toHaveCount(3); - }); - - test('should highlight the currently applied filter', async ({ page }) => { - await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); - - //create locators for active and completed links - const activeLink = page.getByRole('link', { name: 'Active' }); - const completedLink = page.getByRole('link', { name: 'Completed' }); - await activeLink.click(); - - // Page change - active items. - await expect(activeLink).toHaveClass('selected'); - await completedLink.click(); - - // Page change - completed items. - await expect(completedLink).toHaveClass('selected'); - }); -}); - -async function createDefaultTodos(page: Page) { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - for (const item of TODO_ITEMS) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } -} - -async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).length === e; - }, expected); -} - -async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; - }, expected); -} - -async function checkTodosInLocalStorage(page: Page, title: string) { - return await page.waitForFunction(t => { - return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); - }, title); -} From 8290a6bae38659c5e5349950b4e8c32c466b48ae Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Sat, 19 Oct 2024 00:11:42 +0200 Subject: [PATCH 313/640] no sudo --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 456f4917ce..a1868847a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -85,7 +85,7 @@ jobs: if: ${{ matrix.command == 'test:acceptance' }} - name: Install Playwright Browsers - run: sudo pnpm exec playwright install --with-deps + run: pnpm exec playwright install --with-deps if: ${{ matrix.command == 'test:acceptance' }} - name: Set up Docker Buildx From 3fc6118609296ef6ed5bf647f11922c0bb6fa46d Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Sat, 19 Oct 2024 00:24:28 +0200 Subject: [PATCH 314/640] keep dir --- acceptance/pat/.gitignore | 3 ++- acceptance/pat/.gitkeep | 0 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 acceptance/pat/.gitkeep diff --git a/acceptance/pat/.gitignore b/acceptance/pat/.gitignore index f59ec20aab..f0fa09f556 100644 --- a/acceptance/pat/.gitignore +++ b/acceptance/pat/.gitignore @@ -1 +1,2 @@ -* \ No newline at end of file +* +!.gitkeep \ No newline at end of file diff --git a/acceptance/pat/.gitkeep b/acceptance/pat/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 From 7cde73f8795485e6baa452d84f28cc68739d28f9 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Sat, 19 Oct 2024 00:27:39 +0200 Subject: [PATCH 315/640] fmt --- README.md | 1 + acceptance/tests/username-password.spec.ts | 16 +++++------ acceptance/tests/welcome.ts | 8 +++--- package.json | 1 + playwright.config.ts | 32 +++++++++++----------- 5 files changed, 30 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 9d6b6354d2..f1f8a9303d 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,7 @@ ZITADEL_API_URL=https://zitadel-tlx3du.us1.zitadel.cloud ZITADEL_SERVICE_USER_ID=289106423158521850 ZITADEL_SERVICE_USER_TOKEN=1S6w48thfWFI2klgfwkCnhXJLf9FQ457E-_3H74ePQxfO3Af0Tm4V5Xi-ji7urIl_xbn-Rk ``` + Start the login application in dev mode: diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index 72f2d86780..276a03909d 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -1,12 +1,12 @@ -import { test, expect } from '@playwright/test'; +import { test } from "@playwright/test"; -test('username and password', async ({ page }) => { - await page.goto('/'); - const loginname = page.getByLabel('Loginname') +test("username and password", async ({ page }) => { + await page.goto("/"); + const loginname = page.getByLabel("Loginname"); await loginname.pressSequentially("zitadel-admin@zitadel.localhost"); - await loginname.press( 'Enter'); - const password = page.getByLabel('Password') + await loginname.press("Enter"); + const password = page.getByLabel("Password"); await password.pressSequentially("Password1!"); - await password.press( 'Enter'); - await page.getByText('Skip').click(); + await password.press("Enter"); + await page.getByText("Skip").click(); }); diff --git a/acceptance/tests/welcome.ts b/acceptance/tests/welcome.ts index 22734b0c11..7ff6b7d1c5 100644 --- a/acceptance/tests/welcome.ts +++ b/acceptance/tests/welcome.ts @@ -1,6 +1,6 @@ -import { test } from '@playwright/test'; +import { test } from "@playwright/test"; -test('login is accessible', async ({ page }) => { - await page.goto('http://localhost:3000/'); - await page.getByRole('heading', { name: 'Welcome back!' }).isVisible(); +test("login is accessible", async ({ page }) => { + await page.goto("http://localhost:3000/"); + await page.getByRole("heading", { name: "Welcome back!" }).isVisible(); }); diff --git a/package.json b/package.json index 95a38ef542..774a7612fd 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", "clean": "turbo run clean && rm -rf node_modules", + "format:fix": "prettier --write \"**/*.{ts,tsx,md}\"", "format": "prettier --check \"**/*.{ts,tsx,md}\"", "changeset": "changeset", "version-packages": "changeset version", diff --git a/playwright.config.ts b/playwright.config.ts index 9795c4abb0..8bddf81933 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, devices } from '@playwright/test'; +import { defineConfig, devices } from "@playwright/test"; /** * Read environment variables from file. @@ -12,7 +12,7 @@ import { defineConfig, devices } from '@playwright/test'; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './acceptance/tests', + testDir: "./acceptance/tests", /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -22,28 +22,28 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + reporter: "html", /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://127.0.0.1:3000', + baseURL: "http://127.0.0.1:3000", /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', + trace: "on-first-retry", }, /* Configure projects for major browsers */ projects: [ { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + name: "chromium", + use: { ...devices["Desktop Chrome"] }, }, { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, + name: "firefox", + use: { ...devices["Desktop Firefox"] }, }, -/* TODO: webkit fails. Is this a bug? + /* TODO: webkit fails. Is this a bug? { name: 'webkit', use: { ...devices['Desktop Safari'] }, @@ -72,10 +72,10 @@ export default defineConfig({ ], /* Run local dev server before starting the tests */ - webServer: { - command: 'pnpm start', - url: 'http://127.0.0.1:3000', - reuseExistingServer: !process.env.CI, - timeout: 5 * 60_000, - }, + webServer: { + command: "pnpm start", + url: "http://127.0.0.1:3000", + reuseExistingServer: !process.env.CI, + timeout: 5 * 60_000, + }, }); From 614bb5ddb17f1f066f9376d04bf8c90617c3bba6 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Sat, 19 Oct 2024 00:35:29 +0200 Subject: [PATCH 316/640] debug path --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a1868847a9..42d280b7b2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,6 +31,8 @@ jobs: - name: Checkout Repo uses: actions/checkout@v4.1.6 + - run : ls -la acceptance/pat + - name: Setup Node.js 20.x uses: actions/setup-node@v4.0.2 with: From edffb9929611793f63961432f78c614a42278466 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Sat, 19 Oct 2024 00:36:58 +0200 Subject: [PATCH 317/640] run zitadel as root --- .github/workflows/test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 42d280b7b2..72296472d7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,8 +31,6 @@ jobs: - name: Checkout Repo uses: actions/checkout@v4.1.6 - - run : ls -la acceptance/pat - - name: Setup Node.js 20.x uses: actions/setup-node@v4.0.2 with: @@ -95,7 +93,7 @@ jobs: if: ${{ matrix.command == 'test:acceptance' }} - name: Run ZITADEL - run: pnpm run-zitadel + run: ZITADEL_DEV_UID=root pnpm run-zitadel if: ${{ matrix.command == 'test:acceptance' }} - name: Check From 0459bea8dd8fb632d28b805d65bcf7f7b5768d3c Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Sat, 19 Oct 2024 00:44:10 +0200 Subject: [PATCH 318/640] build --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 72296472d7..8be38dee6f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -96,6 +96,10 @@ jobs: run: ZITADEL_DEV_UID=root pnpm run-zitadel if: ${{ matrix.command == 'test:acceptance' }} + - name: Install Playwright Browsers + run: pnpm build + if: ${{ matrix.command == 'test:acceptance' }} + - name: Check id: check run: pnpm ${{ matrix.command }} From bdfbd9466144a440142a366bee83f8ed58b1f0be Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Sat, 19 Oct 2024 01:13:04 +0200 Subject: [PATCH 319/640] no prestart --- apps/login/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/login/package.json b/apps/login/package.json index 9617300a2e..8863d361e7 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -22,7 +22,6 @@ "lint:fix": "prettier --write .", "lint-staged": "lint-staged", "build": "next build", - "prestart": "pnpm build", "start": "next start", "clean": "pnpm mock:destroy && rm -rf .turbo && rm -rf node_modules && rm -rf .next" }, From 3ce170305599f50edffff3959aa8915db435e69e Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Sat, 19 Oct 2024 01:16:59 +0200 Subject: [PATCH 320/640] start already built --- apps/login/package.json | 2 ++ package.json | 1 + playwright.config.ts | 2 +- turbo.json | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/login/package.json b/apps/login/package.json index 8863d361e7..541766325f 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -22,7 +22,9 @@ "lint:fix": "prettier --write .", "lint-staged": "lint-staged", "build": "next build", + "prestart": "pnpm build", "start": "next start", + "start:built": "next start", "clean": "pnpm mock:destroy && rm -rf .turbo && rm -rf node_modules && rm -rf .next" }, "git": { diff --git a/package.json b/package.json index 774a7612fd..6b5e27cc7d 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "build": "turbo run build", "test": "turbo run test", "start": "turbo run start", + "start:built": "turbo run start:built", "test:unit": "turbo run test:unit -- --passWithNoTests", "test:integration": "turbo run test:integration", "test:acceptance": "pnpm exec playwright test", diff --git a/playwright.config.ts b/playwright.config.ts index 8bddf81933..bf73cb21d1 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -73,7 +73,7 @@ export default defineConfig({ /* Run local dev server before starting the tests */ webServer: { - command: "pnpm start", + command: "pnpm start:built", url: "http://127.0.0.1:3000", reuseExistingServer: !process.env.CI, timeout: 5 * 60_000, diff --git a/turbo.json b/turbo.json index e2412dfe97..a98ff8726d 100644 --- a/turbo.json +++ b/turbo.json @@ -22,6 +22,7 @@ "build": {}, "test": {}, "start": {}, + "start:built": {}, "test:unit": {}, "test:integration": {}, "test:watch": { From cc53c44c2089f43b07ae3906c9dc2b8df6be4ad3 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Sat, 19 Oct 2024 01:27:25 +0200 Subject: [PATCH 321/640] update test --- acceptance/tests/username-password.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index 276a03909d..23b22f4bfe 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -8,5 +8,5 @@ test("username and password", async ({ page }) => { const password = page.getByLabel("Password"); await password.pressSequentially("Password1!"); await password.press("Enter"); - await page.getByText("Skip").click(); + await page.getByRole("heading", {name: "Welcome ZITADEL Admin!"}).click(); }); From bfd8a7c9e1697b2aa21feed4968c4a0fa56daf60 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Sat, 19 Oct 2024 01:30:20 +0200 Subject: [PATCH 322/640] fmt --- acceptance/tests/username-password.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index 23b22f4bfe..31d165358e 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -8,5 +8,5 @@ test("username and password", async ({ page }) => { const password = page.getByLabel("Password"); await password.pressSequentially("Password1!"); await password.press("Enter"); - await page.getByRole("heading", {name: "Welcome ZITADEL Admin!"}).click(); + await page.getByRole("heading", { name: "Welcome ZITADEL Admin!" }).click(); }); From 98a5e042b24cbfc01b9d2006805cf98af88480a2 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 21 Oct 2024 10:54:53 +0200 Subject: [PATCH 323/640] auth methods on verify page --- apps/login/src/app/(login)/verify/page.tsx | 4 +- apps/login/src/components/auth-methods.tsx | 68 +++++++++++++++++++ .../src/components/verify-email-form.tsx | 45 ++++++++++-- apps/login/src/lib/server/email.ts | 28 +++++++- apps/login/src/lib/zitadel.ts | 2 +- 5 files changed, 135 insertions(+), 12 deletions(-) diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 07e00e7a08..09723e9706 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -15,9 +15,9 @@ export default async function Page({ searchParams }: { searchParams: any }) { loginName, sessionId, code, - submit, organization, authRequestId, + invite, } = searchParams; const branding = await getBrandingSettings(organization); @@ -41,11 +41,11 @@ export default async function Page({ searchParams }: { searchParams: any }) { userId={userId} loginName={loginName} code={code} - submit={submit === "true"} organization={organization} authRequestId={authRequestId} sessionId={sessionId} loginSettings={loginSettings} + isInvite={invite === "true"} /> ) : (
    diff --git a/apps/login/src/components/auth-methods.tsx b/apps/login/src/components/auth-methods.tsx index de40ab615b..fb65c38494 100644 --- a/apps/login/src/components/auth-methods.tsx +++ b/apps/login/src/components/auth-methods.tsx @@ -194,6 +194,74 @@ export const SMS = (alreadyAdded: boolean, link: string) => { ); }; +export const PASSKEYS = (alreadyAdded: boolean, link: string) => { + return ( + +
    + + + + Passkeys +
    + {alreadyAdded && ( + <> + + + )} +
    + ); +}; + +export const PASSWORD = (alreadyAdded: boolean, link: string) => { + return ( + +
    + + + + Password +
    + {alreadyAdded && ( + <> + + + )} +
    + ); +}; + function Setup() { return (
    diff --git a/apps/login/src/components/verify-email-form.tsx b/apps/login/src/components/verify-email-form.tsx index 77cb9d9771..bec0a5bb6e 100644 --- a/apps/login/src/components/verify-email-form.tsx +++ b/apps/login/src/components/verify-email-form.tsx @@ -1,12 +1,14 @@ "use client"; import { Alert } from "@/components/alert"; -import { resendVerifyEmail, verifyUserByEmail } from "@/lib/server/email"; +import { resendVerifyEmail, 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 { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; +import { PASSKEYS, PASSWORD } from "./auth-methods"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; @@ -19,22 +21,22 @@ type Props = { userId: string; loginName: string; code: string; - submit: boolean; organization?: string; authRequestId?: string; sessionId?: string; loginSettings?: LoginSettings; + isInvite: boolean; }; export function VerifyEmailForm({ userId, loginName, code, - submit, organization, authRequestId, sessionId, loginSettings, + isInvite, }: Props) { const t = useTranslations("verify"); @@ -45,8 +47,12 @@ export function VerifyEmailForm({ }, }); + const [authMethods, setAuthMethods] = useState< + AuthenticationMethodType[] | null + >(null); + useEffect(() => { - if (submit && code && userId) { + 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 }); @@ -59,6 +65,21 @@ export function VerifyEmailForm({ const router = useRouter(); + const params = new URLSearchParams({}); + + if (loginName) { + params.append("loginName", loginName); + } + if (sessionId) { + params.append("sessionId", sessionId); + } + if (authRequestId) { + params.append("authRequestId", authRequestId); + } + if (organization) { + params.append("organization", organization); + } + async function resendCode() { setLoading(true); const response = await resendVerifyEmail({ @@ -73,9 +94,10 @@ export function VerifyEmailForm({ async function submitCodeAndContinue(value: Inputs): Promise { setLoading(true); - const verifyResponse = await verifyUserByEmail({ + const verifyResponse = await verifyUser({ code: value.code, userId, + isInvite: isInvite, }).catch(() => { setError("Could not verify email"); }); @@ -87,6 +109,10 @@ export function VerifyEmailForm({ return; } + if (verifyResponse.authMethodTypes) { + setAuthMethods(verifyResponse.authMethodTypes); + } + const params = new URLSearchParams({}); if (organization) { @@ -102,7 +128,7 @@ export function VerifyEmailForm({ } } - return ( + return !authMethods ? (
    + ) : ( +
    + {!authMethods.includes(AuthenticationMethodType.PASSWORD) && + PASSWORD(false, "/password/set?" + params)} + {!authMethods.includes(AuthenticationMethodType.PASSKEY) && + PASSKEYS(false, "/passkeys/set?" + params)} +
    ); } diff --git a/apps/login/src/lib/server/email.ts b/apps/login/src/lib/server/email.ts index a048b117a2..51e3fc462c 100644 --- a/apps/login/src/lib/server/email.ts +++ b/apps/login/src/lib/server/email.ts @@ -1,14 +1,36 @@ "use server"; -import { resendEmailCode, verifyEmail } from "@/lib/zitadel"; +import { + listAuthenticationMethodTypes, + resendEmailCode, + verifyEmail, + verifyInviteCode, +} from "@/lib/zitadel"; type VerifyUserByEmailCommand = { userId: string; code: string; + isInvite: boolean; }; -export async function verifyUserByEmail(command: VerifyUserByEmailCommand) { - return verifyEmail(command.userId, command.code); +export async function verifyUser(command: VerifyUserByEmailCommand) { + const verifyResponse = command.isInvite + ? await verifyInviteCode(command.userId, command.code) + : await verifyEmail(command.userId, command.code); + + if (!verifyResponse) { + return { error: "Could not verify user email" }; + } + + const authMethodResponse = await listAuthenticationMethodTypes( + command.userId, + ); + + if (!authMethodResponse || !authMethodResponse.authMethodTypes) { + return { error: "Could not load possible authenticators" }; + } + + return { authMethodTypes: authMethodResponse.authMethodTypes }; } type resendVerifyEmailCommand = { diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 6eda092329..7fade047fa 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -320,7 +320,7 @@ export async function createInviteCode(userId: string, host: string | null) { if (host) { medium = { ...medium, - urlTemplate: `https://${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}`, + urlTemplate: `https://${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true`, }; } From 8edd22a6a418cfd739a1bccdaad7941aeb0dcfb9 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 21 Oct 2024 16:47:12 +0200 Subject: [PATCH 324/640] fix verify response --- apps/login/src/components/auth-methods.tsx | 12 +++--------- apps/login/src/components/verify-email-form.tsx | 15 ++++++++++++--- apps/login/src/lib/server/email.ts | 8 ++++++-- apps/login/src/lib/zitadel.ts | 9 ++++++++- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/apps/login/src/components/auth-methods.tsx b/apps/login/src/components/auth-methods.tsx index fb65c38494..3176b659d4 100644 --- a/apps/login/src/components/auth-methods.tsx +++ b/apps/login/src/components/auth-methods.tsx @@ -238,18 +238,12 @@ export const PASSWORD = (alreadyAdded: boolean, link: string) => { )} > - + form-textbox-password + Password
    diff --git a/apps/login/src/components/verify-email-form.tsx b/apps/login/src/components/verify-email-form.tsx index bec0a5bb6e..9c13bab0b9 100644 --- a/apps/login/src/components/verify-email-form.tsx +++ b/apps/login/src/components/verify-email-form.tsx @@ -1,7 +1,7 @@ "use client"; import { Alert } from "@/components/alert"; -import { resendVerifyEmail, 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 { useTranslations } from "next-intl"; @@ -49,7 +49,7 @@ export function VerifyEmailForm({ const [authMethods, setAuthMethods] = useState< AuthenticationMethodType[] | null - >(null); + >([]); useEffect(() => { if (code && userId) { @@ -82,10 +82,14 @@ export function VerifyEmailForm({ async function resendCode() { setLoading(true); - const response = await resendVerifyEmail({ + + const response = await resendVerification({ userId, + isInvite: isInvite, }).catch(() => { setError("Could not resend email"); + setLoading(false); + return; }); setLoading(false); @@ -94,12 +98,15 @@ export function VerifyEmailForm({ async function submitCodeAndContinue(value: Inputs): Promise { setLoading(true); + + console.log("verifyUser", value.code, userId, isInvite); const verifyResponse = await verifyUser({ code: value.code, userId, isInvite: isInvite, }).catch(() => { setError("Could not verify email"); + return; }); setLoading(false); @@ -111,8 +118,10 @@ export function VerifyEmailForm({ if (verifyResponse.authMethodTypes) { setAuthMethods(verifyResponse.authMethodTypes); + return; } + // if auth methods fall trough, we complete to login const params = new URLSearchParams({}); if (organization) { diff --git a/apps/login/src/lib/server/email.ts b/apps/login/src/lib/server/email.ts index 51e3fc462c..50c21ec95f 100644 --- a/apps/login/src/lib/server/email.ts +++ b/apps/login/src/lib/server/email.ts @@ -3,6 +3,7 @@ import { listAuthenticationMethodTypes, resendEmailCode, + resendInviteCode, verifyEmail, verifyInviteCode, } from "@/lib/zitadel"; @@ -35,8 +36,11 @@ export async function verifyUser(command: VerifyUserByEmailCommand) { type resendVerifyEmailCommand = { userId: string; + isInvite: boolean; }; -export async function resendVerifyEmail(command: resendVerifyEmailCommand) { - return resendEmailCode(command.userId); +export async function resendVerification(command: resendVerifyEmailCommand) { + return command.isInvite + ? resendEmailCode(command.userId) + : resendInviteCode(command.userId); } diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 7fade047fa..2f104d2f7c 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -281,7 +281,13 @@ export async function addHumanUser({ organization, }: AddHumanUserData) { return userService.addHumanUser({ - email: { email }, + email: { + email, + verification: { + case: "isVerified", + value: false, + }, + }, username: email, profile: { givenName: firstName, familyName: lastName }, organization: organization @@ -309,6 +315,7 @@ export async function verifyInviteCode( } export async function resendInviteCode(userId: string) { + console.log("resetInit"); return userService.resendInviteCode({ userId }, {}); } From 2f6ad371dd8bf31c9cef787619536e934ebe5a03 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 21 Oct 2024 16:47:37 +0200 Subject: [PATCH 325/640] revert method state --- apps/login/src/components/verify-email-form.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/login/src/components/verify-email-form.tsx b/apps/login/src/components/verify-email-form.tsx index 9c13bab0b9..c63645ba46 100644 --- a/apps/login/src/components/verify-email-form.tsx +++ b/apps/login/src/components/verify-email-form.tsx @@ -49,7 +49,7 @@ export function VerifyEmailForm({ const [authMethods, setAuthMethods] = useState< AuthenticationMethodType[] | null - >([]); + >(null); useEffect(() => { if (code && userId) { @@ -99,7 +99,6 @@ export function VerifyEmailForm({ async function submitCodeAndContinue(value: Inputs): Promise { setLoading(true); - console.log("verifyUser", value.code, userId, isInvite); const verifyResponse = await verifyUser({ code: value.code, userId, From 8a88e939e7c84c1ed3036033bf7ce08903da289a Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 21 Oct 2024 17:16:22 +0200 Subject: [PATCH 326/640] error handling --- apps/login/src/lib/server/email.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/login/src/lib/server/email.ts b/apps/login/src/lib/server/email.ts index 50c21ec95f..6e05e7de46 100644 --- a/apps/login/src/lib/server/email.ts +++ b/apps/login/src/lib/server/email.ts @@ -16,11 +16,17 @@ type VerifyUserByEmailCommand = { export async function verifyUser(command: VerifyUserByEmailCommand) { const verifyResponse = command.isInvite - ? await verifyInviteCode(command.userId, command.code) - : await verifyEmail(command.userId, command.code); + ? await verifyInviteCode(command.userId, command.code).catch((error) => { + console.log(error.code); + return { error: "Could not verify invite" }; + }) + : await verifyEmail(command.userId, command.code).catch((error) => { + console.log(error.code); + return { error: "Could not verify email" }; + }); if (!verifyResponse) { - return { error: "Could not verify user email" }; + return { error: "Could not verify user" }; } const authMethodResponse = await listAuthenticationMethodTypes( From c626dd53e86d5ab291440287a8057abf64438e65 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 22 Oct 2024 14:01:11 +0200 Subject: [PATCH 327/640] initial param for password set page, fix cookie overflow --- .../src/app/(login)/password/change/page.tsx | 2 +- .../src/app/(login)/password/set/page.tsx | 6 +- apps/login/src/components/auth-methods.tsx | 10 +-- .../src/components/change-password-form.tsx | 3 - .../src/components/set-password-form.tsx | 73 +++++++++---------- .../src/components/verify-email-form.tsx | 13 +++- apps/login/src/lib/cookies.ts | 11 ++- apps/login/src/lib/idp.ts | 1 - apps/login/src/lib/server/password.ts | 6 +- apps/login/src/lib/zitadel.ts | 48 +++++++++--- 10 files changed, 104 insertions(+), 69 deletions(-) diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx index 006d2a15b5..55b8c0ad49 100644 --- a/apps/login/src/app/(login)/password/change/page.tsx +++ b/apps/login/src/app/(login)/password/change/page.tsx @@ -19,7 +19,7 @@ export default async function Page({ const t = await getTranslations({ locale, namespace: "password" }); const tError = await getTranslations({ locale, namespace: "error" }); - const { loginName, organization, authRequestId, code } = searchParams; + const { loginName, organization, authRequestId } = searchParams; // also allow no session to be found (ignoreUnkownUsername) const sessionFactors = await loadMostRecentSession({ diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index 8e0a8a6993..b7cd7c0d1b 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -22,7 +22,8 @@ export default async function Page({ const t = await getTranslations({ locale, namespace: "password" }); const tError = await getTranslations({ locale, namespace: "error" }); - const { userId, loginName, organization, authRequestId, code } = searchParams; + const { userId, loginName, organization, authRequestId, code, initial } = + searchParams; // also allow no session to be found (ignoreUnkownUsername) let session: Session | undefined; @@ -81,7 +82,7 @@ export default async function Page({ > ) : null} - {t("set.codeSent")} + {!initial && {t("set.codeSent")}} {passwordComplexity && (loginName ?? user?.preferredLoginName) && @@ -93,6 +94,7 @@ export default async function Page({ authRequestId={authRequestId} organization={organization} passwordComplexitySettings={passwordComplexity} + codeRequired={!(initial === "true")} /> ) : (
    diff --git a/apps/login/src/components/auth-methods.tsx b/apps/login/src/components/auth-methods.tsx index 3176b659d4..75a0cc84c1 100644 --- a/apps/login/src/components/auth-methods.tsx +++ b/apps/login/src/components/auth-methods.tsx @@ -104,13 +104,13 @@ export const U2F = (alreadyAdded: boolean, link: string) => { xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" - stroke-width="1.5" + strokeWidth="1.5" stroke="currentColor" className="w-8 h-8 mr-4" > @@ -143,8 +143,8 @@ export const EMAIL = (alreadyAdded: boolean, link: string) => { stroke="currentColor" > diff --git a/apps/login/src/components/change-password-form.tsx b/apps/login/src/components/change-password-form.tsx index ccdcd79281..9671752074 100644 --- a/apps/login/src/components/change-password-form.tsx +++ b/apps/login/src/components/change-password-form.tsx @@ -12,7 +12,6 @@ import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { useTranslations } from "next-intl"; -import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; import { Alert } from "./alert"; @@ -57,8 +56,6 @@ export function ChangePasswordForm({ const [loading, setLoading] = useState(false); const [error, setError] = useState(""); - const router = useRouter(); - async function submitChange(values: Inputs) { setLoading(true); const changeResponse = await setMyPassword({ diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 60aa85f028..7473bae970 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -35,6 +35,7 @@ type Props = { userId: string; organization?: string; authRequestId?: string; + codeRequired: boolean; }; export function SetPasswordForm({ @@ -44,6 +45,7 @@ export function SetPasswordForm({ loginName, userId, code, + codeRequired, }: Props) { const t = useTranslations("password"); @@ -59,11 +61,17 @@ export function SetPasswordForm({ async function submitRegister(values: Inputs) { setLoading(true); - const changeResponse = await changePassword({ + let payload: { userId: string; password: string; code?: string } = { userId: userId, password: values.password, - code: values.code, - }).catch(() => { + }; + + // this is not required for initial password setup + if (codeRequired) { + payload = { ...payload, code: values.code }; + } + + const changeResponse = await changePassword(payload).catch(() => { setError("Could not register user"); }); @@ -94,7 +102,8 @@ export function SetPasswordForm({ password: { password: values.password }, }), authRequestId, - }).catch(() => { + }).catch((error) => { + console.error("verifyerror", error); setLoading(false); setError("Could not verify password"); return; @@ -109,23 +118,6 @@ export function SetPasswordForm({ ) { setError(passwordResponse.error); } - - // // skip verification for now as it is an app based flow - // // return router.push(`/verify?` + params); - - // // check for mfa force to continue with mfa setup - - // if (authRequestId && changeResponse.sessionId) { - // if (authRequestId) { - // params.append("authRequest", authRequestId); - // } - // return router.push(`/login?` + params); - // } else { - // if (authRequestId) { - // params.append("authRequestId", authRequestId); - // } - // return router.push(`/signedin?` + params); - // } } const { errors } = formState; @@ -152,25 +144,28 @@ export function SetPasswordForm({ return (
    -
    -
    - + {codeRequired && ( +
    +
    + +
    + +
    + +
    -
    - -
    -
    + )}
    ( if (index > -1) { currentSessions[index] = session; } else { - currentSessions = [...currentSessions, session]; + const temp = [...currentSessions, session]; + + if (temp.length > MAX_COOKIE_SIZE) { + // TODO: improve cookie handling + // this replaces the first session (oldest) with the new one + currentSessions = [session].concat(currentSessions.slice(1)); + } } if (cleanup) { diff --git a/apps/login/src/lib/idp.ts b/apps/login/src/lib/idp.ts index fb8386c2bd..1c021cbfe0 100644 --- a/apps/login/src/lib/idp.ts +++ b/apps/login/src/lib/idp.ts @@ -169,7 +169,6 @@ export const PROVIDER_MAPPING: { } = { [IdentityProviderType.GOOGLE]: (idp: IDPInformation) => { const rawInfo = idp.rawInformation as OIDC_USER; - console.log(rawInfo); return create(AddHumanUserRequestSchema, { username: idp.userName, diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index c1467e6b39..4997f23ca0 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -71,8 +71,6 @@ export async function sendPassword(command: UpdateSessionCommand) { organizationId: command.organization, }); - console.log(users); - if (users.details?.totalResult == BigInt(1) && users.result[0].userId) { user = users.result[0]; @@ -89,7 +87,7 @@ export async function sendPassword(command: UpdateSessionCommand) { } // this is a fake error message to hide that the user does not even exist - return { error: "Could not verify password!" }; + return { error: "Could not verify password" }; } else { session = await setSessionAndUpdateCookie( sessionCookie, @@ -274,7 +272,7 @@ export async function sendPassword(command: UpdateSessionCommand) { } export async function changePassword(command: { - code: string; + code?: string; userId: string; password: string; }) { diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 2f104d2f7c..0f9a1534a2 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -13,6 +13,7 @@ import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { RetrieveIdentityProviderIntentRequest, + SetPasswordRequestSchema, VerifyPasskeyRegistrationRequest, VerifyU2FRegistrationRequest, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; @@ -315,7 +316,6 @@ export async function verifyInviteCode( } export async function resendInviteCode(userId: string) { - console.log("resetInit"); return userService.resendInviteCode({ userId }, {}); } @@ -580,24 +580,50 @@ export async function passwordReset(userId: string, host: string | null) { ); } +/** + * + * @param userId userId of the user to set the password for + * @param password the new password + * @param code optional if the password should be set with a code (reset), no code for initial setup of password + * @returns + */ export async function setPassword( userId: string, password: string, - code: string, + code?: string, ) { - return userService.setPassword( - { - userId, - newPassword: { - password, - }, + let payload = create(SetPasswordRequestSchema, { + userId, + newPassword: { + password, + }, + }); + + // check if the user has no password set in order to set a password + if (!code) { + const authmethods = await listAuthenticationMethodTypes(userId); + + // if the user has no authmethods set, we can set a password otherwise we need a code + if ( + !authmethods || + !authmethods.authMethodTypes || + authmethods.authMethodTypes.length === 0 + ) { + return { error: "Provide a code to set a password" }; + } + } + + if (code) { + payload = { + ...payload, verification: { case: "verificationCode", value: code, }, - }, - {}, - ); + }; + } + + return userService.setPassword(payload, {}); } /** From 3fadf2651ee1630092a3e567829eb5e4a4a6a484 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 22 Oct 2024 14:15:17 +0200 Subject: [PATCH 328/640] cookie overflow handling --- apps/login/src/lib/cookies.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/login/src/lib/cookies.ts b/apps/login/src/lib/cookies.ts index 098ca7ccb7..7ac0a4caa9 100644 --- a/apps/login/src/lib/cookies.ts +++ b/apps/login/src/lib/cookies.ts @@ -4,7 +4,7 @@ import { cookies } from "next/headers"; import { LANGUAGE_COOKIE_NAME } from "./i18n"; // TODO: improve this to handle overflow -export const MAX_COOKIE_SIZE = 4096; +const MAX_COOKIE_SIZE = 2048; export type Cookie = { id: string; @@ -61,7 +61,8 @@ export async function addSessionToCookie( } else { const temp = [...currentSessions, session]; - if (temp.length > MAX_COOKIE_SIZE) { + if (JSON.stringify(temp).length >= MAX_COOKIE_SIZE) { + console.log("WARNING COOKIE OVERFLOW"); // TODO: improve cookie handling // this replaces the first session (oldest) with the new one currentSessions = [session].concat(currentSessions.slice(1)); From ebae5567156cf28bcefde999bbf0853ad3e94e2c Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 22 Oct 2024 15:17:08 +0200 Subject: [PATCH 329/640] error handling --- apps/login/src/components/set-password-form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 7473bae970..251c04956f 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -72,7 +72,7 @@ export function SetPasswordForm({ } const changeResponse = await changePassword(payload).catch(() => { - setError("Could not register user"); + setError("Could not set password"); }); if (changeResponse && "error" in changeResponse) { From 4afa3c00aecdf53e6b5179fefc2b401039bed554 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 18 Oct 2024 12:32:57 -0400 Subject: [PATCH 330/640] chore: enable unit tests --- .github/workflows/test.yml | 3 + packages/zitadel-client/package.json | 21 +- .../zitadel-client/src/interceptors.test.ts | 40 +-- packages/zitadel-node/package.json | 8 +- pnpm-lock.yaml | 245 ++++++++++-------- 5 files changed, 179 insertions(+), 138 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 91c78a4db2..ea604c1f32 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,6 +26,9 @@ jobs: - name: Checkout Repo uses: actions/checkout@v4.1.6 + - name: Setup Buf + uses: bufbuild/buf-setup-action@v1.45.0 + - name: Setup Node.js 20.x uses: actions/setup-node@v4.0.2 with: diff --git a/packages/zitadel-client/package.json b/packages/zitadel-client/package.json index 8b271b6bab..0802387274 100644 --- a/packages/zitadel-client/package.json +++ b/packages/zitadel-client/package.json @@ -9,24 +9,24 @@ "type": "module", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/index.js", - "require": "./dist/index.cjs", - "types": "./dist/index.d.ts" + "require": "./dist/index.cjs" }, "./v1": { + "types": "./dist/v1.d.ts", "import": "./dist/v1.js", - "require": "./dist/v1.cjs", - "types": "./dist/v1.d.ts" + "require": "./dist/v1.cjs" }, "./v2": { + "types": "./dist/v2.d.ts", "import": "./dist/v2.js", - "require": "./dist/v2.cjs", - "types": "./dist/v2.d.ts" + "require": "./dist/v2.cjs" }, "./v3alpha": { + "types": "./dist/v3alpha.d.ts", "import": "./dist/v3alpha.js", - "require": "./dist/v3alpha.cjs", - "types": "./dist/v3alpha.d.ts" + "require": "./dist/v3alpha.cjs" } }, "files": [ @@ -44,11 +44,12 @@ "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist" }, "dependencies": { - "@zitadel/proto": "workspace:*", "@bufbuild/protobuf": "^2.0.0", - "@connectrpc/connect": "2.0.0-alpha.1" + "@connectrpc/connect": "2.0.0-alpha.1", + "@zitadel/proto": "workspace:*" }, "devDependencies": { + "@bufbuild/protocompile": "^0.0.1", "@zitadel/tsconfig": "workspace:*", "eslint-config-zitadel": "workspace:*" } diff --git a/packages/zitadel-client/src/interceptors.test.ts b/packages/zitadel-client/src/interceptors.test.ts index 10fa06d3ee..2d80a7f5b8 100644 --- a/packages/zitadel-client/src/interceptors.test.ts +++ b/packages/zitadel-client/src/interceptors.test.ts @@ -1,20 +1,24 @@ -import { Int32Value, Int32ValueSchema, StringValueSchema } from "@bufbuild/protobuf/wkt"; +import { Int32Value } from "@bufbuild/protobuf/wkt"; +import { compileService } from "@bufbuild/protocompile"; import { createRouterTransport, HandlerContext } from "@connectrpc/connect"; import { describe, expect, test, vitest } from "vitest"; import { NewAuthorizationBearerInterceptor } from "./interceptors"; -const TestService = { - typeName: "handwritten.TestService", - methods: { - unary: { - input: Int32ValueSchema, - output: StringValueSchema, - methodKind: "unary", - }, - }, -} as const; +const TestService = compileService(` + syntax = "proto3"; + package handwritten; + service TestService { + rpc Unary(Int32Value) returns (StringValue); + } + message Int32Value { + int32 value = 1; + } + message StringValue { + string value = 1; + } +`); -describe.skip("NewAuthorizationBearerInterceptor", () => { +describe("NewAuthorizationBearerInterceptor", () => { const transport = { interceptors: [NewAuthorizationBearerInterceptor("mytoken")], }; @@ -25,13 +29,13 @@ describe.skip("NewAuthorizationBearerInterceptor", () => { }); const service = createRouterTransport( - ({ service }) => { - service(TestService, { unary: handler }); + ({ rpc }) => { + rpc(TestService.method.unary, handler); }, { transport }, ); - await service.unary(TestService, TestService.methods.unary, undefined, undefined, {}, { value: 9001 }); + await service.unary(TestService.method.unary, undefined, undefined, {}, { value: 9001 }); expect(handler).toBeCalled(); expect(handler.mock.calls[0][1].requestHeader.get("Authorization")).toBe("Bearer mytoken"); @@ -43,14 +47,14 @@ describe.skip("NewAuthorizationBearerInterceptor", () => { }); const service = createRouterTransport( - ({ service }) => { - service(TestService, { unary: handler }); + ({ rpc }) => { + rpc(TestService.method.unary, handler); }, { transport }, ); await service.unary( - TestService.methods.unary, + TestService.method.unary, undefined, undefined, { Authorization: "Bearer somethingelse" }, diff --git a/packages/zitadel-node/package.json b/packages/zitadel-node/package.json index 844e0ea86c..ef8046f5e0 100644 --- a/packages/zitadel-node/package.json +++ b/packages/zitadel-node/package.json @@ -6,9 +6,9 @@ "license": "MIT", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/index.js", - "require": "./dist/index.cjs", - "types": "./dist/index.d.ts" + "require": "./dist/index.cjs" } }, "files": [ @@ -28,7 +28,8 @@ "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist && rm -rf src/proto" }, "peerDependencies": { - "@zitadel/client": "workspace:*" + "@zitadel/client": "workspace:*", + "@connectrpc/connect": "^2.0.0-alpha.1" }, "dependencies": { "@connectrpc/connect-node": "^2.0.0-alpha.1", @@ -36,6 +37,7 @@ "jose": "^5.3.0" }, "devDependencies": { + "@connectrpc/connect": "^2.0.0-alpha.1", "@types/node": "^22.5.5", "@zitadel/client": "workspace:*", "@zitadel/tsconfig": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a2f4ff98a..f1d69cb354 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -238,6 +238,9 @@ importers: specifier: workspace:* version: link:../zitadel-proto devDependencies: + '@bufbuild/protocompile': + specifier: ^0.0.1 + version: 0.0.1(@bufbuild/buf@1.41.0) '@zitadel/tsconfig': specifier: workspace:* version: link:../zitadel-tsconfig @@ -249,14 +252,17 @@ importers: dependencies: '@connectrpc/connect-node': specifier: ^2.0.0-alpha.1 - version: 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)) + version: 2.0.0-alpha.1(@bufbuild/protobuf@2.2.0)(@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.2.0)) '@connectrpc/connect-web': specifier: ^2.0.0-alpha.1 - version: 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)) + version: 2.0.0-alpha.1(@bufbuild/protobuf@2.2.0)(@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.2.0)) jose: specifier: ^5.3.0 version: 5.8.0 devDependencies: + '@connectrpc/connect': + specifier: ^2.0.0-alpha.1 + version: 2.0.0-alpha.1(@bufbuild/protobuf@2.2.0) '@types/node': specifier: ^22.5.5 version: 22.5.5 @@ -311,7 +317,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/code-frame@7.25.7': - resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} + resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==, tarball: https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz} engines: {node: '>=6.9.0'} '@babel/compat-data@7.25.4': @@ -357,7 +363,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/helper-validator-identifier@7.25.7': - resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} + resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==, tarball: https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz} engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.24.8': @@ -373,7 +379,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/highlight@7.25.7': - resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} + resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==, tarball: https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz} engines: {node: '>=6.9.0'} '@babel/parser@7.25.6': @@ -414,37 +420,37 @@ packages: engines: {node: '>=6.9.0'} '@bufbuild/buf-darwin-arm64@1.41.0': - resolution: {integrity: sha512-+G5DwpIgnm0AkqgxORxoYXVT0RGDcw8P4SXFXcovgvDBkk9rPvEI1dbPF83n3SUxzcu2A2OxC7DxlXszWIh2Gw==} + resolution: {integrity: sha512-+G5DwpIgnm0AkqgxORxoYXVT0RGDcw8P4SXFXcovgvDBkk9rPvEI1dbPF83n3SUxzcu2A2OxC7DxlXszWIh2Gw==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.41.0.tgz} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@bufbuild/buf-darwin-x64@1.41.0': - resolution: {integrity: sha512-qjkJ/LAWqNk3HX65n+JTt18WtKrhrrAhIu3Dpfbe0eujsxafFZKoPzlWJYybxvsaF9CdEyMMm/OalBPpoosMOA==} + resolution: {integrity: sha512-qjkJ/LAWqNk3HX65n+JTt18WtKrhrrAhIu3Dpfbe0eujsxafFZKoPzlWJYybxvsaF9CdEyMMm/OalBPpoosMOA==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.41.0.tgz} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@bufbuild/buf-linux-aarch64@1.41.0': - resolution: {integrity: sha512-5E+MLAF4QHPwAjwVVRRP3Is2U3zpIpQQR7S3di9HlKACbgvefJEBrUfRqQZvHrMuuynQRqjFuZD16Sfvxn9rCQ==} + resolution: {integrity: sha512-5E+MLAF4QHPwAjwVVRRP3Is2U3zpIpQQR7S3di9HlKACbgvefJEBrUfRqQZvHrMuuynQRqjFuZD16Sfvxn9rCQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.41.0.tgz} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@bufbuild/buf-linux-x64@1.41.0': - resolution: {integrity: sha512-W4T+uqmdtypzzatv6OXjUzGacZiNzGECogr+qDkJF38MSZd3jHXhTEN2KhRckl3i9rRAnfHBwG68BjCTxxBCOQ==} + resolution: {integrity: sha512-W4T+uqmdtypzzatv6OXjUzGacZiNzGECogr+qDkJF38MSZd3jHXhTEN2KhRckl3i9rRAnfHBwG68BjCTxxBCOQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.41.0.tgz} engines: {node: '>=12'} cpu: [x64] os: [linux] '@bufbuild/buf-win32-arm64@1.41.0': - resolution: {integrity: sha512-OsRVoTZHJZYGIphAwaRqcCeYR9Sk5VEMjpCJiFt/dkHxx2acKH4u/7O+633gcCxQL8EnsU2l8AfdbW7sQaOvlg==} + resolution: {integrity: sha512-OsRVoTZHJZYGIphAwaRqcCeYR9Sk5VEMjpCJiFt/dkHxx2acKH4u/7O+633gcCxQL8EnsU2l8AfdbW7sQaOvlg==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.41.0.tgz} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@bufbuild/buf-win32-x64@1.41.0': - resolution: {integrity: sha512-2KJLp7Py0GsfRjDxwBzS17RMpaYFGCvzkwY5CtxfPMw8cg6cE7E36r+vcjHh5dBOj/CumaiXLTwxhCSBtp0V1g==} + resolution: {integrity: sha512-2KJLp7Py0GsfRjDxwBzS17RMpaYFGCvzkwY5CtxfPMw8cg6cE7E36r+vcjHh5dBOj/CumaiXLTwxhCSBtp0V1g==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.41.0.tgz} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -457,6 +463,14 @@ packages: '@bufbuild/protobuf@2.0.0': resolution: {integrity: sha512-sw2JhwJyvyL0zlhG61aDzOVryEfJg2PDZFSV7i7IdC7nAE41WuXCru3QWLGiP87At0BMzKOoKO/FqEGoKygGZQ==} + '@bufbuild/protobuf@2.2.0': + resolution: {integrity: sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ==, tarball: https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.0.tgz} + + '@bufbuild/protocompile@0.0.1': + resolution: {integrity: sha512-cOTMtjcWLcbjF17dPYgeMtVC5jZyS0bSjz3jy8kDPjOgjgSYMD2u2It7w8aCc2z23hTPIKl/2SNdMnz0Jzu3Xg==, tarball: https://registry.npmjs.org/@bufbuild/protocompile/-/protocompile-0.0.1.tgz} + peerDependencies: + '@bufbuild/buf': ^1.22.0 + '@changesets/apply-release-plan@7.0.5': resolution: {integrity: sha512-1cWCk+ZshEkSVEZrm2fSj1Gz8sYvxgUL4Q78+1ZZqeqfuevPTPk033/yUZ3df8BKMohkqqHfzj0HOOrG0KtXTw==} @@ -513,7 +527,7 @@ packages: resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==, tarball: https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz} engines: {node: '>=0.1.90'} '@connectrpc/connect-node@2.0.0-alpha.1': @@ -530,7 +544,7 @@ packages: '@connectrpc/connect': 2.0.0-alpha.1 '@connectrpc/connect@2.0.0-alpha.1': - resolution: {integrity: sha512-eAjezEy1m+7bDtWt/TeXY2qIayq7R+WW8Jqwq3p8Q7ZTHpFJu0SBQivhRGQbNXSYR+QmqMk+j/FLAVlgB6CfHA==} + resolution: {integrity: sha512-eAjezEy1m+7bDtWt/TeXY2qIayq7R+WW8Jqwq3p8Q7ZTHpFJu0SBQivhRGQbNXSYR+QmqMk+j/FLAVlgB6CfHA==, tarball: https://registry.npmjs.org/@connectrpc/connect/-/connect-2.0.0-alpha.1.tgz} peerDependencies: '@bufbuild/protobuf': ^2.0.0-beta.2 @@ -542,283 +556,283 @@ packages: resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz} engines: {node: '>=12'} cpu: [ppc64] os: [aix] '@esbuild/aix-ppc64@0.23.1': - resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz} engines: {node: '>=18'} cpu: [ppc64] os: [aix] '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm64] os: [android] '@esbuild/android-arm64@0.23.1': - resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm64] os: [android] '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm] os: [android] '@esbuild/android-arm@0.23.1': - resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm] os: [android] '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [android] '@esbuild/android-x64@0.23.1': - resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [android] '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@esbuild/darwin-arm64@0.23.1': - resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@esbuild/darwin-x64@0.23.1': - resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [darwin] '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-arm64@0.23.1': - resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [freebsd] '@esbuild/freebsd-x64@0.23.1': - resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [freebsd] '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@esbuild/linux-arm64@0.23.1': - resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm] os: [linux] '@esbuild/linux-arm@0.23.1': - resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz} engines: {node: '>=12'} cpu: [ia32] os: [linux] '@esbuild/linux-ia32@0.23.1': - resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz} engines: {node: '>=18'} cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz} engines: {node: '>=12'} cpu: [loong64] os: [linux] '@esbuild/linux-loong64@0.23.1': - resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz} engines: {node: '>=18'} cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz} engines: {node: '>=12'} cpu: [mips64el] os: [linux] '@esbuild/linux-mips64el@0.23.1': - resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz} engines: {node: '>=18'} cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz} engines: {node: '>=12'} cpu: [ppc64] os: [linux] '@esbuild/linux-ppc64@0.23.1': - resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz} engines: {node: '>=18'} cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz} engines: {node: '>=12'} cpu: [riscv64] os: [linux] '@esbuild/linux-riscv64@0.23.1': - resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz} engines: {node: '>=18'} cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz} engines: {node: '>=12'} cpu: [s390x] os: [linux] '@esbuild/linux-s390x@0.23.1': - resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz} engines: {node: '>=18'} cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [linux] '@esbuild/linux-x64@0.23.1': - resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [linux] '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [netbsd] '@esbuild/netbsd-x64@0.23.1': - resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [netbsd] '@esbuild/openbsd-arm64@0.23.1': - resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [openbsd] '@esbuild/openbsd-x64@0.23.1': - resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [openbsd] '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [sunos] '@esbuild/sunos-x64@0.23.1': - resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [sunos] '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@esbuild/win32-arm64@0.23.1': - resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz} engines: {node: '>=12'} cpu: [ia32] os: [win32] '@esbuild/win32-ia32@0.23.1': - resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz} engines: {node: '>=18'} cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [win32] '@esbuild/win32-x64@0.23.1': - resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -963,55 +977,55 @@ packages: resolution: {integrity: sha512-+7xh142AdhZGjY9/L0iFo7mqRBMJHe+q+uOL+hto1Lfo9DeWCGcR6no4StlFbVSVcA6fQLKEX6y6qhMsSKbgNQ==} '@next/swc-darwin-arm64@14.2.14': - resolution: {integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==} + resolution: {integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==, tarball: https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.14.tgz} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] '@next/swc-darwin-x64@14.2.14': - resolution: {integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==} + resolution: {integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==, tarball: https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.14.tgz} engines: {node: '>= 10'} cpu: [x64] os: [darwin] '@next/swc-linux-arm64-gnu@14.2.14': - resolution: {integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==} + resolution: {integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==, tarball: https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.14.tgz} engines: {node: '>= 10'} cpu: [arm64] os: [linux] '@next/swc-linux-arm64-musl@14.2.14': - resolution: {integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==} + resolution: {integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==, tarball: https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.14.tgz} engines: {node: '>= 10'} cpu: [arm64] os: [linux] '@next/swc-linux-x64-gnu@14.2.14': - resolution: {integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==} + resolution: {integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==, tarball: https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.14.tgz} engines: {node: '>= 10'} cpu: [x64] os: [linux] '@next/swc-linux-x64-musl@14.2.14': - resolution: {integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==} + resolution: {integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==, tarball: https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.14.tgz} engines: {node: '>= 10'} cpu: [x64] os: [linux] '@next/swc-win32-arm64-msvc@14.2.14': - resolution: {integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==} + resolution: {integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==, tarball: https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.14.tgz} engines: {node: '>= 10'} cpu: [arm64] os: [win32] '@next/swc-win32-ia32-msvc@14.2.14': - resolution: {integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==} + resolution: {integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==, tarball: https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.14.tgz} engines: {node: '>= 10'} cpu: [ia32] os: [win32] '@next/swc-win32-x64-msvc@14.2.14': - resolution: {integrity: sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg==} + resolution: {integrity: sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg==, tarball: https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.14.tgz} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1033,7 +1047,7 @@ packages: engines: {node: '>=12.4.0'} '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, tarball: https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz} engines: {node: '>=14'} '@protobufjs/aspromise@1.1.2': @@ -1098,82 +1112,82 @@ packages: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 '@rollup/rollup-android-arm-eabi@4.21.3': - resolution: {integrity: sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==} + resolution: {integrity: sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz} cpu: [arm] os: [android] '@rollup/rollup-android-arm64@4.21.3': - resolution: {integrity: sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==} + resolution: {integrity: sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz} cpu: [arm64] os: [android] '@rollup/rollup-darwin-arm64@4.21.3': - resolution: {integrity: sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==} + resolution: {integrity: sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz} cpu: [arm64] os: [darwin] '@rollup/rollup-darwin-x64@4.21.3': - resolution: {integrity: sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==} + resolution: {integrity: sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz} cpu: [x64] os: [darwin] '@rollup/rollup-linux-arm-gnueabihf@4.21.3': - resolution: {integrity: sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==} + resolution: {integrity: sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz} cpu: [arm] os: [linux] '@rollup/rollup-linux-arm-musleabihf@4.21.3': - resolution: {integrity: sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==} + resolution: {integrity: sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz} cpu: [arm] os: [linux] '@rollup/rollup-linux-arm64-gnu@4.21.3': - resolution: {integrity: sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==} + resolution: {integrity: sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz} cpu: [arm64] os: [linux] '@rollup/rollup-linux-arm64-musl@4.21.3': - resolution: {integrity: sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==} + resolution: {integrity: sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz} cpu: [arm64] os: [linux] '@rollup/rollup-linux-powerpc64le-gnu@4.21.3': - resolution: {integrity: sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==} + resolution: {integrity: sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz} cpu: [ppc64] os: [linux] '@rollup/rollup-linux-riscv64-gnu@4.21.3': - resolution: {integrity: sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==} + resolution: {integrity: sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz} cpu: [riscv64] os: [linux] '@rollup/rollup-linux-s390x-gnu@4.21.3': - resolution: {integrity: sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==} + resolution: {integrity: sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz} cpu: [s390x] os: [linux] '@rollup/rollup-linux-x64-gnu@4.21.3': - resolution: {integrity: sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==} + resolution: {integrity: sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz} cpu: [x64] os: [linux] '@rollup/rollup-linux-x64-musl@4.21.3': - resolution: {integrity: sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==} + resolution: {integrity: sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz} cpu: [x64] os: [linux] '@rollup/rollup-win32-arm64-msvc@4.21.3': - resolution: {integrity: sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==} + resolution: {integrity: sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz} cpu: [arm64] os: [win32] '@rollup/rollup-win32-ia32-msvc@4.21.3': - resolution: {integrity: sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==} + resolution: {integrity: sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz} cpu: [ia32] os: [win32] '@rollup/rollup-win32-x64-msvc@4.21.3': - resolution: {integrity: sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==} + resolution: {integrity: sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz} cpu: [x64] os: [win32] @@ -1215,7 +1229,7 @@ packages: resolution: {integrity: sha512-1giLc4dzgEKLMx5pgKjL6HlG5fjZMgCjzlKAlpr7yoUtetVPELgER1NtephAI910nMwfPTHNyWKSFmJdHkz2Cw==} '@testing-library/dom@10.4.0': - resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==, tarball: https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz} engines: {node: '>=18'} '@testing-library/jest-dom@6.5.0': @@ -1238,7 +1252,7 @@ packages: optional: true '@types/aria-query@5.0.4': - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==, tarball: https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz} '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1298,7 +1312,7 @@ packages: resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==, tarball: https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz} '@typescript-eslint/parser@7.18.0': resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} @@ -1448,7 +1462,7 @@ packages: engines: {node: '>=8'} ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==, tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz} engines: {node: '>=10'} ansi-styles@6.2.1: @@ -2001,7 +2015,7 @@ packages: engines: {node: '>=6.0.0'} dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==, tarball: https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz} dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} @@ -2324,6 +2338,9 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==, tarball: https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz} + figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -2404,7 +2421,7 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] @@ -3051,7 +3068,7 @@ packages: engines: {node: '>=10'} lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==, tarball: https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz} hasBin: true magic-string@0.30.11: @@ -3627,7 +3644,7 @@ packages: engines: {node: '>=6'} pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==, tarball: https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} process@0.11.10: @@ -3702,7 +3719,7 @@ packages: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==, tarball: https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz} react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} @@ -4276,32 +4293,32 @@ packages: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} turbo-darwin-64@2.1.2: - resolution: {integrity: sha512-3TEBxHWh99h2yIzkuIigMEOXt/ItYQp0aPiJjPd1xN4oDcsKK5AxiFKPH9pdtfIBzYsY59kQhZiFj0ELnSP7Bw==} + resolution: {integrity: sha512-3TEBxHWh99h2yIzkuIigMEOXt/ItYQp0aPiJjPd1xN4oDcsKK5AxiFKPH9pdtfIBzYsY59kQhZiFj0ELnSP7Bw==, tarball: https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.1.2.tgz} cpu: [x64] os: [darwin] turbo-darwin-arm64@2.1.2: - resolution: {integrity: sha512-he0miWNq2WxJzsH82jS2Z4MXpnkzn9SH8a79iPXiJkq25QREImucscM4RPasXm8wARp91pyysJMq6aasD45CeA==} + resolution: {integrity: sha512-he0miWNq2WxJzsH82jS2Z4MXpnkzn9SH8a79iPXiJkq25QREImucscM4RPasXm8wARp91pyysJMq6aasD45CeA==, tarball: https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.1.2.tgz} cpu: [arm64] os: [darwin] turbo-linux-64@2.1.2: - resolution: {integrity: sha512-fKUBcc0rK8Vdqv5a/E3CSpMBLG1bzwv+Q0Q83F8fG2ZfNCNKGbcEYABdonNZkkx141Rj03cZQFCgxu3MVEGU+A==} + resolution: {integrity: sha512-fKUBcc0rK8Vdqv5a/E3CSpMBLG1bzwv+Q0Q83F8fG2ZfNCNKGbcEYABdonNZkkx141Rj03cZQFCgxu3MVEGU+A==, tarball: https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.1.2.tgz} cpu: [x64] os: [linux] turbo-linux-arm64@2.1.2: - resolution: {integrity: sha512-sV8Bpmm0WiuxgbhxymcC7wSsuxfBBieI98GegSwbr/bs1ANAgzCg93urIrdKdQ3/b31zZxQwcaP4FBF1wx1Qdg==} + resolution: {integrity: sha512-sV8Bpmm0WiuxgbhxymcC7wSsuxfBBieI98GegSwbr/bs1ANAgzCg93urIrdKdQ3/b31zZxQwcaP4FBF1wx1Qdg==, tarball: https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.1.2.tgz} cpu: [arm64] os: [linux] turbo-windows-64@2.1.2: - resolution: {integrity: sha512-wcmIJZI9ORT9ykHGliFE6kWRQrlH930QGSjSgWC8uFChFFuOyUlvC7ttcxuSvU9VqC7NF4C+GVAcFJQ8lTjN7g==} + resolution: {integrity: sha512-wcmIJZI9ORT9ykHGliFE6kWRQrlH930QGSjSgWC8uFChFFuOyUlvC7ttcxuSvU9VqC7NF4C+GVAcFJQ8lTjN7g==, tarball: https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.1.2.tgz} cpu: [x64] os: [win32] turbo-windows-arm64@2.1.2: - resolution: {integrity: sha512-zdnXjrhk7YO6CP+Q5wPueEvOCLH4lDa6C4rrwiakcWcPgcQGbVozJlo4uaQ6awo8HLWQEvOwu84RkWTdLAc/Hw==} + resolution: {integrity: sha512-zdnXjrhk7YO6CP+Q5wPueEvOCLH4lDa6C4rrwiakcWcPgcQGbVozJlo4uaQ6awo8HLWQEvOwu84RkWTdLAc/Hw==, tarball: https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.1.2.tgz} cpu: [arm64] os: [win32] @@ -4817,6 +4834,14 @@ snapshots: '@bufbuild/protobuf@2.0.0': {} + '@bufbuild/protobuf@2.2.0': {} + + '@bufbuild/protocompile@0.0.1(@bufbuild/buf@1.41.0)': + dependencies: + '@bufbuild/buf': 1.41.0 + '@bufbuild/protobuf': 2.2.0 + fflate: 0.8.2 + '@changesets/apply-release-plan@7.0.5': dependencies: '@changesets/config': 3.0.3 @@ -4964,21 +4989,25 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@connectrpc/connect-node@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0))': + '@connectrpc/connect-node@2.0.0-alpha.1(@bufbuild/protobuf@2.2.0)(@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.2.0))': dependencies: - '@bufbuild/protobuf': 2.0.0 - '@connectrpc/connect': 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0) + '@bufbuild/protobuf': 2.2.0 + '@connectrpc/connect': 2.0.0-alpha.1(@bufbuild/protobuf@2.2.0) undici: 5.28.4 - '@connectrpc/connect-web@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)(@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0))': + '@connectrpc/connect-web@2.0.0-alpha.1(@bufbuild/protobuf@2.2.0)(@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.2.0))': dependencies: - '@bufbuild/protobuf': 2.0.0 - '@connectrpc/connect': 2.0.0-alpha.1(@bufbuild/protobuf@2.0.0) + '@bufbuild/protobuf': 2.2.0 + '@connectrpc/connect': 2.0.0-alpha.1(@bufbuild/protobuf@2.2.0) '@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.0.0)': dependencies: '@bufbuild/protobuf': 2.0.0 + '@connectrpc/connect@2.0.0-alpha.1(@bufbuild/protobuf@2.2.0)': + dependencies: + '@bufbuild/protobuf': 2.2.0 + '@cypress/request@3.0.1': dependencies: aws-sign2: 0.7.0 @@ -6921,6 +6950,8 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fflate@0.8.2: {} + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 From cde5f6cbd088e0525a327810b43b0c1a23eb2fc5 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 23 Oct 2024 09:30:13 +0200 Subject: [PATCH 331/640] component wrapping --- apps/login/src/app/(login)/verify/page.tsx | 38 ++---- .../src/components/verify-email-form.tsx | 110 +++++++++++------- 2 files changed, 76 insertions(+), 72 deletions(-) diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 09723e9706..43adf22968 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -1,8 +1,6 @@ -import { Alert } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { VerifyEmailForm } from "@/components/verify-email-form"; import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; -import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ searchParams }: { searchParams: any }) { @@ -27,32 +25,16 @@ export default async function Page({ searchParams }: { searchParams: any }) { return (
    -

    {t("title")}

    -

    {t("description")}

    - - {!userId && ( -
    - {tError("unknownContext")} -
    - )} - - {userId ? ( - - ) : ( -
    - - {t("userIdMissing")} -
    - )} +
    ); diff --git a/apps/login/src/components/verify-email-form.tsx b/apps/login/src/components/verify-email-form.tsx index 7f8d6dcc06..31540b445b 100644 --- a/apps/login/src/components/verify-email-form.tsx +++ b/apps/login/src/components/verify-email-form.tsx @@ -18,7 +18,7 @@ type Inputs = { }; type Props = { - userId: string; + userId?: string; loginName: string; code: string; organization?: string; @@ -39,6 +39,7 @@ export function VerifyEmailForm({ isInvite, }: Props) { const t = useTranslations("verify"); + const tError = useTranslations("error"); const { register, handleSubmit, formState } = useForm({ mode: "onBlur", @@ -65,9 +66,11 @@ export function VerifyEmailForm({ const router = useRouter(); - const params = new URLSearchParams({ - userId: userId, - }); + const params = new URLSearchParams({}); + + if (userId) { + params.append("userId", userId); + } if (isInvite) { params.append("initial", "true"); @@ -128,10 +131,13 @@ export function VerifyEmailForm({ // if auth methods fall trough, we complete to login const params = new URLSearchParams({ - userId: userId, 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) { params.set("organization", organization); } @@ -146,50 +152,66 @@ export function VerifyEmailForm({ } return !authMethods ? ( - -
    - -
    + <> +

    {t("title")}

    +

    {t("description")}

    - {error && ( + {!userId && (
    - {error} + {tError("unknownContext")}
    )} -
    - - - -
    - +
    +
    + +
    + + {error && ( +
    + {error} +
    + )} + +
    + + + +
    +
    + ) : ( -
    - {!authMethods.includes(AuthenticationMethodType.PASSWORD) && - PASSWORD(false, "/password/set?" + params)} - {!authMethods.includes(AuthenticationMethodType.PASSKEY) && - PASSKEYS(false, "/passkeys/set?" + params)} -
    + <> +

    {t("title")}

    +

    {t("description")}

    + +
    + {!authMethods.includes(AuthenticationMethodType.PASSWORD) && + PASSWORD(false, "/password/set?" + params)} + {!authMethods.includes(AuthenticationMethodType.PASSKEY) && + PASSKEYS(false, "/passkeys/set?" + params)} +
    + ); } From 52ce9219bbba2a7684712ea7814f7dca3534f1a5 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 23 Oct 2024 14:28:33 +0200 Subject: [PATCH 332/640] tests, session using timestamp --- apps/login/cypress/integration/verify.cy.ts | 9 +- apps/login/locales/de.json | 15 ++- apps/login/locales/en.json | 15 ++- apps/login/locales/es.json | 15 ++- apps/login/locales/it.json | 15 ++- .../logo/zitadel-logo-solo-darkdesign.svg | 2 +- .../logo/zitadel-logo-solo-lightdesign.svg | 2 +- apps/login/public/zitadel-logo-dark.svg | 2 +- apps/login/public/zitadel-logo-light.svg | 2 +- apps/login/src/app/(login)/invite/page.tsx | 11 ++- apps/login/src/app/(login)/verify/page.tsx | 96 ++++++++++++++++--- apps/login/src/components/auth-methods.tsx | 14 +-- .../src/components/authenticator-methods.tsx | 18 ++++ .../src/components/change-password-form.tsx | 2 + .../src/components/set-password-form.tsx | 19 ++-- ...{verify-email-form.tsx => verify-form.tsx} | 63 ++++-------- apps/login/src/lib/server/cookie.ts | 18 ++-- apps/login/src/lib/server/loginname.ts | 1 + apps/login/src/lib/server/password.ts | 2 +- apps/login/src/lib/zitadel.ts | 25 +++-- packages/zitadel-client/src/index.ts | 2 +- 21 files changed, 232 insertions(+), 116 deletions(-) create mode 100644 apps/login/src/components/authenticator-methods.tsx rename apps/login/src/components/{verify-email-form.tsx => verify-form.tsx} (72%) diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index a26d8b51e2..8beba527c5 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -1,10 +1,11 @@ import { stub } from "../support/mock"; describe("/verify", () => { - it("redirects after successful email verification", () => { + it("shows password and passkey method after successful invite verification", () => { stub("zitadel.user.v2.UserService", "VerifyEmail"); - cy.visit("/verify?userId=123&code=abc&submit=true"); - cy.location("pathname", { timeout: 10_000 }).should("eq", "/loginname"); + cy.visit("/verify?userId=123&code=abc&submit=true&invite=true"); + cy.contains("Password"); + cy.contains("Passkey"); }); it("shows an error if validation failed", () => { stub("zitadel.user.v2.UserService", "VerifyEmail", { @@ -14,6 +15,6 @@ describe("/verify", () => { // TODO: Avoid uncaught exception in application cy.once("uncaught:exception", () => false); cy.visit("/verify?userId=123&code=abc&submit=true"); - cy.contains("Could not verify email"); + cy.contains("Could not verify user"); }); }); diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 48df445d13..5664819ef4 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -139,6 +139,7 @@ "title": "Benutzer einladen", "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.", + "notAllowed": "Sie haben keine Berechtigung, Benutzer einzuladen.", "submit": "Einladen", "success": { "title": "Einladung erfolgreich", @@ -153,11 +154,17 @@ "description": "Sie sind angemeldet." }, "verify": { - "title": "Benutzer verifizieren", - "description": "Geben Sie den Code ein, der in der Bestätigungs-E-Mail angegeben ist.", "userIdMissing": "Keine Benutzer-ID angegeben!", - "resendCode": "Code erneut senden", - "submit": "Weiter" + "verify": { + "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": { "unknownContext": "Der Kontext des Benutzers konnte nicht ermittelt werden. Stellen Sie sicher, dass Sie zuerst den Benutzernamen eingeben oder einen loginName als Suchparameter angeben.", diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 60c10a077f..e512385902 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -139,6 +139,7 @@ "title": "Invite User", "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.", + "notAllowed": "Your settings do not allow you to invite users.", "submit": "Continue", "success": { "title": "User invited", @@ -153,11 +154,17 @@ "description": "You are signed in." }, "verify": { - "title": "Verify user", - "description": "Enter the Code provided in the verification email.", "userIdMissing": "No userId provided!", - "resendCode": "Resend code", - "submit": "Continue" + "verify": { + "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": { "unknownContext": "Could not get the context of the user. Make sure to enter the username first or provide a loginName as searchParam.", diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index f0fb832c80..3a5f9e1643 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -139,6 +139,7 @@ "title": "Invitar usuario", "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.", + "notAllowed": "No tienes permiso para invitar usuarios.", "submit": "Invitar usuario", "success": { "title": "¡Usuario invitado!", @@ -153,11 +154,17 @@ "description": "Has iniciado sesión." }, "verify": { - "title": "Verificar usuario", - "description": "Introduce el código proporcionado en el correo electrónico de verificación.", "userIdMissing": "¡No se proporcionó userId!", - "resendCode": "Reenviar código", - "submit": "Continuar" + "verify": { + "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": { "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.", diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index aef1042a0d..7086fc5f0f 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -139,6 +139,7 @@ "title": "Invita Utente", "description": "Inserisci l'indirizzo email dell'utente che desideri invitare.", "info": "L'utente riceverà un'email con ulteriori istruzioni.", + "notAllowed": "Non hai i permessi per invitare un utente.", "submit": "Invita Utente", "success": { "title": "Invito inviato", @@ -153,11 +154,17 @@ "description": "Sei connesso." }, "verify": { - "title": "Verifica utente", - "description": "Inserisci il codice fornito nell'email di verifica.", "userIdMissing": "Nessun userId fornito!", - "resendCode": "Invia di nuovo il codice", - "submit": "Continua" + "verify": { + "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": { "unknownContext": "Impossibile ottenere il contesto dell'utente. Assicurati di inserire prima il nome utente o di fornire un loginName come parametro di ricerca.", diff --git a/apps/login/public/logo/zitadel-logo-solo-darkdesign.svg b/apps/login/public/logo/zitadel-logo-solo-darkdesign.svg index df44ec5398..4a4e8be71b 100644 --- a/apps/login/public/logo/zitadel-logo-solo-darkdesign.svg +++ b/apps/login/public/logo/zitadel-logo-solo-darkdesign.svg @@ -1,6 +1,6 @@ - + diff --git a/apps/login/public/logo/zitadel-logo-solo-lightdesign.svg b/apps/login/public/logo/zitadel-logo-solo-lightdesign.svg index 4d3181174e..33ea6b583b 100644 --- a/apps/login/public/logo/zitadel-logo-solo-lightdesign.svg +++ b/apps/login/public/logo/zitadel-logo-solo-lightdesign.svg @@ -1,6 +1,6 @@ - + diff --git a/apps/login/public/zitadel-logo-dark.svg b/apps/login/public/zitadel-logo-dark.svg index 95ff80187c..6dcfe06e6d 100644 --- a/apps/login/public/zitadel-logo-dark.svg +++ b/apps/login/public/zitadel-logo-dark.svg @@ -1,6 +1,6 @@ - + diff --git a/apps/login/public/zitadel-logo-light.svg b/apps/login/public/zitadel-logo-light.svg index 7edc748903..d48a5eeb94 100644 --- a/apps/login/public/zitadel-logo-light.svg +++ b/apps/login/public/zitadel-logo-light.svg @@ -1,6 +1,6 @@ - + diff --git a/apps/login/src/app/(login)/invite/page.tsx b/apps/login/src/app/(login)/invite/page.tsx index 2bf115a8d3..83beb9d9bf 100644 --- a/apps/login/src/app/(login)/invite/page.tsx +++ b/apps/login/src/app/(login)/invite/page.tsx @@ -4,6 +4,7 @@ import { InviteForm } from "@/components/invite-form"; import { getBrandingSettings, getDefaultOrg, + getLoginSettings, getPasswordComplexitySettings, } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; @@ -27,6 +28,8 @@ export default async function Page({ organization = org.id; } + const loginSettings = await getLoginSettings(organization); + const passwordComplexitySettings = await getPasswordComplexitySettings(organization); @@ -38,9 +41,13 @@ export default async function Page({

    {t("title")}

    {t("description")}

    - {t("info")} + {!loginSettings?.allowRegister ? ( + {t("notAllowed")} + ) : ( + {t("info")} + )} - {passwordComplexitySettings && ( + {passwordComplexitySettings && loginSettings?.allowRegister && ( { + 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 (
    - + {!userId && ( + <> +

    {t("verify.title")}

    +

    {t("verify.description")}

    + +
    + {tError("unknownContext")} +
    + + )} + {!verifyResponse || !verifyResponse.authMethodTypes ? ( + + ) : ( + <> +

    {t("setup.title")}

    +

    {t("setup.description")}

    + {user && ( + + )} + + + )}
    ); diff --git a/apps/login/src/components/auth-methods.tsx b/apps/login/src/components/auth-methods.tsx index 75a0cc84c1..578fdee810 100644 --- a/apps/login/src/components/auth-methods.tsx +++ b/apps/login/src/components/auth-methods.tsx @@ -139,7 +139,7 @@ export const EMAIL = (alreadyAdded: boolean, link: string) => { xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" - stroke-width={1.5} + strokeWidth={1.5} stroke="currentColor" > { xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" - stroke-width="1.5" + strokeWidth="1.5" stroke="currentColor" > @@ -207,13 +207,13 @@ export const PASSKEYS = (alreadyAdded: boolean, link: string) => { xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" - stroke-width="1.5" + strokeWidth="1.5" stroke="currentColor" className="w-8 h-8 mr-4" > diff --git a/apps/login/src/components/authenticator-methods.tsx b/apps/login/src/components/authenticator-methods.tsx new file mode 100644 index 0000000000..bd90f19b87 --- /dev/null +++ b/apps/login/src/components/authenticator-methods.tsx @@ -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 ( +
    + {!authMethods.includes(AuthenticationMethodType.PASSWORD) && + PASSWORD(false, "/password/set?" + params)} + {!authMethods.includes(AuthenticationMethodType.PASSKEY) && + PASSKEYS(false, "/passkeys/set?" + params)} +
    + ); +} diff --git a/apps/login/src/components/change-password-form.tsx b/apps/login/src/components/change-password-form.tsx index 9671752074..5fc9368ace 100644 --- a/apps/login/src/components/change-password-form.tsx +++ b/apps/login/src/components/change-password-form.tsx @@ -77,6 +77,8 @@ export function ChangePasswordForm({ return; } + await new Promise((resolve) => setTimeout(resolve, 1000)); // wait for a second, to prevent eventual consistency issues + const passwordResponse = await sendPassword({ loginName, organization, diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 251c04956f..0e07767e1a 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -59,7 +59,7 @@ export function SetPasswordForm({ const [loading, setLoading] = useState(false); const [error, setError] = useState(""); - async function submitRegister(values: Inputs) { + async function submitPassword(values: Inputs) { setLoading(true); let payload: { userId: string; password: string; code?: string } = { userId: userId, @@ -73,16 +73,19 @@ export function SetPasswordForm({ const changeResponse = await changePassword(payload).catch(() => { setError("Could not set password"); + setLoading(false); + return; }); - if (changeResponse && "error" in changeResponse) { - setError(changeResponse.error); - } - setLoading(false); + if (changeResponse && "error" in changeResponse) { + setError(changeResponse.error); + return; + } + if (!changeResponse) { - setError("Could not register user"); + setError("Could not set password"); return; } @@ -95,6 +98,8 @@ export function SetPasswordForm({ 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({ loginName, organization, @@ -213,7 +218,7 @@ export function SetPasswordForm({ !formState.isValid || watchPassword !== watchConfirmPassword } - onClick={handleSubmit(submitRegister)} + onClick={handleSubmit(submitPassword)} > {loading && } {t("set.submit")} diff --git a/apps/login/src/components/verify-email-form.tsx b/apps/login/src/components/verify-form.tsx similarity index 72% rename from apps/login/src/components/verify-email-form.tsx rename to apps/login/src/components/verify-form.tsx index 31540b445b..4dfca56d79 100644 --- a/apps/login/src/components/verify-email-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -2,13 +2,12 @@ import { Alert } from "@/components/alert"; 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 { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { useForm } from "react-hook-form"; -import { PASSKEYS, PASSWORD } from "./auth-methods"; +import { AuthenticatorMethods } from "./authenticator-methods"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; @@ -18,25 +17,25 @@ type Inputs = { }; type Props = { - userId?: string; + userId: string; loginName: string; code: string; organization?: string; authRequestId?: string; sessionId?: string; - loginSettings?: LoginSettings; isInvite: boolean; + verifyError?: string; }; -export function VerifyEmailForm({ +export function VerifyForm({ userId, loginName, code, organization, authRequestId, sessionId, - loginSettings, isInvite, + verifyError, }: Props) { const t = useTranslations("verify"); const tError = useTranslations("error"); @@ -52,30 +51,19 @@ export function VerifyEmailForm({ AuthenticationMethodType[] | null >(null); - useEffect(() => { - 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(""); + const [error, setError] = useState(verifyError || ""); const [loading, setLoading] = useState(false); const router = useRouter(); - const params = new URLSearchParams({}); - - if (userId) { - params.append("userId", userId); - } + const params = new URLSearchParams({ + userId: userId, + }); if (isInvite) { params.append("initial", "true"); } - if (loginName) { params.append("loginName", loginName); } @@ -131,13 +119,10 @@ export function VerifyEmailForm({ // if auth methods fall trough, we complete to login const params = new URLSearchParams({ + userId: userId, 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) { params.set("organization", organization); } @@ -153,14 +138,8 @@ export function VerifyEmailForm({ return !authMethods ? ( <> -

    {t("title")}

    -

    {t("description")}

    - - {!userId && ( -
    - {tError("unknownContext")} -
    - )} +

    {t("verify.title")}

    +

    {t("verify.description")}

    @@ -169,7 +148,6 @@ export function VerifyEmailForm({ autoComplete="one-time-code" {...register("code", { required: "This field is required" })} label="Code" - // error={errors.username?.message as string} />
    @@ -185,7 +163,7 @@ export function VerifyEmailForm({ onClick={() => resendCode()} variant={ButtonVariants.Secondary} > - {t("resendCode")} + {t("verify.resendCode")}
    ) : ( <> -

    {t("title")}

    -

    {t("description")}

    +

    {t("setup.title")}

    +

    {t("setup.description")}

    -
    - {!authMethods.includes(AuthenticationMethodType.PASSWORD) && - PASSWORD(false, "/password/set?" + params)} - {!authMethods.includes(AuthenticationMethodType.PASSKEY) && - PASSKEYS(false, "/passkeys/set?" + params)} -
    + ); } diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 65dcc1494a..2b380bf325 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -7,7 +7,7 @@ import { getSession, setSession, } from "@/lib/zitadel"; -import { timestampDate } from "@zitadel/client"; +import { timestampMs } from "@zitadel/client"; import { Challenges, RequestChallenges, @@ -43,13 +43,13 @@ export async function createSessionAndUpdateCookie( id: createdSession.sessionId, token: createdSession.sessionToken, creationDate: response.session.creationDate - ? `${timestampDate(response.session.creationDate).toDateString()}` + ? `${timestampMs(response.session.creationDate)}` : "", expirationDate: response.session.expirationDate - ? `${timestampDate(response.session.expirationDate).toDateString()}` + ? `${timestampMs(response.session.expirationDate)}` : "", changeDate: response.session.changeDate - ? `${timestampDate(response.session.changeDate).toDateString()}` + ? `${timestampMs(response.session.changeDate)}` : "", loginName: response.session.factors.user.loginName ?? "", }; @@ -98,13 +98,13 @@ export async function createSessionForIdpAndUpdateCookie( id: createdSession.sessionId, token: createdSession.sessionToken, creationDate: response.session.creationDate - ? `${timestampDate(response.session.creationDate).toDateString()}` + ? `${timestampMs(response.session.creationDate)}` : "", expirationDate: response.session.expirationDate - ? `${timestampDate(response.session.expirationDate).toDateString()}` + ? `${timestampMs(response.session.expirationDate)}` : "", changeDate: response.session.changeDate - ? `${timestampDate(response.session.changeDate).toDateString()}` + ? `${timestampMs(response.session.changeDate)}` : "", loginName: response.session.factors.user.loginName ?? "", organization: response.session.factors.user.organizationId ?? "", @@ -155,7 +155,7 @@ export async function setSessionAndUpdateCookie( expirationDate: recentCookie.expirationDate, // just overwrite the changeDate with the new one changeDate: updatedSession.details?.changeDate - ? `${timestampDate(updatedSession.details.changeDate).toDateString()}` + ? `${timestampMs(updatedSession.details.changeDate)}` : "", loginName: recentCookie.loginName, organization: recentCookie.organization, @@ -178,7 +178,7 @@ export async function setSessionAndUpdateCookie( expirationDate: sessionCookie.expirationDate, // just overwrite the changeDate with the new one changeDate: updatedSession.details?.changeDate - ? `${timestampDate(updatedSession.details.changeDate).toDateString()}` + ? `${timestampMs(updatedSession.details.changeDate)}` : "", loginName: session.factors?.user?.loginName ?? "", organization: session.factors?.user?.organizationId ?? "", diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 2d67b46f3a..2a115b8cd4 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -139,6 +139,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (users.result[0].state === UserState.INITIAL) { const params = new URLSearchParams({ loginName: session.factors?.user?.loginName, + initial: "true", // this does not require a code to be set }); if (command.organization || session.factors?.user?.organizationId) { diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 4997f23ca0..2cb512bb97 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -284,5 +284,5 @@ export async function changePassword(command: { } const userId = user.userId; - return setPassword(userId, command.password, command.code); + return setPassword(userId, command.password, user, command.code); } diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 0f9a1534a2..864d19ae25 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -37,7 +37,11 @@ import { SearchQuery, SearchQuerySchema, } 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 { PROVIDER_MAPPING } from "./idp"; @@ -327,7 +331,7 @@ export async function createInviteCode(userId: string, host: string | null) { if (host) { 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) { 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( userId: string, password: string, + user: User, code?: string, ) { 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 ( - !authmethods || - !authmethods.authMethodTypes || - authmethods.authMethodTypes.length === 0 + !(authmethods.authMethodTypes.length === 0) && + user.state !== UserState.INITIAL ) { 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; + } + }); } /** diff --git a/packages/zitadel-client/src/index.ts b/packages/zitadel-client/src/index.ts index 2e262dd013..d78559257d 100644 --- a/packages/zitadel-client/src/index.ts +++ b/packages/zitadel-client/src/index.ts @@ -3,5 +3,5 @@ export { NewAuthorizationBearerInterceptor } from "./interceptors"; // TODO: Move this to `./protobuf.ts` and export it from there 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"; From cb2d5d645a0ef9a3c8b8ab19971bb39a00ff6eaa Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 23 Oct 2024 15:56:38 +0200 Subject: [PATCH 333/640] move auth setup to seperate page, create session after verification --- apps/login/locales/de.json | 9 +- apps/login/locales/en.json | 9 +- apps/login/locales/es.json | 9 +- apps/login/locales/it.json | 9 +- .../app/(login)/authenticator/set/page.tsx | 160 ++++++++++++++++++ .../src/app/(login)/passkey/set/page.tsx | 3 +- apps/login/src/app/(login)/verify/page.tsx | 69 ++------ .../src/components/authenticator-methods.tsx | 18 -- .../choose-authenticator-to-setup.tsx | 52 ++++++ apps/login/src/components/verify-form.tsx | 89 ++-------- apps/login/src/lib/server/email.ts | 40 +++-- 11 files changed, 291 insertions(+), 176 deletions(-) create mode 100644 apps/login/src/app/(login)/authenticator/set/page.tsx delete mode 100644 apps/login/src/components/authenticator-methods.tsx create mode 100644 apps/login/src/components/choose-authenticator-to-setup.tsx diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 5664819ef4..ad184962b2 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -160,12 +160,13 @@ "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." } }, + "authenticator": { + "title": "Authentifizierungsmethode auswählen", + "description": "Wählen Sie die Methode, mit der Sie sich authentifizieren möchten.", + "noMethodsAvailable": "Keine Authentifizierungsmethoden verfügbar" + }, "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.", "sessionExpired": "Ihre aktuelle Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.", diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index e512385902..70844a8a1f 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -160,12 +160,13 @@ "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" } }, + "authenticator": { + "title": "Choose authentication method", + "description": "Select the method you would like to authenticate", + "noMethodsAvailable": "No authentication methods available" + }, "error": { "unknownContext": "Could not get the context of the user. Make sure to enter the username first or provide a loginName as searchParam.", "sessionExpired": "Your current session has expired. Please login again.", diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index 3a5f9e1643..b5f67b40c3 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -160,12 +160,13 @@ "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" } }, + "authenticator": { + "title": "Seleccionar método de autenticación", + "description": "Selecciona el método con el que deseas autenticarte", + "noMethodsAvailable": "No hay métodos de autenticación disponibles" + }, "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.", "sessionExpired": "Tu sesión actual ha expirado. Por favor, inicia sesión de nuevo.", diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index 7086fc5f0f..b743609809 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -160,12 +160,13 @@ "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" } }, + "authenticator": { + "title": "Seleziona metodo di autenticazione", + "description": "Seleziona il metodo con cui desideri autenticarti", + "noMethodsAvailable": "Nessun metodo di autenticazione disponibile" + }, "error": { "unknownContext": "Impossibile ottenere il contesto dell'utente. Assicurati di inserire prima il nome utente o di fornire un loginName come parametro di ricerca.", "sessionExpired": "La tua sessione attuale è scaduta. Effettua nuovamente l'accesso.", diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx new file mode 100644 index 0000000000..d7daaec05b --- /dev/null +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -0,0 +1,160 @@ +import { Alert } from "@/components/alert"; +import { BackButton } from "@/components/back-button"; +import { ChooseAuthenticatorToSetup } from "@/components/choose-authenticator-to-setup"; +import { DynamicTheme } from "@/components/dynamic-theme"; +import { UserAvatar } from "@/components/user-avatar"; +import { getSessionCookieById } from "@/lib/cookies"; +import { loadMostRecentSession } from "@/lib/session"; +import { + getBrandingSettings, + getLoginSettings, + getSession, + getUserByID, + listAuthenticationMethodTypes, +} from "@/lib/zitadel"; +import { Timestamp, timestampDate } from "@zitadel/client"; +import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { getLocale, getTranslations } from "next-intl/server"; + +function isSessionValid(session: Partial): { + valid: boolean; + verifiedAt?: Timestamp; +} { + const validPassword = session?.factors?.password?.verifiedAt; + const validPasskey = session?.factors?.webAuthN?.verifiedAt; + const stillValid = session.expirationDate + ? timestampDate(session.expirationDate) > new Date() + : true; + + const verifiedAt = validPassword || validPasskey; + const valid = !!((validPassword || validPasskey) && stillValid); + + return { valid, verifiedAt }; +} + +export default async function Page({ + searchParams, +}: { + searchParams: Record; +}) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "authenticator" }); + const tError = await getTranslations({ locale, namespace: "error" }); + + const { loginName, checkAfter, authRequestId, organization, sessionId } = + searchParams; + + const sessionWithData = sessionId + ? await loadSessionById(sessionId, organization) + : await loadSessionByLoginname(loginName, organization); + + async function getAuthMethodsAndUser(session?: Session) { + const userId = session?.factors?.user?.id; + + if (!userId) { + throw Error("Could not get user id from session"); + } + + return listAuthenticationMethodTypes(userId).then((methods) => { + return getUserByID(userId).then((user) => { + const humanUser = + user.user?.type.case === "human" ? user.user?.type.value : undefined; + + return { + factors: session?.factors, + authMethods: methods.authMethodTypes ?? [], + phoneVerified: humanUser?.phone?.isVerified ?? false, + emailVerified: humanUser?.email?.isVerified ?? false, + expirationDate: session?.expirationDate, + }; + }); + }); + } + + async function loadSessionByLoginname( + loginName?: string, + organization?: string, + ) { + return loadMostRecentSession({ + loginName, + organization, + }).then((session) => { + return getAuthMethodsAndUser(session); + }); + } + + async function loadSessionById(sessionId: string, organization?: string) { + const recent = await getSessionCookieById({ sessionId, organization }); + return getSession({ + sessionId: recent.id, + sessionToken: recent.token, + }).then((sessionResponse) => { + return getAuthMethodsAndUser(sessionResponse.session); + }); + } + + const branding = await getBrandingSettings(organization); + + const loginSettings = await getLoginSettings( + sessionWithData.factors?.user?.organizationId, + ); + + const { valid } = isSessionValid(sessionWithData); + + const params = new URLSearchParams({ + initial: "true", // defines that a code is not required and is therefore not shown in the UI + }); + + // if (sessionWithData?.factors?.user?.id) { + // params.set("userId", sessionWithData.factors.user.id); + // } + + if (loginName) { + params.set("loginName", loginName); + } + + if (organization) { + params.set("organization", organization); + } + + if (authRequestId) { + params.set("authRequestId", authRequestId); + } + + return ( + +
    +

    {t("set.title")}

    + +

    {t("set.description")}

    + + {sessionWithData && ( + + )} + + {!(loginName || sessionId) && {tError("unknownContext")}} + + {!valid && {tError("sessionExpired")}} + + {loginSettings && sessionWithData && ( + + )} + +
    + + +
    +
    +
    + ); +} diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx index 26015e99a9..45ea1d7803 100644 --- a/apps/login/src/app/(login)/passkey/set/page.tsx +++ b/apps/login/src/app/(login)/passkey/set/page.tsx @@ -15,7 +15,8 @@ export default async function Page({ const t = await getTranslations({ locale, namespace: "passkey" }); const tError = await getTranslations({ locale, namespace: "error" }); - const { loginName, prompt, organization, authRequestId } = searchParams; + const { loginName, prompt, organization, authRequestId, userId } = + searchParams; const session = await loadMostRecentSession({ loginName, diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 1a81c3ccb6..1c6b629c4c 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -1,9 +1,6 @@ import { Alert } from "@/components/alert"; -import { AuthenticatorMethods } from "@/components/authenticator-methods"; import { DynamicTheme } from "@/components/dynamic-theme"; -import { UserAvatar } from "@/components/user-avatar"; 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"; @@ -13,29 +10,11 @@ export default async function Page({ searchParams }: { searchParams: any }) { const t = await getTranslations({ locale, namespace: "verify" }); const tError = await getTranslations({ locale, namespace: "error" }); - const { - userId, - loginName, - sessionId, - code, - organization, - authRequestId, - invite, - } = searchParams; + const { userId, loginName, code, organization, authRequestId, invite } = + searchParams; const branding = await getBrandingSettings(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) { @@ -53,16 +32,16 @@ export default async function Page({ searchParams }: { searchParams: any }) { initial: "true", // defines that a code is not required and is therefore not shown in the UI }); + if (loginName) { + params.set("loginName", loginName); + } + if (organization) { params.set("organization", organization); } if (authRequestId) { - params.set("authRequest", authRequestId); - } - - if (sessionId) { - params.set("sessionId", sessionId); + params.set("authRequestId", authRequestId); } return ( @@ -78,33 +57,13 @@ export default async function Page({ searchParams }: { searchParams: any }) {
    )} - {!verifyResponse || !verifyResponse.authMethodTypes ? ( - - ) : ( - <> -

    {t("setup.title")}

    -

    {t("setup.description")}

    - {user && ( - - )} - - - )} + +
    ); diff --git a/apps/login/src/components/authenticator-methods.tsx b/apps/login/src/components/authenticator-methods.tsx deleted file mode 100644 index bd90f19b87..0000000000 --- a/apps/login/src/components/authenticator-methods.tsx +++ /dev/null @@ -1,18 +0,0 @@ -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 ( -
    - {!authMethods.includes(AuthenticationMethodType.PASSWORD) && - PASSWORD(false, "/password/set?" + params)} - {!authMethods.includes(AuthenticationMethodType.PASSKEY) && - PASSKEYS(false, "/passkeys/set?" + params)} -
    - ); -} diff --git a/apps/login/src/components/choose-authenticator-to-setup.tsx b/apps/login/src/components/choose-authenticator-to-setup.tsx new file mode 100644 index 0000000000..171fabd3cf --- /dev/null +++ b/apps/login/src/components/choose-authenticator-to-setup.tsx @@ -0,0 +1,52 @@ +import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { + LoginSettings, + PasskeysType, +} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { useTranslations } from "next-intl"; +import { Alert, AlertType } from "./alert"; +import { PASSKEYS, PASSWORD } from "./auth-methods"; +import { UserAvatar } from "./user-avatar"; + +type Props = { + authMethods: AuthenticationMethodType[]; + params: URLSearchParams; + sessionFactors?: Factors; + loginSettings: LoginSettings; +}; + +export function ChooseAuthenticatorToSetup({ + authMethods, + params, + sessionFactors, + loginSettings, +}: Props) { + const t = useTranslations("authenticator"); + + return ( + <> + {sessionFactors && ( + + )} + + {loginSettings.passkeysType === PasskeysType.ALLOWED && + !loginSettings.allowUsernamePassword && ( + {t("noMethodsAvailable")} + )} + +
    + {!authMethods.includes(AuthenticationMethodType.PASSWORD) && + loginSettings.allowUsernamePassword && + PASSWORD(false, "/password/set?" + params)} + {!authMethods.includes(AuthenticationMethodType.PASSKEY) && + loginSettings.passkeysType === PasskeysType.ALLOWED && + PASSKEYS(false, "/passkeys/set?" + params)} +
    + + ); +} diff --git a/apps/login/src/components/verify-form.tsx b/apps/login/src/components/verify-form.tsx index 4dfca56d79..d9cea2017e 100644 --- a/apps/login/src/components/verify-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -1,13 +1,14 @@ "use client"; import { Alert } from "@/components/alert"; -import { resendVerification, verifyUser } from "@/lib/server/email"; -import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { + resendVerification, + verifyUserAndCreateSession, +} from "@/lib/server/email"; import { useTranslations } from "next-intl"; -import { useRouter } from "next/navigation"; +import { useRouter } from "next/router"; import { useState } from "react"; import { useForm } from "react-hook-form"; -import { AuthenticatorMethods } from "./authenticator-methods"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; @@ -18,25 +19,12 @@ type Inputs = { type Props = { userId: string; - loginName: string; - code: string; - organization?: string; - authRequestId?: string; - sessionId?: string; + code?: string; isInvite: boolean; - verifyError?: string; + params: URLSearchParams; }; -export function VerifyForm({ - userId, - loginName, - code, - organization, - authRequestId, - sessionId, - isInvite, - verifyError, -}: Props) { +export function VerifyForm({ userId, code, isInvite, params }: Props) { const t = useTranslations("verify"); const tError = useTranslations("error"); @@ -47,36 +35,12 @@ export function VerifyForm({ }, }); - const [authMethods, setAuthMethods] = useState< - AuthenticationMethodType[] | null - >(null); - - const [error, setError] = useState(verifyError || ""); + const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const router = useRouter(); - const params = new URLSearchParams({ - userId: userId, - }); - - if (isInvite) { - params.append("initial", "true"); - } - if (loginName) { - params.append("loginName", loginName); - } - if (sessionId) { - params.append("sessionId", sessionId); - } - if (authRequestId) { - params.append("authRequestId", authRequestId); - } - if (organization) { - params.append("organization", organization); - } - async function resendCode() { setLoading(true); @@ -96,12 +60,13 @@ export function VerifyForm({ async function submitCodeAndContinue(value: Inputs): Promise { setLoading(true); - const verifyResponse = await verifyUser({ + const verifyResponse = await verifyUserAndCreateSession({ code: value.code, userId, isInvite: isInvite, }).catch(() => { setError("Could not verify email"); + setLoading(false); return; }); @@ -110,33 +75,12 @@ export function VerifyForm({ if (!verifyResponse) { setError("Could not verify email"); return; - } - - if (verifyResponse.authMethodTypes) { - setAuthMethods(verifyResponse.authMethodTypes); - return; - } - - // if auth methods fall trough, we complete to login - 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 && sessionId) { - params.set("authRequest", authRequestId); - params.set("sessionId", sessionId); - return router.push(`/login?` + params); } else { - return router.push(`/loginname?` + params); + router.push("/authenticator/set?" + params); } } - return !authMethods ? ( + return ( <>

    {t("verify.title")}

    {t("verify.description")}

    @@ -179,12 +123,5 @@ export function VerifyForm({
    - ) : ( - <> -

    {t("setup.title")}

    -

    {t("setup.description")}

    - - - ); } diff --git a/apps/login/src/lib/server/email.ts b/apps/login/src/lib/server/email.ts index 6e05e7de46..8e34640fd7 100644 --- a/apps/login/src/lib/server/email.ts +++ b/apps/login/src/lib/server/email.ts @@ -1,27 +1,31 @@ "use server"; import { - listAuthenticationMethodTypes, + getUserByID, resendEmailCode, resendInviteCode, verifyEmail, verifyInviteCode, } from "@/lib/zitadel"; +import { create } from "@zitadel/client"; +import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { createSessionAndUpdateCookie } from "./cookie"; type VerifyUserByEmailCommand = { userId: string; code: string; isInvite: boolean; + authRequestId?: string; }; -export async function verifyUser(command: VerifyUserByEmailCommand) { +export async function verifyUserAndCreateSession( + command: VerifyUserByEmailCommand, +) { const verifyResponse = command.isInvite ? await verifyInviteCode(command.userId, command.code).catch((error) => { - console.log(error.code); return { error: "Could not verify invite" }; }) : await verifyEmail(command.userId, command.code).catch((error) => { - console.log(error.code); return { error: "Could not verify email" }; }); @@ -29,15 +33,31 @@ export async function verifyUser(command: VerifyUserByEmailCommand) { return { error: "Could not verify user" }; } - const authMethodResponse = await listAuthenticationMethodTypes( - command.userId, - ); + const userResponse = await getUserByID(command.userId); - if (!authMethodResponse || !authMethodResponse.authMethodTypes) { - return { error: "Could not load possible authenticators" }; + if (!userResponse || !userResponse.user) { + return { error: "Could not load user" }; } - return { authMethodTypes: authMethodResponse.authMethodTypes }; + const checks = create(ChecksSchema, { + user: { + search: { + case: "loginName", + value: userResponse.user.preferredLoginName, + }, + }, + }); + + const session = await createSessionAndUpdateCookie( + checks, + undefined, + command.authRequestId, + ); + + return { + sessionId: session.id, + factors: session.factors, + }; } type resendVerifyEmailCommand = { From f45c5304a7df05090c71b7647181bd0cff8678cd Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 23 Oct 2024 17:16:37 +0200 Subject: [PATCH 334/640] redirect from server action --- apps/login/cypress/integration/verify.cy.ts | 8 +++-- apps/login/src/components/invite-form.tsx | 2 ++ apps/login/src/components/verify-form.tsx | 3 +- apps/login/src/lib/server/email.ts | 37 ++++++++++++++++++--- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index 8beba527c5..886ea29882 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -4,8 +4,10 @@ describe("/verify", () => { it("shows password and passkey method after successful invite verification", () => { stub("zitadel.user.v2.UserService", "VerifyEmail"); cy.visit("/verify?userId=123&code=abc&submit=true&invite=true"); - cy.contains("Password"); - cy.contains("Passkey"); + cy.location("pathname", { timeout: 10_000 }).should( + "eq", + "/authenticator/set", + ); }); it("shows an error if validation failed", () => { stub("zitadel.user.v2.UserService", "VerifyEmail", { @@ -14,7 +16,7 @@ describe("/verify", () => { }); // TODO: Avoid uncaught exception in application cy.once("uncaught:exception", () => false); - cy.visit("/verify?userId=123&code=abc&submit=true"); + cy.visit("/verify?userId=123&code=abc&submit=true&invite=true"); cy.contains("Could not verify user"); }); }); diff --git a/apps/login/src/components/invite-form.tsx b/apps/login/src/components/invite-form.tsx index d7b0aadd6f..a8a84e8150 100644 --- a/apps/login/src/components/invite-form.tsx +++ b/apps/login/src/components/invite-form.tsx @@ -58,6 +58,7 @@ export function InviteForm({ }).catch(() => { setError("Could not create invitation Code"); setLoading(false); + return; }); setLoading(false); @@ -69,6 +70,7 @@ export function InviteForm({ if (!response) { setError("Could not create invitation Code"); + return; } const params = new URLSearchParams({}); diff --git a/apps/login/src/components/verify-form.tsx b/apps/login/src/components/verify-form.tsx index d9cea2017e..b78c367469 100644 --- a/apps/login/src/components/verify-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -6,7 +6,7 @@ import { verifyUserAndCreateSession, } from "@/lib/server/email"; import { useTranslations } from "next-intl"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { Button, ButtonVariants } from "./button"; @@ -65,6 +65,7 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { userId, isInvite: isInvite, }).catch(() => { + console.log(error); setError("Could not verify email"); setLoading(false); return; diff --git a/apps/login/src/lib/server/email.ts b/apps/login/src/lib/server/email.ts index 8e34640fd7..fce219715f 100644 --- a/apps/login/src/lib/server/email.ts +++ b/apps/login/src/lib/server/email.ts @@ -2,6 +2,7 @@ import { getUserByID, + listAuthenticationMethodTypes, resendEmailCode, resendInviteCode, verifyEmail, @@ -9,6 +10,7 @@ import { } from "@/lib/zitadel"; import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { redirect } from "next/navigation"; import { createSessionAndUpdateCookie } from "./cookie"; type VerifyUserByEmailCommand = { @@ -54,10 +56,37 @@ export async function verifyUserAndCreateSession( command.authRequestId, ); - return { - sessionId: session.id, - factors: session.factors, - }; + const authMethodResponse = await listAuthenticationMethodTypes( + command.userId, + ); + + if (!authMethodResponse || !authMethodResponse.authMethodTypes) { + return { error: "Could not load possible authenticators" }; + } + console.log("xs"); + // if no authmethods are found on the user, redirect to set one up + if ( + authMethodResponse && + authMethodResponse.authMethodTypes && + authMethodResponse.authMethodTypes.length == 0 + ) { + const params = new URLSearchParams({ + sessionId: session.id, + }); + + if (session.factors?.user?.loginName) { + params.set("loginName", session.factors?.user?.loginName); + } + + console.log("/authenticator/set?" + params); + return redirect("/authenticator/set?" + params); + } + + // return { + // authMethodTypes: authMethodResponse.authMethodTypes, + // sessionId: session.id, + // factors: session.factors, + // }; } type resendVerifyEmailCommand = { From c4da6fd07740c001da735d4fd09caaa2feb78b96 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 24 Oct 2024 10:02:12 +0200 Subject: [PATCH 335/640] ts for cookie, loginname to verification --- apps/login/locales/de.json | 1 + apps/login/locales/en.json | 1 + apps/login/locales/es.json | 1 + apps/login/locales/it.json | 1 + .../app/(login)/authenticator/set/page.tsx | 14 ++++--- apps/login/src/app/(login)/verify/page.tsx | 30 ++++++++++---- apps/login/src/components/verify-form.tsx | 18 ++++----- apps/login/src/lib/cookies.ts | 39 +++++++++++-------- apps/login/src/lib/server/cookie.ts | 30 +++++++------- apps/login/src/lib/server/email.ts | 4 +- apps/login/src/lib/server/loginname.ts | 24 ++++++++++++ packages/zitadel-client/src/index.ts | 2 +- 12 files changed, 107 insertions(+), 58 deletions(-) diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index ad184962b2..0a15315a6f 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -155,6 +155,7 @@ }, "verify": { "userIdMissing": "Keine Benutzer-ID angegeben!", + "success": "Erfolgreich verifiziert", "verify": { "title": "Benutzer verifizieren", "description": "Geben Sie den Code ein, der in der Bestätigungs-E-Mail angegeben ist.", diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 70844a8a1f..f14a983509 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -155,6 +155,7 @@ }, "verify": { "userIdMissing": "No userId provided!", + "success": "The user has been verified successfully.", "verify": { "title": "Verify user", "description": "Enter the Code provided in the verification email.", diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index b5f67b40c3..b1f50506fb 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -155,6 +155,7 @@ }, "verify": { "userIdMissing": "¡No se proporcionó userId!", + "success": "¡Verificación exitosa!", "verify": { "title": "Verificar usuario", "description": "Introduce el código proporcionado en el correo electrónico de verificación.", diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index b743609809..31f80a1f63 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -155,6 +155,7 @@ }, "verify": { "userIdMissing": "Nessun userId fornito!", + "success": "Verifica effettuata con successo!", "verify": { "title": "Verifica utente", "description": "Inserisci il codice fornito nell'email di verifica.", diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index d7daaec05b..aa66a19237 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -1,6 +1,5 @@ import { Alert } from "@/components/alert"; import { BackButton } from "@/components/back-button"; -import { ChooseAuthenticatorToSetup } from "@/components/choose-authenticator-to-setup"; import { DynamicTheme } from "@/components/dynamic-theme"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; @@ -41,13 +40,14 @@ export default async function Page({ const t = await getTranslations({ locale, namespace: "authenticator" }); const tError = await getTranslations({ locale, namespace: "error" }); - const { loginName, checkAfter, authRequestId, organization, sessionId } = - searchParams; + const { loginName, authRequestId, organization, sessionId } = searchParams; const sessionWithData = sessionId ? await loadSessionById(sessionId, organization) : await loadSessionByLoginname(loginName, organization); + console.log("sessionWithData", sessionWithData); + async function getAuthMethodsAndUser(session?: Session) { const userId = session?.factors?.user?.id; @@ -93,7 +93,9 @@ export default async function Page({ }); } - const branding = await getBrandingSettings(organization); + const branding = await getBrandingSettings( + sessionWithData.factors?.user?.organizationId, + ); const loginSettings = await getLoginSettings( sessionWithData.factors?.user?.organizationId, @@ -141,14 +143,14 @@ export default async function Page({ {!valid && {tError("sessionExpired")}} - {loginSettings && sessionWithData && ( + {/* {loginSettings && sessionWithData && ( - )} + )} */}
    diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 1c6b629c4c..368e9c1c5c 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -1,5 +1,6 @@ -import { Alert } from "@/components/alert"; +import { Alert, AlertType } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; +import { UserAvatar } from "@/components/user-avatar"; import { VerifyForm } from "@/components/verify-form"; import { getBrandingSettings, getUserByID } from "@/lib/zitadel"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; @@ -47,6 +48,9 @@ export default async function Page({ searchParams }: { searchParams: any }) { return (
    +

    {t("verify.title")}

    +

    {t("verify.description")}

    + {!userId && ( <>

    {t("verify.title")}

    @@ -58,12 +62,24 @@ export default async function Page({ searchParams }: { searchParams: any }) { )} - + {user && ( + + )} + {human?.email?.isVerified ? ( + {t("success")} + ) : ( + // check if auth methods are set + + )}
    ); diff --git a/apps/login/src/components/verify-form.tsx b/apps/login/src/components/verify-form.tsx index b78c367469..d974c1a949 100644 --- a/apps/login/src/components/verify-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -1,13 +1,10 @@ "use client"; import { Alert } from "@/components/alert"; -import { - resendVerification, - verifyUserAndCreateSession, -} from "@/lib/server/email"; +import { resendVerification, sendVerification } from "@/lib/server/email"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; @@ -41,6 +38,12 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { const router = useRouter(); + useEffect(() => { + if (code) { + submitCodeAndContinue({ code }); + } + }, []); + async function resendCode() { setLoading(true); @@ -60,7 +63,7 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { async function submitCodeAndContinue(value: Inputs): Promise { setLoading(true); - const verifyResponse = await verifyUserAndCreateSession({ + const verifyResponse = await sendVerification({ code: value.code, userId, isInvite: isInvite, @@ -83,9 +86,6 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { return ( <> -

    {t("verify.title")}

    -

    {t("verify.description")}

    -
    ( // TODO: improve cookie handling // this replaces the first session (oldest) with the new one currentSessions = [session].concat(currentSessions.slice(1)); + } else { + currentSessions = [session].concat(currentSessions); } } if (cleanup) { const now = new Date(); const filteredSessions = currentSessions.filter((session) => - session.expirationDate ? new Date(session.expirationDate) > now : true, + session.expirationTs + ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now + : true, ); return setSessionHttpOnlyCookie(filteredSessions); } else { @@ -99,7 +104,9 @@ export async function updateSessionCookie( if (cleanup) { const now = new Date(); const filteredSessions = sessions.filter((session) => - session.expirationDate ? new Date(session.expirationDate) > now : true, + session.expirationTs + ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now + : true, ); return setSessionHttpOnlyCookie(filteredSessions); } else { @@ -125,7 +132,9 @@ export async function removeSessionFromCookie( if (cleanup) { const now = new Date(); const filteredSessions = reducedSessions.filter((session) => - session.expirationDate ? new Date(session.expirationDate) > now : true, + session.expirationTs + ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now + : true, ); return setSessionHttpOnlyCookie(filteredSessions); } else { @@ -141,10 +150,7 @@ export async function getMostRecentSessionCookie(): Promise { const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); const latest = sessions.reduce((prev, current) => { - return new Date(prev.changeDate).getTime() > - new Date(current.changeDate).getTime() - ? prev - : current; + return prev.changeTs > current.changeTs ? prev : current; }); return latest; @@ -226,8 +232,8 @@ export async function getAllSessionCookieIds( const now = new Date(); return sessions .filter((session) => - session.expirationDate - ? new Date(session.expirationDate) > now + session.expirationTs + ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now : true, ) .map((session) => session.id); @@ -256,7 +262,9 @@ export async function getAllSessions( if (cleanup) { const now = new Date(); return sessions.filter((session) => - session.expirationDate ? new Date(session.expirationDate) > now : true, + session.expirationTs + ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now + : true, ); } else { return sessions; @@ -297,10 +305,7 @@ export async function getMostRecentCookieWithLoginname({ const latest = filtered && filtered.length ? filtered.reduce((prev, current) => { - return new Date(prev.changeDate).getTime() > - new Date(current.changeDate).getTime() - ? prev - : current; + return prev.changeTs > current.changeTs ? prev : current; }) : undefined; diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 2b380bf325..17be3844aa 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -20,9 +20,9 @@ type CustomCookieData = { token: string; loginName: string; organization?: string; - creationDate: string; - expirationDate: string; - changeDate: string; + creationTs: string; + expirationTs: string; + changeTs: string; authRequestId?: string; // if its linked to an OIDC flow }; @@ -42,13 +42,13 @@ export async function createSessionAndUpdateCookie( const sessionCookie: CustomCookieData = { id: createdSession.sessionId, token: createdSession.sessionToken, - creationDate: response.session.creationDate + creationTs: response.session.creationDate ? `${timestampMs(response.session.creationDate)}` : "", - expirationDate: response.session.expirationDate + expirationTs: response.session.expirationDate ? `${timestampMs(response.session.expirationDate)}` : "", - changeDate: response.session.changeDate + changeTs: response.session.changeDate ? `${timestampMs(response.session.changeDate)}` : "", loginName: response.session.factors.user.loginName ?? "", @@ -97,13 +97,13 @@ export async function createSessionForIdpAndUpdateCookie( const sessionCookie: CustomCookieData = { id: createdSession.sessionId, token: createdSession.sessionToken, - creationDate: response.session.creationDate + creationTs: response.session.creationDate ? `${timestampMs(response.session.creationDate)}` : "", - expirationDate: response.session.expirationDate + expirationTs: response.session.expirationDate ? `${timestampMs(response.session.expirationDate)}` : "", - changeDate: response.session.changeDate + changeTs: response.session.changeDate ? `${timestampMs(response.session.changeDate)}` : "", loginName: response.session.factors.user.loginName ?? "", @@ -151,10 +151,10 @@ export async function setSessionAndUpdateCookie( const sessionCookie: CustomCookieData = { id: recentCookie.id, token: updatedSession.sessionToken, - creationDate: recentCookie.creationDate, - expirationDate: recentCookie.expirationDate, + creationTs: recentCookie.creationTs, + expirationTs: recentCookie.expirationTs, // just overwrite the changeDate with the new one - changeDate: updatedSession.details?.changeDate + changeTs: updatedSession.details?.changeDate ? `${timestampMs(updatedSession.details.changeDate)}` : "", loginName: recentCookie.loginName, @@ -174,10 +174,10 @@ export async function setSessionAndUpdateCookie( const newCookie: CustomCookieData = { id: sessionCookie.id, token: updatedSession.sessionToken, - creationDate: sessionCookie.creationDate, - expirationDate: sessionCookie.expirationDate, + creationTs: sessionCookie.creationTs, + expirationTs: sessionCookie.expirationTs, // just overwrite the changeDate with the new one - changeDate: updatedSession.details?.changeDate + changeTs: updatedSession.details?.changeDate ? `${timestampMs(updatedSession.details.changeDate)}` : "", loginName: session.factors?.user?.loginName ?? "", diff --git a/apps/login/src/lib/server/email.ts b/apps/login/src/lib/server/email.ts index fce219715f..ff08d3fa0a 100644 --- a/apps/login/src/lib/server/email.ts +++ b/apps/login/src/lib/server/email.ts @@ -20,9 +20,7 @@ type VerifyUserByEmailCommand = { authRequestId?: string; }; -export async function verifyUserAndCreateSession( - command: VerifyUserByEmailCommand, -) { +export async function sendVerification(command: VerifyUserByEmailCommand) { const verifyResponse = command.isInvite ? await verifyInviteCode(command.userId, command.code).catch((error) => { return { error: "Could not verify invite" }; diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 2a115b8cd4..fc5fd77c5c 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -7,6 +7,7 @@ import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_se import { headers } from "next/headers"; import { redirect } from "next/navigation"; import { idpTypeToIdentityProviderType, idpTypeToSlug } from "../idp"; + import { getActiveIdentityProviders, getIDPByID, @@ -161,6 +162,29 @@ export async function sendLoginname(command: SendLoginnameCommand) { ); if (!methods.authMethodTypes || !methods.authMethodTypes.length) { + if ( + users.result[0].type.case === "human" && + users.result[0].type.value.email && + !users.result[0].type.value.email.isVerified + ) { + const paramsVerify = new URLSearchParams({ + loginName: session.factors?.user?.loginName, + userId: session.factors?.user?.id, // verify needs user id + }); + + if (command.organization || session.factors?.user?.organizationId) { + paramsVerify.append( + "organization", + command.organization ?? session.factors?.user?.organizationId, + ); + } + + if (command.authRequestId) { + paramsVerify.append("authRequestId", command.authRequestId); + } + + redirect("/verify?" + paramsVerify); + } return { error: "User has no available authentication methods. Contact your administrator to setup authentication for the requested user.", diff --git a/packages/zitadel-client/src/index.ts b/packages/zitadel-client/src/index.ts index d78559257d..dd677643fc 100644 --- a/packages/zitadel-client/src/index.ts +++ b/packages/zitadel-client/src/index.ts @@ -3,5 +3,5 @@ export { NewAuthorizationBearerInterceptor } from "./interceptors"; // TODO: Move this to `./protobuf.ts` and export it from there export { create, fromJson, toJson } from "@bufbuild/protobuf"; -export { TimestampSchema, timestampDate, timestampFromDate, timestampMs } from "@bufbuild/protobuf/wkt"; +export { TimestampSchema, timestampDate, timestampFromDate, timestampFromMs, timestampMs } from "@bufbuild/protobuf/wkt"; export type { Timestamp } from "@bufbuild/protobuf/wkt"; From 4d0e4bc74763ec36ba4af9eb79aebe87b2ce2f98 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 24 Oct 2024 10:39:38 +0200 Subject: [PATCH 336/640] loginname to authenticator setup --- .../app/(login)/authenticator/set/page.tsx | 36 +++---------------- .../choose-authenticator-to-setup.tsx | 11 +----- apps/login/src/lib/server/loginname.ts | 27 +++++++++++--- 3 files changed, 29 insertions(+), 45 deletions(-) diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index aa66a19237..db1686dd30 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -1,5 +1,6 @@ import { Alert } from "@/components/alert"; import { BackButton } from "@/components/back-button"; +import { ChooseAuthenticatorToSetup } from "@/components/choose-authenticator-to-setup"; import { DynamicTheme } from "@/components/dynamic-theme"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; @@ -11,26 +12,9 @@ import { getUserByID, listAuthenticationMethodTypes, } from "@/lib/zitadel"; -import { Timestamp, timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { getLocale, getTranslations } from "next-intl/server"; -function isSessionValid(session: Partial): { - valid: boolean; - verifiedAt?: Timestamp; -} { - const validPassword = session?.factors?.password?.verifiedAt; - const validPasskey = session?.factors?.webAuthN?.verifiedAt; - const stillValid = session.expirationDate - ? timestampDate(session.expirationDate) > new Date() - : true; - - const verifiedAt = validPassword || validPasskey; - const valid = !!((validPassword || validPasskey) && stillValid); - - return { valid, verifiedAt }; -} - export default async function Page({ searchParams, }: { @@ -46,8 +30,6 @@ export default async function Page({ ? await loadSessionById(sessionId, organization) : await loadSessionByLoginname(loginName, organization); - console.log("sessionWithData", sessionWithData); - async function getAuthMethodsAndUser(session?: Session) { const userId = session?.factors?.user?.id; @@ -101,16 +83,10 @@ export default async function Page({ sessionWithData.factors?.user?.organizationId, ); - const { valid } = isSessionValid(sessionWithData); - const params = new URLSearchParams({ initial: "true", // defines that a code is not required and is therefore not shown in the UI }); - // if (sessionWithData?.factors?.user?.id) { - // params.set("userId", sessionWithData.factors.user.id); - // } - if (loginName) { params.set("loginName", loginName); } @@ -126,9 +102,9 @@ export default async function Page({ return (
    -

    {t("set.title")}

    +

    {t("title")}

    -

    {t("set.description")}

    +

    {t("description")}

    {sessionWithData && ( {tError("unknownContext")}} - {!valid && {tError("sessionExpired")}} - - {/* {loginSettings && sessionWithData && ( + {loginSettings && sessionWithData && ( - )} */} + )}
    diff --git a/apps/login/src/components/choose-authenticator-to-setup.tsx b/apps/login/src/components/choose-authenticator-to-setup.tsx index 171fabd3cf..250ed1bd5f 100644 --- a/apps/login/src/components/choose-authenticator-to-setup.tsx +++ b/apps/login/src/components/choose-authenticator-to-setup.tsx @@ -7,7 +7,6 @@ import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_se import { useTranslations } from "next-intl"; import { Alert, AlertType } from "./alert"; import { PASSKEYS, PASSWORD } from "./auth-methods"; -import { UserAvatar } from "./user-avatar"; type Props = { authMethods: AuthenticationMethodType[]; @@ -26,14 +25,6 @@ export function ChooseAuthenticatorToSetup({ return ( <> - {sessionFactors && ( - - )} - {loginSettings.passkeysType === PasskeysType.ALLOWED && !loginSettings.allowUsernamePassword && ( {t("noMethodsAvailable")} @@ -45,7 +36,7 @@ export function ChooseAuthenticatorToSetup({ PASSWORD(false, "/password/set?" + params)} {!authMethods.includes(AuthenticationMethodType.PASSKEY) && loginSettings.passkeysType === PasskeysType.ALLOWED && - PASSKEYS(false, "/passkeys/set?" + params)} + PASSKEYS(false, "/passkey/set?" + params)}
    ); diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index fc5fd77c5c..0f4d392ef2 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -185,10 +185,29 @@ export async function sendLoginname(command: SendLoginnameCommand) { redirect("/verify?" + paramsVerify); } - return { - error: - "User has no available authentication methods. Contact your administrator to setup authentication for the requested user.", - }; + // what to do with users with valid email but no auth methods? redirect to /authenticator/set? + // return { + // error: + // "User has no available authentication methods. Contact your administrator to setup authentication for the requested user.", + // }; + + const paramsAuthenticatorSetup = new URLSearchParams({ + loginName: session.factors?.user?.loginName, + userId: session.factors?.user?.id, // verify needs user id + }); + + if (command.organization || session.factors?.user?.organizationId) { + paramsAuthenticatorSetup.append( + "organization", + command.organization ?? session.factors?.user?.organizationId, + ); + } + + if (command.authRequestId) { + paramsAuthenticatorSetup.append("authRequestId", command.authRequestId); + } + + redirect("/authenticator/set?" + paramsAuthenticatorSetup); } if (methods.authMethodTypes.length == 1) { From fb656bc5e9f4dfb5c64b555b3558f7f3cee49790 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 24 Oct 2024 14:04:07 +0200 Subject: [PATCH 337/640] fix overflow --- apps/login/.gitignore | 3 ++- apps/login/src/app/(login)/layout.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/login/.gitignore b/apps/login/.gitignore index e2a663b546..63ddd0c9eb 100644 --- a/apps/login/.gitignore +++ b/apps/login/.gitignore @@ -1 +1,2 @@ -custom-config.js \ No newline at end of file +custom-config.js +.env.local \ No newline at end of file diff --git a/apps/login/src/app/(login)/layout.tsx b/apps/login/src/app/(login)/layout.tsx index e218bbcfae..a53b6b7c28 100644 --- a/apps/login/src/app/(login)/layout.tsx +++ b/apps/login/src/app/(login)/layout.tsx @@ -35,7 +35,7 @@ export default async function RootLayout({
    From 93413bd3f4a938cd4d1c87b10db4b7b3842b7dd0 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 24 Oct 2024 14:22:03 +0200 Subject: [PATCH 338/640] fix register session --- apps/login/cypress/integration/register.cy.ts | 2 +- .../register-form-without-password.tsx | 1 + apps/login/src/lib/server/register.ts | 19 +++++++++++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/login/cypress/integration/register.cy.ts b/apps/login/cypress/integration/register.cy.ts index 9eb4e8be15..02d0d45db4 100644 --- a/apps/login/cypress/integration/register.cy.ts +++ b/apps/login/cypress/integration/register.cy.ts @@ -9,7 +9,7 @@ describe("register", () => { }); }); - it("should redirect a user who selects passwordless on register to /passkeys/add", () => { + it("should redirect a user who selects passwordless on register to /passkey/set", () => { cy.visit("/register"); cy.get('input[autocomplete="firstname"]').focus().type("John"); cy.get('input[autocomplete="lastname"]').focus().type("Doe"); diff --git a/apps/login/src/components/register-form-without-password.tsx b/apps/login/src/components/register-form-without-password.tsx index 774feb64d0..c0b9ba25e3 100644 --- a/apps/login/src/components/register-form-without-password.tsx +++ b/apps/login/src/components/register-form-without-password.tsx @@ -69,6 +69,7 @@ export function RegisterFormWithoutPassword({ }).catch((error) => { setError("Could not register user"); setLoading(false); + return; }); if (response && "error" in response) { diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 052f4dbd25..35327adc7b 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -4,7 +4,10 @@ import { createSessionAndUpdateCookie } from "@/lib/server/cookie"; import { addHumanUser } from "@/lib/zitadel"; import { create } from "@zitadel/client"; import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { + ChecksJson, + ChecksSchema, +} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; type RegisterUserCommand = { email: string; @@ -34,10 +37,18 @@ export async function registerUser(command: RegisterUserCommand) { return { error: "Could not create user" }; } - const checks = create(ChecksSchema, { + let checkPayload: any = { user: { search: { case: "userId", value: human.userId } }, - password: { password: command.password }, - }); + }; + + if (command.password) { + checkPayload = { + ...checkPayload, + password: { password: command.password }, + } as ChecksJson; + } + + const checks = create(ChecksSchema, checkPayload); return createSessionAndUpdateCookie( checks, From 35ec0bac30762760c20ad818b062f58970932fd5 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 24 Oct 2024 14:25:33 +0200 Subject: [PATCH 339/640] tests --- apps/login/cypress/integration/verify.cy.ts | 25 ++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index 886ea29882..af5a2633dc 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -2,15 +2,15 @@ import { stub } from "../support/mock"; describe("/verify", () => { it("shows password and passkey method after successful invite verification", () => { - stub("zitadel.user.v2.UserService", "VerifyEmail"); + stub("zitadel.user.v2.UserService", "VerifyInviteCode"); cy.visit("/verify?userId=123&code=abc&submit=true&invite=true"); cy.location("pathname", { timeout: 10_000 }).should( "eq", "/authenticator/set", ); }); - it("shows an error if validation failed", () => { - stub("zitadel.user.v2.UserService", "VerifyEmail", { + it("shows an error if invite code validation failed", () => { + stub("zitadel.user.v2.UserService", "VerifyInviteCode", { code: 3, error: "error validating code", }); @@ -19,4 +19,23 @@ describe("/verify", () => { cy.visit("/verify?userId=123&code=abc&submit=true&invite=true"); cy.contains("Could not verify user"); }); + + it("shows password and passkey method after successful invite verification", () => { + stub("zitadel.user.v2.UserService", "VerifyEmail"); + cy.visit("/verify?userId=123&code=abc&submit=true"); + cy.location("pathname", { timeout: 10_000 }).should( + "eq", + "/authenticator/set", + ); + }); + it("shows an error if invite code validation failed", () => { + stub("zitadel.user.v2.UserService", "VerifyEmail", { + code: 3, + error: "error validating code", + }); + // TODO: Avoid uncaught exception in application + cy.once("uncaught:exception", () => false); + cy.visit("/verify?userId=123&code=abc&submit=true"); + cy.contains("Could not verify user"); + }); }); From 3b6291bcd40bc6c41c2e29f37d9e2e6e04e8e472 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 24 Oct 2024 15:10:39 +0200 Subject: [PATCH 340/640] fix layout responsiveness --- apps/login/src/app/(login)/layout.tsx | 15 +++++------ apps/login/src/components/dynamic-theme.tsx | 30 ++++++++++----------- apps/login/src/components/theme-wrapper.tsx | 2 +- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/apps/login/src/app/(login)/layout.tsx b/apps/login/src/app/(login)/layout.tsx index a53b6b7c28..75820d7990 100644 --- a/apps/login/src/app/(login)/layout.tsx +++ b/apps/login/src/app/(login)/layout.tsx @@ -35,16 +35,13 @@ export default async function RootLayout({
    -
    - - -
    - -
    -
    - {children} +
    + {children} +
    + +
    diff --git a/apps/login/src/components/dynamic-theme.tsx b/apps/login/src/components/dynamic-theme.tsx index eb3944b12c..7d0fecb558 100644 --- a/apps/login/src/components/dynamic-theme.tsx +++ b/apps/login/src/components/dynamic-theme.tsx @@ -14,23 +14,21 @@ export function DynamicTheme({ }) { return ( -
    -
    -
    -
    - {branding && ( - - )} -
    - -
    {children}
    -
    +
    +
    +
    + {branding && ( + + )}
    + +
    {children}
    +
    diff --git a/apps/login/src/components/theme-wrapper.tsx b/apps/login/src/components/theme-wrapper.tsx index 1ece18880f..8c8ebc4630 100644 --- a/apps/login/src/components/theme-wrapper.tsx +++ b/apps/login/src/components/theme-wrapper.tsx @@ -14,7 +14,7 @@ export const ThemeWrapper = ({ children, branding }: Props) => { setTheme(document, branding); }, []); - const defaultClasses = "bg-background-light-600 dark:bg-background-dark-600"; + const defaultClasses = ""; return
    {children}
    ; }; From facd27ca5a8ef51cc0c9bda7a41ae29a288cc5d2 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 24 Oct 2024 15:28:09 +0200 Subject: [PATCH 341/640] tests --- apps/login/cypress/integration/register.cy.ts | 7 +++++++ apps/login/cypress/integration/verify.cy.ts | 11 ++++++----- apps/login/src/components/language-switcher.tsx | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/login/cypress/integration/register.cy.ts b/apps/login/cypress/integration/register.cy.ts index 02d0d45db4..f32b98564a 100644 --- a/apps/login/cypress/integration/register.cy.ts +++ b/apps/login/cypress/integration/register.cy.ts @@ -5,6 +5,13 @@ describe("register", () => { stub("zitadel.user.v2.UserService", "AddHumanUser", { data: { userId: "123", + email: { + email: "john@zitadel.com", + }, + profile: { + givenName: "John", + familyName: "Doe", + }, }, }); }); diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index af5a2633dc..a89a39672d 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -1,10 +1,10 @@ import { stub } from "../support/mock"; describe("/verify", () => { - it("shows password and passkey method after successful invite verification", () => { + it("shows authenticators after successful invite verification", () => { stub("zitadel.user.v2.UserService", "VerifyInviteCode"); cy.visit("/verify?userId=123&code=abc&submit=true&invite=true"); - cy.location("pathname", { timeout: 10_000 }).should( + cy.location("pathname", { timeout: 10000 }).should( "eq", "/authenticator/set", ); @@ -17,17 +17,18 @@ describe("/verify", () => { // TODO: Avoid uncaught exception in application cy.once("uncaught:exception", () => false); cy.visit("/verify?userId=123&code=abc&submit=true&invite=true"); - cy.contains("Could not verify user"); + cy.contains("Could not verify invite", { timeout: 10000 }); }); it("shows password and passkey method after successful invite verification", () => { stub("zitadel.user.v2.UserService", "VerifyEmail"); cy.visit("/verify?userId=123&code=abc&submit=true"); - cy.location("pathname", { timeout: 10_000 }).should( + cy.location("pathname", { timeout: 10000 }).should( "eq", "/authenticator/set", ); }); + it("shows an error if invite code validation failed", () => { stub("zitadel.user.v2.UserService", "VerifyEmail", { code: 3, @@ -36,6 +37,6 @@ describe("/verify", () => { // TODO: Avoid uncaught exception in application cy.once("uncaught:exception", () => false); cy.visit("/verify?userId=123&code=abc&submit=true"); - cy.contains("Could not verify user"); + cy.contains("Could not verify email", { timeout: 10000 }); }); }); diff --git a/apps/login/src/components/language-switcher.tsx b/apps/login/src/components/language-switcher.tsx index c7dcad7ec1..8d68248c4a 100644 --- a/apps/login/src/components/language-switcher.tsx +++ b/apps/login/src/components/language-switcher.tsx @@ -51,7 +51,7 @@ export function LanguageSwitcher() { anchor="bottom" transition className={clsx( - "w-[var(--button-width)] rounded-xl border border-black/5 dark:border-white/5 bg-black/5 dark:bg-white/5 p-1 [--anchor-gap:var(--spacing-1)] focus:outline-none", + "w-[var(--button-width)] rounded-xl border border-black/5 dark:border-white/5 bg-background-light-500 dark:bg-background-dark-500 p-1 [--anchor-gap:var(--spacing-1)] focus:outline-none", "transition duration-100 ease-in data-[leave]:data-[closed]:opacity-0", )} > From 1c65dbdb9a8231f6de45a722c16ae01b25916b69 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 25 Oct 2024 08:50:21 +0200 Subject: [PATCH 342/640] block authentictor setup if one method is set --- apps/login/locales/de.json | 3 +- apps/login/locales/en.json | 3 +- apps/login/locales/es.json | 3 +- apps/login/locales/it.json | 3 +- .../choose-authenticator-to-setup.tsx | 39 ++++++++++--------- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 0a15315a6f..4314e65119 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -166,7 +166,8 @@ "authenticator": { "title": "Authentifizierungsmethode auswählen", "description": "Wählen Sie die Methode, mit der Sie sich authentifizieren möchten.", - "noMethodsAvailable": "Keine Authentifizierungsmethoden verfügbar" + "noMethodsAvailable": "Keine Authentifizierungsmethoden verfügbar", + "allSetup": "Sie haben bereits einen Authentifikator eingerichtet!" }, "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.", diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index f14a983509..7b1411488f 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -166,7 +166,8 @@ "authenticator": { "title": "Choose authentication method", "description": "Select the method you would like to authenticate", - "noMethodsAvailable": "No authentication methods available" + "noMethodsAvailable": "No authentication methods available", + "allSetup": "You have already setup an authenticator!" }, "error": { "unknownContext": "Could not get the context of the user. Make sure to enter the username first or provide a loginName as searchParam.", diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index b1f50506fb..ee894d732b 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -166,7 +166,8 @@ "authenticator": { "title": "Seleccionar método de autenticación", "description": "Selecciona el método con el que deseas autenticarte", - "noMethodsAvailable": "No hay métodos de autenticación disponibles" + "noMethodsAvailable": "No hay métodos de autenticación disponibles", + "allSetup": "¡Ya has configurado un autenticador!" }, "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.", diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index 31f80a1f63..a81f8f7c16 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -166,7 +166,8 @@ "authenticator": { "title": "Seleziona metodo di autenticazione", "description": "Seleziona il metodo con cui desideri autenticarti", - "noMethodsAvailable": "Nessun metodo di autenticazione disponibile" + "noMethodsAvailable": "Nessun metodo di autenticazione disponibile", + "allSetup": "Hai già configurato un autenticatore!" }, "error": { "unknownContext": "Impossibile ottenere il contesto dell'utente. Assicurati di inserire prima il nome utente o di fornire un loginName come parametro di ricerca.", diff --git a/apps/login/src/components/choose-authenticator-to-setup.tsx b/apps/login/src/components/choose-authenticator-to-setup.tsx index 250ed1bd5f..fe1f66132c 100644 --- a/apps/login/src/components/choose-authenticator-to-setup.tsx +++ b/apps/login/src/components/choose-authenticator-to-setup.tsx @@ -1,4 +1,3 @@ -import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { LoginSettings, PasskeysType, @@ -11,33 +10,35 @@ import { PASSKEYS, PASSWORD } from "./auth-methods"; type Props = { authMethods: AuthenticationMethodType[]; params: URLSearchParams; - sessionFactors?: Factors; loginSettings: LoginSettings; }; export function ChooseAuthenticatorToSetup({ authMethods, params, - sessionFactors, loginSettings, }: Props) { const t = useTranslations("authenticator"); - return ( - <> - {loginSettings.passkeysType === PasskeysType.ALLOWED && - !loginSettings.allowUsernamePassword && ( - {t("noMethodsAvailable")} - )} + if (authMethods.length !== 0) { + return {t("allSetup")}; + } else { + return ( + <> + {loginSettings.passkeysType === PasskeysType.ALLOWED && + !loginSettings.allowUsernamePassword && ( + {t("noMethodsAvailable")} + )} -
    - {!authMethods.includes(AuthenticationMethodType.PASSWORD) && - loginSettings.allowUsernamePassword && - PASSWORD(false, "/password/set?" + params)} - {!authMethods.includes(AuthenticationMethodType.PASSKEY) && - loginSettings.passkeysType === PasskeysType.ALLOWED && - PASSKEYS(false, "/passkey/set?" + params)} -
    - - ); +
    + {!authMethods.includes(AuthenticationMethodType.PASSWORD) && + loginSettings.allowUsernamePassword && + PASSWORD(false, "/password/set?" + params)} + {!authMethods.includes(AuthenticationMethodType.PASSKEY) && + loginSettings.passkeysType === PasskeysType.ALLOWED && + PASSKEYS(false, "/passkey/set?" + params)} +
    + + ); + } } From 5fd72f53884f3ab45d5350ee722c81c730dba1a3 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 25 Oct 2024 10:07:48 +0200 Subject: [PATCH 343/640] show authenticator button if no method set yet --- apps/login/locales/de.json | 1 + apps/login/locales/en.json | 1 + apps/login/locales/es.json | 1 + apps/login/locales/it.json | 1 + .../app/(login)/authenticator/set/page.tsx | 1 - apps/login/src/app/(login)/verify/page.tsx | 38 ++++++++++++++++++- 6 files changed, 40 insertions(+), 3 deletions(-) diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 4314e65119..48e7c7f643 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -156,6 +156,7 @@ "verify": { "userIdMissing": "Keine Benutzer-ID angegeben!", "success": "Erfolgreich verifiziert", + "setupAuthenticator": "Authentifikator einrichten", "verify": { "title": "Benutzer verifizieren", "description": "Geben Sie den Code ein, der in der Bestätigungs-E-Mail angegeben ist.", diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 7b1411488f..e709b553be 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -156,6 +156,7 @@ "verify": { "userIdMissing": "No userId provided!", "success": "The user has been verified successfully.", + "setupAuthenticator": "Setup authenticator", "verify": { "title": "Verify user", "description": "Enter the Code provided in the verification email.", diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index ee894d732b..e9a8e1b5ab 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -156,6 +156,7 @@ "verify": { "userIdMissing": "¡No se proporcionó userId!", "success": "¡Verificación exitosa!", + "setupAuthenticator": "Configurar autenticador", "verify": { "title": "Verificar usuario", "description": "Introduce el código proporcionado en el correo electrónico de verificación.", diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index a81f8f7c16..b5ff7a48e5 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -156,6 +156,7 @@ "verify": { "userIdMissing": "Nessun userId fornito!", "success": "Verifica effettuata con successo!", + "setupAuthenticator": "Configura autenticatore", "verify": { "title": "Verifica utente", "description": "Inserisci il codice fornito nell'email di verifica.", diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index db1686dd30..78a06ab329 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -120,7 +120,6 @@ export default async function Page({ {loginSettings && sessionWithData && ( diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 368e9c1c5c..030e7af608 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -1,10 +1,18 @@ import { Alert, AlertType } from "@/components/alert"; +import { BackButton } from "@/components/back-button"; +import { Button, ButtonVariants } from "@/components/button"; import { DynamicTheme } from "@/components/dynamic-theme"; import { UserAvatar } from "@/components/user-avatar"; import { VerifyForm } from "@/components/verify-form"; -import { getBrandingSettings, getUserByID } from "@/lib/zitadel"; +import { + getBrandingSettings, + getUserByID, + listAuthenticationMethodTypes, +} from "@/lib/zitadel"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; +import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import Link from "next/link"; export default async function Page({ searchParams }: { searchParams: any }) { const locale = getLocale(); @@ -28,6 +36,14 @@ export default async function Page({ searchParams }: { searchParams: any }) { } } + let authMethods: AuthenticationMethodType[] | null = null; + if (human?.email?.isVerified) { + const authMethodsResponse = await listAuthenticationMethodTypes(userId); + if (authMethodsResponse.authMethodTypes) { + authMethods = authMethodsResponse.authMethodTypes; + } + } + const params = new URLSearchParams({ userId: userId, initial: "true", // defines that a code is not required and is therefore not shown in the UI @@ -70,7 +86,25 @@ export default async function Page({ searchParams }: { searchParams: any }) { /> )} {human?.email?.isVerified ? ( - {t("success")} + <> + {t("success")} + +
    + + + {authMethods?.length !== 0 && ( + + + + )} +
    + ) : ( // check if auth methods are set Date: Fri, 25 Oct 2024 10:16:31 +0200 Subject: [PATCH 344/640] cleanup logs --- apps/login/src/components/verify-form.tsx | 1 - apps/login/src/lib/server/email.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/apps/login/src/components/verify-form.tsx b/apps/login/src/components/verify-form.tsx index d974c1a949..4b0d1dba71 100644 --- a/apps/login/src/components/verify-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -68,7 +68,6 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { userId, isInvite: isInvite, }).catch(() => { - console.log(error); setError("Could not verify email"); setLoading(false); return; diff --git a/apps/login/src/lib/server/email.ts b/apps/login/src/lib/server/email.ts index ff08d3fa0a..5827c20b35 100644 --- a/apps/login/src/lib/server/email.ts +++ b/apps/login/src/lib/server/email.ts @@ -61,7 +61,6 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { if (!authMethodResponse || !authMethodResponse.authMethodTypes) { return { error: "Could not load possible authenticators" }; } - console.log("xs"); // if no authmethods are found on the user, redirect to set one up if ( authMethodResponse && @@ -75,8 +74,6 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { if (session.factors?.user?.loginName) { params.set("loginName", session.factors?.user?.loginName); } - - console.log("/authenticator/set?" + params); return redirect("/authenticator/set?" + params); } From e25c05424c667a5e1a7ed82fd14df8ba292f3bdf Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 25 Oct 2024 10:16:49 +0200 Subject: [PATCH 345/640] cleanup logs --- apps/login/src/components/set-password-form.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 0e07767e1a..1fb3acda90 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -108,7 +108,6 @@ export function SetPasswordForm({ }), authRequestId, }).catch((error) => { - console.error("verifyerror", error); setLoading(false); setError("Could not verify password"); return; From 4bd0f96a3be444b3d511f9f02a377b3529b3e995 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 25 Oct 2024 10:50:42 +0200 Subject: [PATCH 346/640] show auth setup for 0 auth methods --- apps/login/src/app/(login)/verify/page.tsx | 3 ++- apps/login/src/components/verify-form.tsx | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 030e7af608..5dda12040a 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -39,6 +39,7 @@ export default async function Page({ searchParams }: { searchParams: any }) { let authMethods: AuthenticationMethodType[] | null = null; if (human?.email?.isVerified) { const authMethodsResponse = await listAuthenticationMethodTypes(userId); + console.log(authMethodsResponse); if (authMethodsResponse.authMethodTypes) { authMethods = authMethodsResponse.authMethodTypes; } @@ -92,7 +93,7 @@ export default async function Page({ searchParams }: { searchParams: any }) {
    - {authMethods?.length !== 0 && ( + {authMethods?.length === 0 && ( - - )} -
    - + {human?.email?.isVerified ? ( + ) : ( // check if auth methods are set { - createNewSession({ + createNewSessionForIdp({ userId, idpIntent: { idpIntentId, diff --git a/apps/login/src/components/theme-wrapper.tsx b/apps/login/src/components/theme-wrapper.tsx index 8c8ebc4630..314c3a2ef0 100644 --- a/apps/login/src/components/theme-wrapper.tsx +++ b/apps/login/src/components/theme-wrapper.tsx @@ -12,9 +12,7 @@ type Props = { export const ThemeWrapper = ({ children, branding }: Props) => { useEffect(() => { setTheme(document, branding); - }, []); + }, [branding]); - const defaultClasses = ""; - - return
    {children}
    ; + return
    {children}
    ; }; diff --git a/apps/login/src/components/verify-form.tsx b/apps/login/src/components/verify-form.tsx index 801327347a..f0f60005b4 100644 --- a/apps/login/src/components/verify-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -4,7 +4,7 @@ import { Alert } from "@/components/alert"; import { resendVerification, sendVerification } from "@/lib/server/email"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; @@ -37,12 +37,6 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { const router = useRouter(); - useEffect(() => { - if (code) { - submitCodeAndContinue({ code }); - } - }, []); - async function resendCode() { setError(""); setLoading(true); @@ -60,29 +54,32 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { return response; } - async function submitCodeAndContinue(value: Inputs): Promise { - setLoading(true); + const fcn = useCallback( + async function submitCodeAndContinue( + value: Inputs, + ): Promise { + setLoading(true); + + await sendVerification({ + code: value.code, + userId, + isInvite: isInvite, + }).catch((error) => { + setError("Could not verify user"); + setLoading(false); + return; + }); - const verifyResponse = await sendVerification({ - code: value.code, - userId, - isInvite: isInvite, - }).catch(() => { - setError("Could not verify user"); setLoading(false); - return; - }); + }, + [isInvite, userId], + ); - setLoading(false); - - if (!verifyResponse) { - setError("Could not verify user"); - return; - } else { - setError(""); - return router.push("/authenticator/set?" + params); + useEffect(() => { + if (code) { + fcn({ code }); } - } + }, [code, fcn]); return ( <> @@ -116,7 +113,7 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { className="self-end" variant={ButtonVariants.Primary} disabled={loading || !formState.isValid} - onClick={handleSubmit(submitCodeAndContinue)} + onClick={handleSubmit(fcn)} > {loading && } {t("verify.submit")} diff --git a/apps/login/src/components/verify-redirect-button.tsx b/apps/login/src/components/verify-redirect-button.tsx new file mode 100644 index 0000000000..0c09c835d8 --- /dev/null +++ b/apps/login/src/components/verify-redirect-button.tsx @@ -0,0 +1,68 @@ +"use client"; + +import { sendVerificationRedirectWithoutCheck } from "@/lib/server/email"; +import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { useTranslations } from "next-intl"; +import { useState } from "react"; +import { Alert, AlertType } from "./alert"; +import { BackButton } from "./back-button"; +import { Button, ButtonVariants } from "./button"; +import { Spinner } from "./spinner"; + +export function VerifyRedirectButton({ + userId, + authRequestId, + authMethods, +}: { + userId: string; + authRequestId: string; + authMethods: AuthenticationMethodType[] | null; +}) { + const t = useTranslations("verify"); + const [error, setError] = useState(""); + + const [loading, setLoading] = useState(false); + + async function submitAndContinue(): Promise { + setLoading(true); + + await sendVerificationRedirectWithoutCheck({ + userId, + authRequestId, + }).catch((error) => { + setError("Could not verify user"); + setLoading(false); + return; + }); + + setLoading(false); + } + + return ( + <> + {t("success")} + + {error && ( +
    + {error} +
    + )} + +
    + + + {authMethods?.length === 0 && ( + + )} +
    + + ); +} diff --git a/apps/login/src/lib/server/email.ts b/apps/login/src/lib/server/email.ts index 4aa0c58b25..f1925317a4 100644 --- a/apps/login/src/lib/server/email.ts +++ b/apps/login/src/lib/server/email.ts @@ -76,12 +76,6 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { } return redirect("/authenticator/set?" + params); } - - // return { - // authMethodTypes: authMethodResponse.authMethodTypes, - // sessionId: session.id, - // factors: session.factors, - // }; } type resendVerifyEmailCommand = { @@ -94,3 +88,52 @@ export async function resendVerification(command: resendVerifyEmailCommand) { ? resendEmailCode(command.userId) : resendInviteCode(command.userId); } + +export async function sendVerificationRedirectWithoutCheck(command: { + userId: string; + authRequestId?: string; +}) { + const userResponse = await getUserByID(command.userId); + + if (!userResponse || !userResponse.user) { + return { error: "Could not load user" }; + } + + const checks = create(ChecksSchema, { + user: { + search: { + case: "loginName", + value: userResponse.user.preferredLoginName, + }, + }, + }); + + const session = await createSessionAndUpdateCookie( + checks, + undefined, + command.authRequestId, + ); + + const authMethodResponse = await listAuthenticationMethodTypes( + command.userId, + ); + + if (!authMethodResponse || !authMethodResponse.authMethodTypes) { + return { error: "Could not load possible authenticators" }; + } + // if no authmethods are found on the user, redirect to set one up + if ( + authMethodResponse && + authMethodResponse.authMethodTypes && + authMethodResponse.authMethodTypes.length == 0 + ) { + const params = new URLSearchParams({ + sessionId: session.id, + }); + + if (session.factors?.user?.loginName) { + params.set("loginName", session.factors?.user?.loginName); + } + return redirect("/authenticator/set?" + params); + } +} diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 3bd9762916..7700b650b8 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -1,17 +1,12 @@ "use server"; import { - createSessionAndUpdateCookie, createSessionForIdpAndUpdateCookie, setSessionAndUpdateCookie, } from "@/lib/server/cookie"; import { deleteSession, listAuthenticationMethodTypes } from "@/lib/zitadel"; -import { create } from "@zitadel/client"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; -import { - Checks, - ChecksSchema, -} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { headers } from "next/headers"; import { getMostRecentSessionCookie, @@ -31,26 +26,13 @@ type CreateNewSessionCommand = { authRequestId?: string; }; -export async function createNewSession(options: CreateNewSessionCommand) { - const { userId, idpIntent, loginName, password, authRequestId } = options; +export async function createNewSessionForIdp(options: CreateNewSessionCommand) { + const { userId, idpIntent, authRequestId } = options; - if (userId && idpIntent) { - return createSessionForIdpAndUpdateCookie(userId, idpIntent, authRequestId); - } else if (loginName) { - const checks = create( - ChecksSchema, - password - ? { - user: { search: { case: "loginName", value: loginName } }, - password: { password }, - } - : { user: { search: { case: "loginName", value: loginName } } }, - ); - - return createSessionAndUpdateCookie(checks, undefined, authRequestId); - } else { + if (!userId || !idpIntent) { throw new Error("No userId or loginName provided"); } + return createSessionForIdpAndUpdateCookie(userId, idpIntent, authRequestId); } export type UpdateSessionCommand = { From fe3ba12ef595e490a6bbed9fdf6bf2df4b64dd6a Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 25 Oct 2024 16:03:50 +0200 Subject: [PATCH 351/640] stub for register --- apps/login/cypress/integration/register.cy.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/login/cypress/integration/register.cy.ts b/apps/login/cypress/integration/register.cy.ts index f32b98564a..292d35d7b9 100644 --- a/apps/login/cypress/integration/register.cy.ts +++ b/apps/login/cypress/integration/register.cy.ts @@ -2,6 +2,11 @@ import { stub } from "../support/mock"; describe("register", () => { beforeEach(() => { + stub("zitadel.user.v2.OrganizationService", "ListOrganizations", { + data: { + result: [{ id: "123" }], + }, + }); stub("zitadel.user.v2.UserService", "AddHumanUser", { data: { userId: "123", From 870b2e9130b6178103cfb09a7701fc8fea2d6621 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 25 Oct 2024 16:04:26 +0200 Subject: [PATCH 352/640] stub ns --- apps/login/cypress/integration/register.cy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/cypress/integration/register.cy.ts b/apps/login/cypress/integration/register.cy.ts index 292d35d7b9..6b0fca1985 100644 --- a/apps/login/cypress/integration/register.cy.ts +++ b/apps/login/cypress/integration/register.cy.ts @@ -2,7 +2,7 @@ import { stub } from "../support/mock"; describe("register", () => { beforeEach(() => { - stub("zitadel.user.v2.OrganizationService", "ListOrganizations", { + stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { data: { result: [{ id: "123" }], }, From c9c8af2148f1f96250e59c5fd30f7de7735061e4 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 28 Oct 2024 14:52:26 +0100 Subject: [PATCH 353/640] tests --- apps/login/cypress/integration/register.cy.ts | 5 +- apps/login/cypress/integration/verify.cy.ts | 86 ++++++++++++------- apps/login/src/app/(login)/register/page.tsx | 8 +- .../register-form-without-password.tsx | 5 +- 4 files changed, 68 insertions(+), 36 deletions(-) diff --git a/apps/login/cypress/integration/register.cy.ts b/apps/login/cypress/integration/register.cy.ts index 6b0fca1985..bf23fc4037 100644 --- a/apps/login/cypress/integration/register.cy.ts +++ b/apps/login/cypress/integration/register.cy.ts @@ -4,7 +4,10 @@ describe("register", () => { beforeEach(() => { stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { data: { - result: [{ id: "123" }], + details: { + totalResult: 1, + }, + result: [{ id: "256088834543534543" }], }, }); stub("zitadel.user.v2.UserService", "AddHumanUser", { diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index a89a39672d..4204f9a8a2 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -1,42 +1,62 @@ import { stub } from "../support/mock"; -describe("/verify", () => { - it("shows authenticators after successful invite verification", () => { - stub("zitadel.user.v2.UserService", "VerifyInviteCode"); - cy.visit("/verify?userId=123&code=abc&submit=true&invite=true"); - cy.location("pathname", { timeout: 10000 }).should( - "eq", - "/authenticator/set", - ); - }); - it("shows an error if invite code validation failed", () => { - stub("zitadel.user.v2.UserService", "VerifyInviteCode", { - code: 3, - error: "error validating code", - }); - // TODO: Avoid uncaught exception in application - cy.once("uncaught:exception", () => false); - cy.visit("/verify?userId=123&code=abc&submit=true&invite=true"); - cy.contains("Could not verify invite", { timeout: 10000 }); +beforeEach(() => { + stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { + data: { + details: { + totalResult: 1, + }, + result: [{ id: "256088834543534543" }], + }, }); - it("shows password and passkey method after successful invite verification", () => { - stub("zitadel.user.v2.UserService", "VerifyEmail"); - cy.visit("/verify?userId=123&code=abc&submit=true"); - cy.location("pathname", { timeout: 10000 }).should( - "eq", - "/authenticator/set", - ); + stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { + data: { + authMethodTypes: [], + }, }); - it("shows an error if invite code validation failed", () => { - stub("zitadel.user.v2.UserService", "VerifyEmail", { - code: 3, - error: "error validating code", + describe("verify invite", () => { + it.only("shows authenticators after successful invite verification", () => { + stub("zitadel.user.v2.UserService", "VerifyInviteCode"); + cy.visit("/verify?userId=123&code=abc&invite=true"); + cy.location("pathname", { timeout: 10_000 }).should( + "eq", + "/authenticator/set", + ); + }); + + it("shows an error if invite code validation failed", () => { + stub("zitadel.user.v2.UserService", "VerifyInviteCode", { + code: 3, + error: "error validating code", + }); + // TODO: Avoid uncaught exception in application + cy.once("uncaught:exception", () => false); + cy.visit("/verify?userId=123&code=abc&invite=true"); + cy.contains("Could not verify invite", { timeout: 10_000 }); + }); + }); + + describe("verify email", () => { + it("shows password and passkey method after successful invite verification", () => { + stub("zitadel.user.v2.UserService", "VerifyEmail"); + cy.visit("/verify?userId=123&code=abc"); + cy.location("pathname", { timeout: 10_000 }).should( + "eq", + "/authenticator/set", + ); + }); + + it("shows an error if invite code validation failed", () => { + stub("zitadel.user.v2.UserService", "VerifyEmail", { + code: 3, + error: "error validating code", + }); + // TODO: Avoid uncaught exception in application + cy.once("uncaught:exception", () => false); + cy.visit("/verify?userId=123&code=abc&submit=true"); + cy.contains("Could not verify email", { timeout: 10_000 }); }); - // TODO: Avoid uncaught exception in application - cy.once("uncaught:exception", () => false); - cy.visit("/verify?userId=123&code=abc&submit=true"); - cy.contains("Could not verify email", { timeout: 10000 }); }); }); diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 6ade9103e1..d4301d9d2a 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -7,6 +7,7 @@ import { getLegalAndSupportSettings, getPasswordComplexitySettings, } from "@/lib/zitadel"; +import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ @@ -21,11 +22,16 @@ export default async function Page({ searchParams; if (!organization) { - const org = await getDefaultOrg(); + const org: Organization | void = await getDefaultOrg().catch((error) => { + console.log("err"); + }); if (!org) { + console.log("no default organization"); throw new Error("No default organization found"); } + console.log("org", org); + organization = org.id; } diff --git a/apps/login/src/components/register-form-without-password.tsx b/apps/login/src/components/register-form-without-password.tsx index c0b9ba25e3..4b8d4d089a 100644 --- a/apps/login/src/components/register-form-without-password.tsx +++ b/apps/login/src/components/register-form-without-password.tsx @@ -185,7 +185,10 @@ export function RegisterFormWithoutPassword({ variant={ButtonVariants.Primary} disabled={loading || !formState.isValid || !tosAndPolicyAccepted} onClick={handleSubmit((values) => - submitAndContinue(values, selected === methods[0] ? false : true), + submitAndContinue( + values, + selected.name === methods[0].name ? false : true, + ), )} > {loading && } From 34e873809de38c06e561dd3bea410842131c32d8 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 28 Oct 2024 15:06:45 +0100 Subject: [PATCH 354/640] logs --- apps/login/src/app/(login)/register/page.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index d4301d9d2a..cbea16432d 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -23,16 +23,13 @@ export default async function Page({ if (!organization) { const org: Organization | void = await getDefaultOrg().catch((error) => { - console.log("err"); + console.log("getDefaultOrgError", error); }); if (!org) { - console.log("no default organization"); - throw new Error("No default organization found"); + console.warn("No default organization found"); + } else { + organization = org.id; } - - console.log("org", org); - - organization = org.id; } const setPassword = !!(firstname && lastname && email); From b8039be9587b1e450591245c31b7f25cf15abe58 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 28 Oct 2024 15:51:54 +0100 Subject: [PATCH 355/640] response mapping --- apps/login/src/app/(login)/register/page.tsx | 5 +++-- apps/login/src/lib/zitadel.ts | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index cbea16432d..931cad0e08 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -22,8 +22,9 @@ export default async function Page({ searchParams; if (!organization) { - const org: Organization | void = await getDefaultOrg().catch((error) => { - console.log("getDefaultOrgError", error); + const org: Organization | null = await getDefaultOrg().catch((error) => { + console.warn(error); + return null; }); if (!org) { console.warn("No default organization found"); diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 864d19ae25..3eee677f1d 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -21,6 +21,7 @@ import { import { create, fromJson, toJson } from "@zitadel/client"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; +import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { BrandingSettingsSchema } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; import { LegalAndSupportSettingsSchema } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; import { @@ -417,7 +418,7 @@ export async function listUsers({ return userService.listUsers({ queries: queries }); } -export async function getDefaultOrg() { +export async function getDefaultOrg(): Promise { return orgService .listOrganizations( { @@ -432,7 +433,7 @@ export async function getDefaultOrg() { }, {}, ) - .then((resp) => resp.result[0]); + .then((resp) => (resp?.result && resp.result[0] ? resp.result[0] : null)); } export async function getOrgsByDomain(domain: string) { From 357d462b16c7054b248f1c6b6480c2aae76b4f49 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 28 Oct 2024 16:02:58 +0100 Subject: [PATCH 356/640] redirect from server --- .../register-form-without-password.tsx | 20 +++---------------- apps/login/src/lib/server/register.ts | 19 ++++++++++++++++-- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/apps/login/src/components/register-form-without-password.tsx b/apps/login/src/components/register-form-without-password.tsx index 4b8d4d089a..b81ef3eef0 100644 --- a/apps/login/src/components/register-form-without-password.tsx +++ b/apps/login/src/components/register-form-without-password.tsx @@ -1,6 +1,6 @@ "use client"; -import { registerUser, RegisterUserResponse } from "@/lib/server/register"; +import { registerUser } from "@/lib/server/register"; import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; @@ -66,6 +66,7 @@ export function RegisterFormWithoutPassword({ firstName: values.firstname, lastName: values.lastname, organization: organization, + authRequestId: authRequestId, }).catch((error) => { setError("Could not register user"); setLoading(false); @@ -98,22 +99,7 @@ export function RegisterFormWithoutPassword({ if (withPassword) { return router.push(`/register?` + new URLSearchParams(registerParams)); } else { - const session = (await submitAndRegister(value)) as RegisterUserResponse; - - const params = new URLSearchParams({}); - if (session?.factors?.user?.loginName) { - params.set("loginName", session.factors?.user?.loginName); - } - - if (organization) { - params.set("organization", organization); - } - - if (authRequestId) { - params.set("authRequestId", authRequestId); - } - - return router.push(`/passkey/set?` + new URLSearchParams(params)); + return submitAndRegister(value); } } diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 35327adc7b..2825bb97a6 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -8,6 +8,7 @@ import { ChecksJson, ChecksSchema, } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { redirect } from "next/navigation"; type RegisterUserCommand = { email: string; @@ -23,7 +24,6 @@ export type RegisterUserResponse = { sessionId: string; factors: Factors | undefined; }; - export async function registerUser(command: RegisterUserCommand) { const human = await addHumanUser({ email: command.email, @@ -50,7 +50,7 @@ export async function registerUser(command: RegisterUserCommand) { const checks = create(ChecksSchema, checkPayload); - return createSessionAndUpdateCookie( + const session = await createSessionAndUpdateCookie( checks, undefined, command.authRequestId, @@ -61,4 +61,19 @@ export async function registerUser(command: RegisterUserCommand) { factors: session.factors, }; }); + + if (!session || !session.factors?.user) { + return { error: "Could not create session" }; + } + + const params = new URLSearchParams({ + loginName: session.factors.user.loginName, + organization: session.factors.user.organizationId, + }); + + if (command.authRequestId) { + params.append("authRequestId", command.authRequestId); + } + + return redirect("/passkey/set?" + params); } From 0a9d11fe2617e5e9204f4672c4a8a07f8b5868f0 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 28 Oct 2024 16:03:30 +0100 Subject: [PATCH 357/640] loading state --- apps/login/src/components/register-form-without-password.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/login/src/components/register-form-without-password.tsx b/apps/login/src/components/register-form-without-password.tsx index b81ef3eef0..288ef7133a 100644 --- a/apps/login/src/components/register-form-without-password.tsx +++ b/apps/login/src/components/register-form-without-password.tsx @@ -75,6 +75,8 @@ export function RegisterFormWithoutPassword({ if (response && "error" in response) { setError(response.error); + setLoading(false); + return; } setLoading(false); From a088e793d1d1aa59d5a9b3d7c37f7d0483dc943f Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 28 Oct 2024 16:10:45 +0100 Subject: [PATCH 358/640] cleanup password register --- .../components/set-register-password-form.tsx | 44 ++----------------- 1 file changed, 3 insertions(+), 41 deletions(-) diff --git a/apps/login/src/components/set-register-password-form.tsx b/apps/login/src/components/set-register-password-form.tsx index b8f2eaf3c5..b46711a1b7 100644 --- a/apps/login/src/components/set-register-password-form.tsx +++ b/apps/login/src/components/set-register-password-form.tsx @@ -6,10 +6,9 @@ import { symbolValidator, upperCaseValidator, } from "@/helpers/validators"; -import { registerUser, RegisterUserResponse } from "@/lib/server/register"; +import { registerUser } from "@/lib/server/register"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { useTranslations } from "next-intl"; -import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; import { Alert } from "./alert"; @@ -57,8 +56,6 @@ export function SetRegisterPasswordForm({ const [loading, setLoading] = useState(false); const [error, setError] = useState(""); - const router = useRouter(); - async function submitRegister(values: Inputs) { setLoading(true); const response = await registerUser({ @@ -72,47 +69,12 @@ export function SetRegisterPasswordForm({ setError("Could not register user"); }); - if (response && "error" in response) { - setError(response.error); - } - setLoading(false); - if (!response) { - setError("Could not register user"); + if (response && "error" in response) { + setError(response.error); return; } - - const userResponse = response as RegisterUserResponse; - - const params = new URLSearchParams({ userId: userResponse.userId }); - - if (userResponse.factors?.user?.loginName) { - params.append("loginName", userResponse.factors.user.loginName); - } - if (organization) { - params.append("organization", organization); - } - if (userResponse && userResponse.sessionId) { - params.append("sessionId", userResponse.sessionId); - } - - // skip verification for now as it is an app based flow - // return router.push(`/verify?` + params); - - // check for mfa force to continue with mfa setup - - if (authRequestId && userResponse.sessionId) { - if (authRequestId) { - params.append("authRequest", authRequestId); - } - return router.push(`/login?` + params); - } else { - if (authRequestId) { - params.append("authRequestId", authRequestId); - } - return router.push(`/signedin?` + params); - } } const { errors } = formState; From 35f1e0b46050e57edcbbc293460c84f0ae263fee Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 28 Oct 2024 16:15:33 +0100 Subject: [PATCH 359/640] redirect from server --- apps/login/src/lib/server/register.ts | 32 ++++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 2825bb97a6..b132e5acea 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -66,14 +66,30 @@ export async function registerUser(command: RegisterUserCommand) { return { error: "Could not create session" }; } - const params = new URLSearchParams({ - loginName: session.factors.user.loginName, - organization: session.factors.user.organizationId, - }); + if (!command.password) { + const params = new URLSearchParams({ + loginName: session.factors.user.loginName, + organization: session.factors.user.organizationId, + }); - if (command.authRequestId) { - params.append("authRequestId", command.authRequestId); + if (command.authRequestId) { + params.append("authRequestId", command.authRequestId); + } + + return redirect("/passkey/set?" + params); + } else { + const params = new URLSearchParams({ + loginName: session.factors.user.loginName, + organization: session.factors.user.organizationId, + }); + + if (command.authRequestId && session.userId) { + params.append("authRequest", command.authRequestId); + params.append("userId", session.userId); + + return redirect("/login?" + params); + } else { + return redirect("/signedin?" + params); + } } - - return redirect("/passkey/set?" + params); } From 3566ae91e1b345ea2ee184166242f8db9e81ce44 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 28 Oct 2024 16:16:31 +0100 Subject: [PATCH 360/640] sessionid --- apps/login/src/lib/server/register.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index b132e5acea..a3ad8a0927 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -85,7 +85,7 @@ export async function registerUser(command: RegisterUserCommand) { if (command.authRequestId && session.userId) { params.append("authRequest", command.authRequestId); - params.append("userId", session.userId); + params.append("sessionId", session.sessionId); return redirect("/login?" + params); } else { From c8fd63a169e44b1a086ef212282580d790308219 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 28 Oct 2024 16:48:57 +0100 Subject: [PATCH 361/640] hostname for localhost --- apps/login/src/lib/server/passkeys.ts | 12 +++++++++--- apps/login/src/lib/server/session.ts | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 5ad170f8d3..366c54295a 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -37,12 +37,18 @@ export async function registerPasskeyLink( sessionToken: sessionCookie.token, }); - const domain = headers().get("host"); + const host = headers().get("host"); - if (!domain) { + if (!host) { throw new Error("Could not get domain"); } + const [hostname, port] = host.split(":"); + + if (!hostname) { + throw new Error("Could not get hostname"); + } + const userId = session?.session?.factors?.user?.id; if (!userId) { @@ -60,7 +66,7 @@ export async function registerPasskeyLink( throw new Error("Missing code in response"); } - return registerPasskey(userId, registerLink.code, domain); + return registerPasskey(userId, registerLink.code, hostname); } export async function verifyPasskey(command: VerifyPasskeyCommand) { diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 7700b650b8..2cd618ffb3 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -75,7 +75,8 @@ export async function updateSession(options: UpdateSessionCommand) { challenges.webAuthN && !challenges.webAuthN.domain ) { - challenges.webAuthN.domain = host; + const [hostname, port] = host.split(":"); + challenges.webAuthN.domain = hostname; } const recent = await sessionPromise; From c6c15fe935f5293ca73f36cafa5ccf90cb7afe57 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 28 Oct 2024 16:52:30 +0100 Subject: [PATCH 362/640] mocked services --- apps/login/cypress/integration/verify.cy.ts | 117 +++++++++++--------- apps/login/mock/mocked-services.cfg | 3 +- 2 files changed, 69 insertions(+), 51 deletions(-) diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index 4204f9a8a2..6a43f0af0b 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -1,62 +1,79 @@ import { stub } from "../support/mock"; -beforeEach(() => { - stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { - data: { - details: { - totalResult: 1, +describe("verify invite", () => { + beforeEach(() => { + stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { + data: { + details: { + totalResult: 1, + }, + result: [{ id: "256088834543534543" }], }, - result: [{ id: "256088834543534543" }], - }, - }); - - stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { - data: { - authMethodTypes: [], - }, - }); - - describe("verify invite", () => { - it.only("shows authenticators after successful invite verification", () => { - stub("zitadel.user.v2.UserService", "VerifyInviteCode"); - cy.visit("/verify?userId=123&code=abc&invite=true"); - cy.location("pathname", { timeout: 10_000 }).should( - "eq", - "/authenticator/set", - ); }); - it("shows an error if invite code validation failed", () => { - stub("zitadel.user.v2.UserService", "VerifyInviteCode", { - code: 3, - error: "error validating code", - }); - // TODO: Avoid uncaught exception in application - cy.once("uncaught:exception", () => false); - cy.visit("/verify?userId=123&code=abc&invite=true"); - cy.contains("Could not verify invite", { timeout: 10_000 }); + stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { + data: { + authMethodTypes: [], + }, }); }); - describe("verify email", () => { - it("shows password and passkey method after successful invite verification", () => { - stub("zitadel.user.v2.UserService", "VerifyEmail"); - cy.visit("/verify?userId=123&code=abc"); - cy.location("pathname", { timeout: 10_000 }).should( - "eq", - "/authenticator/set", - ); - }); + it.only("shows authenticators after successful invite verification", () => { + stub("zitadel.user.v2.UserService", "VerifyInviteCode"); + cy.visit("/verify?userId=123&code=abc&invite=true"); + cy.location("pathname", { timeout: 10_000 }).should( + "eq", + "/authenticator/set", + ); + }); - it("shows an error if invite code validation failed", () => { - stub("zitadel.user.v2.UserService", "VerifyEmail", { - code: 3, - error: "error validating code", - }); - // TODO: Avoid uncaught exception in application - cy.once("uncaught:exception", () => false); - cy.visit("/verify?userId=123&code=abc&submit=true"); - cy.contains("Could not verify email", { timeout: 10_000 }); + it("shows an error if invite code validation failed", () => { + stub("zitadel.user.v2.UserService", "VerifyInviteCode", { + code: 3, + error: "error validating code", }); + // TODO: Avoid uncaught exception in application + cy.once("uncaught:exception", () => false); + cy.visit("/verify?userId=123&code=abc&invite=true"); + cy.contains("Could not verify invite", { timeout: 10_000 }); + }); +}); + +describe("verify email", () => { + beforeEach(() => { + stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { + data: { + details: { + totalResult: 1, + }, + result: [{ id: "256088834543534543" }], + }, + }); + + stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { + data: { + authMethodTypes: [], + }, + }); + }); + + it("shows password and passkey method after successful invite verification", () => { + stub("zitadel.user.v2.UserService", "VerifyEmail"); + cy.visit("/verify?userId=123&code=abc"); + cy.location("pathname", { timeout: 10_000 }).should( + "eq", + "/authenticator/set", + ); + }); + + it("shows an error if invite code validation failed", () => { + stub("zitadel.user.v2.UserService", "VerifyEmail", { + code: 3, + error: "error validating code", + }); + // TODO: Avoid uncaught exception in application + cy.once("uncaught:exception", () => false); + cy.visit("/verify?userId=123&code=abc&submit=true"); + cy.contains("Could not verify email", { timeout: 10_000 }); }); }); diff --git a/apps/login/mock/mocked-services.cfg b/apps/login/mock/mocked-services.cfg index 72e4d98748..d7a29492f0 100644 --- a/apps/login/mock/mocked-services.cfg +++ b/apps/login/mock/mocked-services.cfg @@ -1,6 +1,7 @@ zitadel/user/v2/user_service.proto +zitadel/org/v2/org_service.proto zitadel/session/v2/session_service.proto zitadel/settings/v2/settings_service.proto zitadel/management.proto zitadel/auth.proto -zitadel/admin.proto \ No newline at end of file +zitadel/admin.proto From 9af39ac1bc2e44754963edfa6cb151fa167100ab Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Mon, 28 Oct 2024 19:44:50 +0100 Subject: [PATCH 363/640] chore: add basic acceptance tests --- acceptance/tests/admin.spec.ts | 7 + acceptance/tests/login.ts | 19 ++ acceptance/tests/password.ts | 12 ++ acceptance/tests/register.spec.ts | 13 ++ acceptance/tests/register.ts | 31 ++++ acceptance/tests/user.ts | 175 ++++++++++++++++++ acceptance/tests/username-passkey.spec.ts | 108 +++++++++++ .../tests/username-password-changed.spec.ts | 26 +++ acceptance/tests/username-password.spec.ts | 36 +++- apps/login/src/lib/server/passkeys.ts | 3 +- apps/login/src/lib/server/session.ts | 3 +- playwright.config.ts | 126 ++++++------- 12 files changed, 484 insertions(+), 75 deletions(-) create mode 100644 acceptance/tests/admin.spec.ts create mode 100644 acceptance/tests/login.ts create mode 100644 acceptance/tests/password.ts create mode 100644 acceptance/tests/register.spec.ts create mode 100644 acceptance/tests/register.ts create mode 100644 acceptance/tests/user.ts create mode 100644 acceptance/tests/username-passkey.spec.ts create mode 100644 acceptance/tests/username-password-changed.spec.ts diff --git a/acceptance/tests/admin.spec.ts b/acceptance/tests/admin.spec.ts new file mode 100644 index 0000000000..e08d7bf82b --- /dev/null +++ b/acceptance/tests/admin.spec.ts @@ -0,0 +1,7 @@ +import {test} from "@playwright/test"; +import {loginWithPassword} from "./login"; + +test("admin login", async ({page}) => { + await loginWithPassword(page, "zitadel-admin@zitadel.localhost", "Password1.") + await page.getByRole("heading", {name: "Welcome ZITADEL Admin!"}).click(); +}); diff --git a/acceptance/tests/login.ts b/acceptance/tests/login.ts new file mode 100644 index 0000000000..3ebcade5f7 --- /dev/null +++ b/acceptance/tests/login.ts @@ -0,0 +1,19 @@ +import {Page} from "@playwright/test"; + +export async function loginWithPassword(page: Page, username: string, password: string) { + await page.goto("/loginname"); + const loginname = page.getByLabel("Loginname"); + await loginname.pressSequentially(username); + await loginname.press("Enter"); + const pw = page.getByLabel("Password"); + await pw.pressSequentially(password); + await pw.press("Enter"); +} + + +export async function loginWithPasskey(page: Page, username: string) { + await page.goto("/loginname"); + const loginname = page.getByLabel("Loginname"); + await loginname.pressSequentially(username); + await loginname.press("Enter"); +} \ No newline at end of file diff --git a/acceptance/tests/password.ts b/acceptance/tests/password.ts new file mode 100644 index 0000000000..360ff65e17 --- /dev/null +++ b/acceptance/tests/password.ts @@ -0,0 +1,12 @@ +import {Page} from "@playwright/test"; + +export async function changePassword(page: Page, loginname: string, password: string) { + await page.goto('password/change?' + new URLSearchParams({loginName: loginname})); + await changePasswordScreen(page, loginname, password, password) + await page.getByRole('button', {name: 'Continue'}).click(); +} + +async function changePasswordScreen(page: Page, loginname: string, password1: string, password2: string) { + await page.getByLabel('New Password *').pressSequentially(password1); + await page.getByLabel('Confirm Password *').pressSequentially(password2); +} \ No newline at end of file diff --git a/acceptance/tests/register.spec.ts b/acceptance/tests/register.spec.ts new file mode 100644 index 0000000000..932bf3134c --- /dev/null +++ b/acceptance/tests/register.spec.ts @@ -0,0 +1,13 @@ +import {test} from "@playwright/test"; +import {registerWithPassword} from './register'; +import {loginWithPassword} from "./login"; + +test("register with password", async ({page}) => { + const firstname = "firstname" + const lastname = "lastname" + const username = "register@example.com" + const password = "Password1!" + await registerWithPassword(page, firstname, lastname, username, password, password) + await page.getByRole("heading", {name: "Welcome " + lastname + " " + lastname + "!"}).click(); + await loginWithPassword(page, username, password) +}); diff --git a/acceptance/tests/register.ts b/acceptance/tests/register.ts new file mode 100644 index 0000000000..bb403f6999 --- /dev/null +++ b/acceptance/tests/register.ts @@ -0,0 +1,31 @@ +import {Page} from "@playwright/test"; + +export async function registerWithPassword(page: Page, firstname: string, lastname: string, email: string, password1: string, password2: string) { + await page.goto('/register'); + await registerUserScreen(page, firstname, lastname, email) + await page.getByLabel('Password').click(); + await page.getByRole('button', {name: 'Continue'}).click(); + await registerPasswordScreen(page, password1, password2) + await page.getByRole('button', {name: 'Continue'}).click(); +} + +export async function registerWithPasskey(page: Page, firstname: string, lastname: string, email: string) { + await page.goto('/register'); + await registerUserScreen(page, firstname, lastname, email) + await page.getByLabel('Passkey').click(); + await page.getByRole('button', {name: 'Continue'}).click(); + await page.getByRole('button', {name: 'Continue'}).click(); +} + +async function registerUserScreen(page: Page, firstname: string, lastname: string, email: string) { + await page.getByLabel('First name *').pressSequentially(firstname); + await page.getByLabel('Last name *').pressSequentially(lastname); + await page.getByLabel('E-mail *').pressSequentially(email); + await page.getByRole('checkbox').first().check(); + await page.getByRole('checkbox').nth(1).check(); +} + +async function registerPasswordScreen(page: Page, password1: string, password2: string) { + await page.getByLabel('Password *', {exact: true}).fill(password1); + await page.getByLabel('Confirm Password *').fill(password2); +} \ No newline at end of file diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts new file mode 100644 index 0000000000..18c2ec55be --- /dev/null +++ b/acceptance/tests/user.ts @@ -0,0 +1,175 @@ +import fetch from 'node-fetch'; +import {Page} from "@playwright/test"; +import {registerWithPasskey} from "./register"; +import {loginWithPasskey, loginWithPassword} from "./login"; +import {changePassword} from "./password"; + +export interface userProps { + email: string; + firstName: string; + lastName: string; + organization: string; + password: string; +} + +class User { + private readonly props: userProps; + private user: string; + + constructor(userProps: userProps) { + this.props = userProps; + } + + async ensure() { + await this.remove() + + const body = { + username: this.props.email, + organization: { + orgId: this.props.organization + }, + profile: { + givenName: this.props.firstName, + familyName: this.props.lastName, + }, + email: { + email: this.props.email, + isVerified: true, + }, + password: { + password: this.props.password!, + } + } + + const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/human", { + method: 'POST', + body: JSON.stringify(body), + headers: { + 'Content-Type': 'application/json', + 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! + } + }); + if (response.statusCode >= 400 && response.statusCode != 409) { + const error = 'HTTP Error: ' + response.statusCode + ' - ' + response.statusMessage; + console.error(error); + throw new Error(error); + } + return + } + + async remove() { + const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + this.userId(), { + method: 'DELETE', + headers: { + 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! + } + }); + if (response.statusCode >= 400 && response.statusCode != 404) { + const error = 'HTTP Error: ' + response.statusCode + ' - ' + response.statusMessage; + console.error(error); + throw new Error(error); + } + return + } + + public userId() { + return this.user; + } + + public username() { + return this.props.email; + } + + public password() { + return this.props.password; + } + + public fullName() { + return this.props.firstName + " " + this.props.lastName + } + + public async login(page: Page) { + await loginWithPassword(page, this.username(), this.password()) + } + + public async changePassword(page: Page, password: string) { + await loginWithPassword(page, this.username(), this.password()) + await changePassword(page, this.username(), password) + this.props.password = password + } +} + +export class PasswordUser extends User { +} + +export interface passkeyUserProps { + email: string; + firstName: string; + lastName: string; + organization: string; +} + +export class PasskeyUser { + private props: passkeyUserProps + + constructor(props: passkeyUserProps) { + this.props = props + } + + async ensurePasskey(page: Page) { + await registerWithPasskey(page, this.props.firstName, this.props.lastName, this.props.email) + } + + public async login(page: Page) { + await loginWithPasskey(page, this.props.email) + } + + public fullName() { + return this.props.firstName + " " + this.props.lastName + } + + async ensurePasskeyRegister() { + const url = new URL(process.env.ZITADEL_API_URL!) + const registerBody = { + domain: url.hostname, + } + const userId = "" + const registerResponse = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + userId + "/passkeys", { + method: 'POST', + body: JSON.stringify(registerBody), + headers: { + 'Content-Type': 'application/json', + 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! + } + }); + if (registerResponse.statusCode >= 400 && registerResponse.statusCode != 409) { + const error = 'HTTP Error: ' + registerResponse.statusCode + ' - ' + registerResponse.statusMessage; + console.error(error); + throw new Error(error); + } + const respJson = await registerResponse.json() + return respJson + } + + async ensurePasskeyVerify(passkeyId: string, credential: Credential) { + const verifyBody = { + publicKeyCredential: credential, + passkeyName: "passkey", + } + const userId = "" + const verifyResponse = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + userId + "/passkeys/" + passkeyId, { + method: 'POST', + body: JSON.stringify(verifyBody), + headers: { + 'Content-Type': 'application/json', + 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! + } + }); + if (verifyResponse.statusCode >= 400 && verifyResponse.statusCode != 409) { + const error = 'HTTP Error: ' + verifyResponse.statusCode + ' - ' + verifyResponse.statusMessage; + console.error(error); + throw new Error(error); + } + return + } +} \ No newline at end of file diff --git a/acceptance/tests/username-passkey.spec.ts b/acceptance/tests/username-passkey.spec.ts new file mode 100644 index 0000000000..0cc98851d9 --- /dev/null +++ b/acceptance/tests/username-passkey.spec.ts @@ -0,0 +1,108 @@ +import path from 'path'; +import dotenv from 'dotenv'; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +/* +const BASE64_ENCODED_PK = + "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDbBOu5Lhs4vpowbCnmCyLUpIE7JM9sm9QXzye2G+jr+Kr" + + "MsinWohEce47BFPJlTaDzHSvOW2eeunBO89ZcvvVc8RLz4qyQ8rO98xS1jtgqi1NcBPETDrtzthODu/gd0sjB2Tk3TLuBGV" + + "oPXt54a+Oo4JbBJ6h3s0+5eAfGplCbSNq6hN3Jh9YOTw5ZA6GCEy5l8zBaOgjXytd2v2OdSVoEDNiNQRkjJd2rmS2oi9AyQ" + + "FR3B7BrPSiDlCcITZFOWgLF5C31Wp/PSHwQhlnh7/6YhnE2y9tzsUvzx0wJXrBADW13+oMxrneDK3WGbxTNYgIi1PvSqXlq" + + "GjHtCK+R2QkXAgMBAAECggEAVc6bu7VAnP6v0gDOeX4razv4FX/adCao9ZsHZ+WPX8PQxtmWYqykH5CY4TSfsuizAgyPuQ0" + + "+j4Vjssr9VODLqFoanspT6YXsvaKanncUYbasNgUJnfnLnw3an2XpU2XdmXTNYckCPRX9nsAAURWT3/n9ljc/XYY22ecYxM" + + "8sDWnHu2uKZ1B7M3X60bQYL5T/lVXkKdD6xgSNLeP4AkRx0H4egaop68hoW8FIwmDPVWYVAvo8etzWCtibRXz5FcNld9MgD" + + "/Ai7ycKy4Q1KhX5GBFI79MVVaHkSQfxPHpr7/XcmpQOEAr+BMPon4s4vnKqAGdGB3j/E3d/+4F2swykoQKBgQD8hCsp6FIQ" + + "5umJlk9/j/nGsMl85LgLaNVYpWlPRKPc54YNumtvj5vx1BG+zMbT7qIE3nmUPTCHP7qb5ERZG4CdMCS6S64/qzZEqijLCqe" + + "pwj6j4fV5SyPWEcpxf6ehNdmcfgzVB3Wolfwh1ydhx/96L1jHJcTKchdJJzlfTvq8wwKBgQDeCnKws1t5GapfE1rmC/h4ol" + + "L2qZTth9oQmbrXYohVnoqNFslDa43ePZwL9Jmd9kYb0axOTNMmyrP0NTj41uCfgDS0cJnNTc63ojKjegxHIyYDKRZNVUR/d" + + "xAYB/vPfBYZUS7M89pO6LLsHhzS3qpu3/hppo/Uc/AM/r8PSflNHQKBgDnWgBh6OQncChPUlOLv9FMZPR1ZOfqLCYrjYEqi" + + "uzGm6iKM13zXFO4AGAxu1P/IAd5BovFcTpg79Z8tWqZaUUwvscnl+cRlj+mMXAmdqCeO8VASOmqM1ml667axeZDIR867ZG8" + + "K5V029Wg+4qtX5uFypNAAi6GfHkxIKrD04yOHAoGACdh4wXESi0oiDdkz3KOHPwIjn6BhZC7z8mx+pnJODU3cYukxv3WTct" + + "lUhAsyjJiQ/0bK1yX87ulqFVgO0Knmh+wNajrb9wiONAJTMICG7tiWJOm7fW5cfTJwWkBwYADmkfTRmHDvqzQSSvoC2S7aa" + + "9QulbC3C/qgGFNrcWgcT9kCgYAZTa1P9bFCDU7hJc2mHwJwAW7/FQKEJg8SL33KINpLwcR8fqaYOdAHWWz636osVEqosRrH" + + "zJOGpf9x2RSWzQJ+dq8+6fACgfFZOVpN644+sAHfNPAI/gnNKU5OfUv+eav8fBnzlf1A3y3GIkyMyzFN3DE7e0n/lyqxE4H" + + "BYGpI8g=="; + +const test = base.extend<{ user: PasskeyUser }>({ + user: async ({page}, use) => { + + // Initialize a CDP session for the current page + const client = await page.context().newCDPSession(page); + // Enable WebAuthn environment in this session + await client.send('WebAuthn.enable', {enableUI: true}); + + // Attach a virtual authenticator with specific options + const result = await client.send('WebAuthn.addVirtualAuthenticator', { + options: { + protocol: 'ctap2', + transport: 'usb', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + }, + }); + const authenticatorId = result.authenticatorId; + + const url = new URL(process.env.ZITADEL_API_URL!) + await client.send('WebAuthn.addCredential', { + credential: { + credentialId: "", + rpId: url.hostname, + privateKey: BASE64_ENCODED_PK, + isResidentCredential: false, + signCount: 0, + }, + authenticatorId: authenticatorId + }); + + await client.send('WebAuthn.setUserVerified', { + authenticatorId: authenticatorId, + isUserVerified: true, + }); + await client.send('WebAuthn.setAutomaticPresenceSimulation', { + authenticatorId: authenticatorId, + enabled: true, + }); + + const user = new PasskeyUser({ + email: "password@example.com", + firstName: "first", + lastName: "last", + organization: "", + }); + await user.ensure(); + const respJson = await user.ensurePasskeyRegister(); + + const credential = await navigator.credentials.create({ + publicKey: respJson.publicKeyCredentialCreationOptions + }); + + await user.ensurePasskeyVerify(respJson.passkeyId, respJson.publicKeyCredentialCreationOptions) + use(user); + await client.send('WebAuthn.setAutomaticPresenceSimulation', { + authenticatorId, + enabled: false, + }); + }, +}); + +const test = base.extend<{ user: PasskeyUser }>({ + user: async ({page}, use) => { + const user = new PasskeyUser({ + email: "passkey@example.com", + firstName: "first", + lastName: "last", + organization: "", + }); + await user.ensurePasskey(page); + await use(user) + }, +}); + +test("username and passkey login", async ({user, page}) => { + await user.login(page) + await page.getByRole("heading", {name: "Welcome " + user.fullName() + "!"}).click(); +}); +*/ \ No newline at end of file diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts new file mode 100644 index 0000000000..a3e5343672 --- /dev/null +++ b/acceptance/tests/username-password-changed.spec.ts @@ -0,0 +1,26 @@ +import {test as base} from "@playwright/test"; +import {PasswordUser} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUser }>({ + user: async ({page}, use) => { + const user = new PasswordUser({ + email: "password-changed@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + }); + await user.ensure(); + await use(user); + }, +}); + +test("username and password changed login", async ({user, page}) => { + await user.changePassword(page, "ChangedPw1!") + await user.login(page) +}); diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index 31d165358e..f0bb8bddb3 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -1,12 +1,28 @@ -import { test } from "@playwright/test"; +import {test as base} from "@playwright/test"; +import {PasswordUser} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; -test("username and password", async ({ page }) => { - await page.goto("/"); - const loginname = page.getByLabel("Loginname"); - await loginname.pressSequentially("zitadel-admin@zitadel.localhost"); - await loginname.press("Enter"); - const password = page.getByLabel("Password"); - await password.pressSequentially("Password1!"); - await password.press("Enter"); - await page.getByRole("heading", { name: "Welcome ZITADEL Admin!" }).click(); +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUser }>({ + user: async ({page}, use) => { + const user = new PasswordUser({ + email: "password@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + }); + await user.ensure(); + await use(user); + }, }); + +test("username and password login", async ({user, page}) => { + await user.login(page) + await page.getByRole("heading", {name: "Welcome " + user.fullName() + "!"}).click(); +}); + + diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 5ad170f8d3..d7a81e7952 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -37,7 +37,8 @@ export async function registerPasskeyLink( sessionToken: sessionCookie.token, }); - const domain = headers().get("host"); + // TODO remove ports from host header for URL with port + const domain = "localhost"; if (!domain) { throw new Error("Could not get domain"); diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 3bd9762916..b1beb6549e 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -85,7 +85,8 @@ export async function updateSession(options: UpdateSessionCommand) { return Promise.reject(error); }); - const host = headers().get("host"); + // TODO remove ports from host header for URL with port + const host = "localhost" if ( host && diff --git a/playwright.config.ts b/playwright.config.ts index bf73cb21d1..f3a9658f82 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, devices } from "@playwright/test"; +import {defineConfig, devices} from "@playwright/test"; /** * Read environment variables from file. @@ -12,70 +12,70 @@ import { defineConfig, devices } from "@playwright/test"; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: "./acceptance/tests", - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: "html", - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: "http://127.0.0.1:3000", + testDir: "./acceptance/tests", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: "http://127.0.0.1:3000", - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: "on-first-retry", - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", }, - { - name: "firefox", - use: { ...devices["Desktop Firefox"] }, + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: {...devices["Desktop Chrome"]}, + }, + /* + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + TODO: webkit fails. Is this a bug? + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + */ + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run local dev server before starting the tests */ + webServer: { + command: "pnpm start:built", + url: "http://127.0.0.1:3000", + reuseExistingServer: !process.env.CI, + timeout: 5 * 60_000, }, - /* TODO: webkit fails. Is this a bug? - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, -*/ - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, - ], - - /* Run local dev server before starting the tests */ - webServer: { - command: "pnpm start:built", - url: "http://127.0.0.1:3000", - reuseExistingServer: !process.env.CI, - timeout: 5 * 60_000, - }, }); From 4ccf0de4b6beec32285cde3342e281d9bfa6166c Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 29 Oct 2024 08:42:48 +0100 Subject: [PATCH 364/640] mock services --- apps/login/mock/mocked-services.cfg | 1 - apps/login/src/app/global-error.tsx | 33 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 apps/login/src/app/global-error.tsx diff --git a/apps/login/mock/mocked-services.cfg b/apps/login/mock/mocked-services.cfg index d7a29492f0..e298938586 100644 --- a/apps/login/mock/mocked-services.cfg +++ b/apps/login/mock/mocked-services.cfg @@ -1,5 +1,4 @@ zitadel/user/v2/user_service.proto -zitadel/org/v2/org_service.proto zitadel/session/v2/session_service.proto zitadel/settings/v2/settings_service.proto zitadel/management.proto diff --git a/apps/login/src/app/global-error.tsx b/apps/login/src/app/global-error.tsx new file mode 100644 index 0000000000..b790dc3070 --- /dev/null +++ b/apps/login/src/app/global-error.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { Boundary } from "@/components/boundary"; +import { Button } from "@/components/button"; +import { ThemeWrapper } from "@/components/theme-wrapper"; + +export default function GlobalError({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ( + // global-error must include html and body tags + + + + +
    +
    + Error: {error?.message} +
    +
    + +
    +
    +
    +
    + + + ); +} From 402d15b0cb0463532a56a8126ed25aa0b96d8a55 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 29 Oct 2024 09:06:34 +0100 Subject: [PATCH 365/640] exclude verify tests --- apps/login/cypress/integration/verify.cy.ts | 88 ++++++++++++++------- apps/login/mock/mocked-services.cfg | 1 + 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index 6a43f0af0b..d9688c631c 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -1,22 +1,50 @@ import { stub } from "../support/mock"; describe("verify invite", () => { - beforeEach(() => { - stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { - data: { - details: { - totalResult: 1, - }, - result: [{ id: "256088834543534543" }], - }, - }); + // beforeEach(() => { + // stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { + // data: { + // details: { + // totalResult: 1, + // }, + // result: [{ id: "256088834543534543" }], + // }, + // }); - stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { - data: { - authMethodTypes: [], - }, - }); - }); + // stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { + // data: { + // authMethodTypes: [], + // }, + // }); + + // stub("zitadel.user.v2.UserService", "GetUserById", { + // data: { + // user: { + // userId: "221394658884845598", + // state: 1, + // username: "john@zitadel.com", + // loginNames: ["john@zitadel.com"], + // preferredLoginName: "john@zitadel.com", + // human: { + // userId: "221394658884845598", + // state: 1, + // username: "john@zitadel.com", + // loginNames: ["john@zitadel.com"], + // preferredLoginName: "john@zitadel.com", + // profile: { + // givenName: "John", + // familyName: "Doe", + // avatarUrl: "https://zitadel.com/avatar.jpg", + // }, + // email: { + // email: "john@zitadel.com", + // isVerified: true, + // }, + // }, + // }, + // }, + // }); + // }); it.only("shows authenticators after successful invite verification", () => { stub("zitadel.user.v2.UserService", "VerifyInviteCode"); @@ -40,22 +68,22 @@ describe("verify invite", () => { }); describe("verify email", () => { - beforeEach(() => { - stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { - data: { - details: { - totalResult: 1, - }, - result: [{ id: "256088834543534543" }], - }, - }); + // beforeEach(() => { + // stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { + // data: { + // details: { + // totalResult: 1, + // }, + // result: [{ id: "256088834543534543" }], + // }, + // }); - stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { - data: { - authMethodTypes: [], - }, - }); - }); + // stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { + // data: { + // authMethodTypes: [], + // }, + // }); + // }); it("shows password and passkey method after successful invite verification", () => { stub("zitadel.user.v2.UserService", "VerifyEmail"); diff --git a/apps/login/mock/mocked-services.cfg b/apps/login/mock/mocked-services.cfg index e298938586..8ea05f0bdf 100644 --- a/apps/login/mock/mocked-services.cfg +++ b/apps/login/mock/mocked-services.cfg @@ -1,3 +1,4 @@ +zitadel/org/v2/org_service.proto zitadel/user/v2/user_service.proto zitadel/session/v2/session_service.proto zitadel/settings/v2/settings_service.proto From 164c7d69057583efd0f1ded6394435b63d5d3fcc Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 29 Oct 2024 09:17:03 +0100 Subject: [PATCH 366/640] revert mocked-services --- apps/login/mock/mocked-services.cfg | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/login/mock/mocked-services.cfg b/apps/login/mock/mocked-services.cfg index 8ea05f0bdf..72e4d98748 100644 --- a/apps/login/mock/mocked-services.cfg +++ b/apps/login/mock/mocked-services.cfg @@ -1,7 +1,6 @@ -zitadel/org/v2/org_service.proto zitadel/user/v2/user_service.proto zitadel/session/v2/session_service.proto zitadel/settings/v2/settings_service.proto zitadel/management.proto zitadel/auth.proto -zitadel/admin.proto +zitadel/admin.proto \ No newline at end of file From cd6a11e269a25345988f44570af3191192c6ec31 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 29 Oct 2024 10:44:03 +0100 Subject: [PATCH 367/640] fix tests --- apps/login/cypress/integration/register.cy.ts | 39 ++++- apps/login/cypress/integration/verify.cy.ts | 157 +++++++++++------- apps/login/mock/mocked-services.cfg | 1 + .../choose-authenticator-to-setup.tsx | 2 +- apps/login/src/components/verify-form.tsx | 3 - apps/login/src/lib/server/email.ts | 4 +- apps/login/src/lib/server/register.ts | 12 +- 7 files changed, 136 insertions(+), 82 deletions(-) diff --git a/apps/login/cypress/integration/register.cy.ts b/apps/login/cypress/integration/register.cy.ts index bf23fc4037..902c6a8061 100644 --- a/apps/login/cypress/integration/register.cy.ts +++ b/apps/login/cypress/integration/register.cy.ts @@ -12,13 +12,40 @@ describe("register", () => { }); stub("zitadel.user.v2.UserService", "AddHumanUser", { data: { - userId: "123", - email: { - email: "john@zitadel.com", + userId: "221394658884845598", + }, + }); + stub("zitadel.session.v2.SessionService", "CreateSession", { + data: { + details: { + sequence: 859, + changeDate: new Date("2024-04-04T09:40:55.577Z"), + resourceOwner: "220516472055706145", }, - profile: { - givenName: "John", - familyName: "Doe", + sessionId: "221394658884845598", + sessionToken: + "SDMc7DlYXPgwRJ-Tb5NlLqynysHjEae3csWsKzoZWLplRji0AYY3HgAkrUEBqtLCvOayLJPMd0ax4Q", + challenges: undefined, + }, + }); + + stub("zitadel.session.v2.SessionService", "GetSession", { + data: { + session: { + id: "221394658884845598", + creationDate: new Date("2024-04-04T09:40:55.577Z"), + changeDate: new Date("2024-04-04T09:40:55.577Z"), + sequence: 859, + factors: { + user: { + id: "221394658884845598", + loginName: "john@zitadel.com", + }, + password: undefined, + webAuthN: undefined, + intent: undefined, + }, + metadata: {}, }, }, }); diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index d9688c631c..7fe4f4afb6 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -1,54 +1,89 @@ import { stub } from "../support/mock"; describe("verify invite", () => { - // beforeEach(() => { - // stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { - // data: { - // details: { - // totalResult: 1, - // }, - // result: [{ id: "256088834543534543" }], - // }, - // }); + beforeEach(() => { + stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { + data: { + details: { + totalResult: 1, + }, + result: [{ id: "256088834543534543" }], + }, + }); - // stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { - // data: { - // authMethodTypes: [], - // }, - // }); + stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { + data: { + authMethodTypes: [], + }, + }); - // stub("zitadel.user.v2.UserService", "GetUserById", { - // data: { - // user: { - // userId: "221394658884845598", - // state: 1, - // username: "john@zitadel.com", - // loginNames: ["john@zitadel.com"], - // preferredLoginName: "john@zitadel.com", - // human: { - // userId: "221394658884845598", - // state: 1, - // username: "john@zitadel.com", - // loginNames: ["john@zitadel.com"], - // preferredLoginName: "john@zitadel.com", - // profile: { - // givenName: "John", - // familyName: "Doe", - // avatarUrl: "https://zitadel.com/avatar.jpg", - // }, - // email: { - // email: "john@zitadel.com", - // isVerified: true, - // }, - // }, - // }, - // }, - // }); - // }); + stub("zitadel.user.v2.UserService", "GetUserByID", { + data: { + user: { + userId: "221394658884845598", + state: 1, + username: "john@zitadel.com", + loginNames: ["john@zitadel.com"], + preferredLoginName: "john@zitadel.com", + human: { + userId: "221394658884845598", + state: 1, + username: "john@zitadel.com", + loginNames: ["john@zitadel.com"], + preferredLoginName: "john@zitadel.com", + profile: { + givenName: "John", + familyName: "Doe", + avatarUrl: "https://zitadel.com/avatar.jpg", + }, + email: { + email: "john@zitadel.com", + isVerified: false, // email is not verified yet + }, + }, + }, + }, + }); + + stub("zitadel.session.v2.SessionService", "CreateSession", { + data: { + details: { + sequence: 859, + changeDate: new Date("2024-04-04T09:40:55.577Z"), + resourceOwner: "220516472055706145", + }, + sessionId: "221394658884845598", + sessionToken: + "SDMc7DlYXPgwRJ-Tb5NlLqynysHjEae3csWsKzoZWLplRji0AYY3HgAkrUEBqtLCvOayLJPMd0ax4Q", + challenges: undefined, + }, + }); + + stub("zitadel.session.v2.SessionService", "GetSession", { + data: { + session: { + id: "221394658884845598", + creationDate: new Date("2024-04-04T09:40:55.577Z"), + changeDate: new Date("2024-04-04T09:40:55.577Z"), + sequence: 859, + factors: { + user: { + id: "221394658884845598", + loginName: "john@zitadel.com", + }, + password: undefined, + webAuthN: undefined, + intent: undefined, + }, + metadata: {}, + }, + }, + }); + }); it.only("shows authenticators after successful invite verification", () => { stub("zitadel.user.v2.UserService", "VerifyInviteCode"); - cy.visit("/verify?userId=123&code=abc&invite=true"); + cy.visit("/verify?userId=221394658884845598&code=abc&invite=true"); cy.location("pathname", { timeout: 10_000 }).should( "eq", "/authenticator/set", @@ -62,32 +97,32 @@ describe("verify invite", () => { }); // TODO: Avoid uncaught exception in application cy.once("uncaught:exception", () => false); - cy.visit("/verify?userId=123&code=abc&invite=true"); + cy.visit("/verify?userId=221394658884845598&code=abc&invite=true"); cy.contains("Could not verify invite", { timeout: 10_000 }); }); }); describe("verify email", () => { - // beforeEach(() => { - // stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { - // data: { - // details: { - // totalResult: 1, - // }, - // result: [{ id: "256088834543534543" }], - // }, - // }); + beforeEach(() => { + stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { + data: { + details: { + totalResult: 1, + }, + result: [{ id: "256088834543534543" }], + }, + }); - // stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { - // data: { - // authMethodTypes: [], - // }, - // }); - // }); + stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { + data: { + authMethodTypes: [], + }, + }); + }); it("shows password and passkey method after successful invite verification", () => { stub("zitadel.user.v2.UserService", "VerifyEmail"); - cy.visit("/verify?userId=123&code=abc"); + cy.visit("/verify?userId=221394658884845598&code=abc"); cy.location("pathname", { timeout: 10_000 }).should( "eq", "/authenticator/set", @@ -101,7 +136,7 @@ describe("verify email", () => { }); // TODO: Avoid uncaught exception in application cy.once("uncaught:exception", () => false); - cy.visit("/verify?userId=123&code=abc&submit=true"); + cy.visit("/verify?userId=221394658884845598&code=abc&submit=true"); cy.contains("Could not verify email", { timeout: 10_000 }); }); }); diff --git a/apps/login/mock/mocked-services.cfg b/apps/login/mock/mocked-services.cfg index 72e4d98748..6a758ab8c1 100644 --- a/apps/login/mock/mocked-services.cfg +++ b/apps/login/mock/mocked-services.cfg @@ -1,4 +1,5 @@ zitadel/user/v2/user_service.proto +zitadel/org/v2/org_service.proto zitadel/session/v2/session_service.proto zitadel/settings/v2/settings_service.proto zitadel/management.proto diff --git a/apps/login/src/components/choose-authenticator-to-setup.tsx b/apps/login/src/components/choose-authenticator-to-setup.tsx index fe1f66132c..25ca69e776 100644 --- a/apps/login/src/components/choose-authenticator-to-setup.tsx +++ b/apps/login/src/components/choose-authenticator-to-setup.tsx @@ -25,7 +25,7 @@ export function ChooseAuthenticatorToSetup({ } else { return ( <> - {loginSettings.passkeysType === PasskeysType.ALLOWED && + {loginSettings.passkeysType === PasskeysType.NOT_ALLOWED && !loginSettings.allowUsernamePassword && ( {t("noMethodsAvailable")} )} diff --git a/apps/login/src/components/verify-form.tsx b/apps/login/src/components/verify-form.tsx index f0f60005b4..b43c0eaa42 100644 --- a/apps/login/src/components/verify-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -3,7 +3,6 @@ import { Alert } from "@/components/alert"; import { resendVerification, sendVerification } from "@/lib/server/email"; import { useTranslations } from "next-intl"; -import { useRouter } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { Button, ButtonVariants } from "./button"; @@ -35,8 +34,6 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { const [loading, setLoading] = useState(false); - const router = useRouter(); - async function resendCode() { setError(""); setLoading(true); diff --git a/apps/login/src/lib/server/email.ts b/apps/login/src/lib/server/email.ts index f1925317a4..d2ab8496c3 100644 --- a/apps/login/src/lib/server/email.ts +++ b/apps/login/src/lib/server/email.ts @@ -74,7 +74,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { if (session.factors?.user?.loginName) { params.set("loginName", session.factors?.user?.loginName); } - return redirect("/authenticator/set?" + params); + return redirect(`/authenticator/set?${params}`); } } @@ -134,6 +134,6 @@ export async function sendVerificationRedirectWithoutCheck(command: { if (session.factors?.user?.loginName) { params.set("loginName", session.factors?.user?.loginName); } - return redirect("/authenticator/set?" + params); + return redirect(`/authenticator/set?${params}`); } } diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index a3ad8a0927..a6d8e40643 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -54,13 +54,7 @@ export async function registerUser(command: RegisterUserCommand) { checks, undefined, command.authRequestId, - ).then((session) => { - return { - userId: human.userId, - sessionId: session.id, - factors: session.factors, - }; - }); + ); if (!session || !session.factors?.user) { return { error: "Could not create session" }; @@ -83,9 +77,9 @@ export async function registerUser(command: RegisterUserCommand) { organization: session.factors.user.organizationId, }); - if (command.authRequestId && session.userId) { + if (command.authRequestId && session.factors.user.id) { params.append("authRequest", command.authRequestId); - params.append("sessionId", session.sessionId); + params.append("sessionId", session.id); return redirect("/login?" + params); } else { From fab3ed72724b21436a23c85395f27356b7b56c98 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Tue, 29 Oct 2024 13:05:16 +0100 Subject: [PATCH 368/640] chore(test:integration): install cypress binary --- .github/workflows/test.yml | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 35f9b1b345..616498bd75 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,23 +57,7 @@ jobs: ${{ runner.os }}-pnpm-store- - name: Install Dependencies - run: CYPRESS_INSTALL_BINARY=0 pnpm install - - - run: echo "CYPRESS_VERSION=$(pnpm list -r | grep cypress | cut -d ' ' -f 2)" >> $GITHUB_ENV - if: ${{ matrix.command == 'test:integration' }} - - - uses: actions/cache@v4.0.2 - name: Setup Cypress binary cache - with: - path: ~/.cache/Cypress - key: ${{ runner.os }}-cypress-binary-${{ env.CYPRESS_VERSION }} - restore-keys: | - ${{ runner.os }}-cypress-binary- - if: ${{ matrix.command == 'test:integration' }} - - - name: Install Cypress Browsers - run: pnpm install - if: ${{ matrix.command == 'test:integration' }} + run: CYPRESS_INSTALL_BINARY=${{ matrix.command == 'test:integration' }} pnpm install --frozen-lockfile - run: echo "PLAYWRIGHT_VERSION=$(npx playwright --version | cut -d ' ' -f 2)" >> $GITHUB_ENV if: ${{ matrix.command == 'test:acceptance' }} @@ -99,7 +83,7 @@ jobs: run: ZITADEL_DEV_UID=root pnpm run-zitadel if: ${{ matrix.command == 'test:acceptance' }} - - name: Install Playwright Browsers + - name: Create Production Build run: pnpm build if: ${{ matrix.command == 'test:acceptance' }} From 050ef343fe5adb83102da8645af672300ed3c082 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Oct 2024 13:59:03 +0100 Subject: [PATCH 369/640] session item rehaul, finish session util --- apps/login/src/components/idp-signin.tsx | 31 ++---------- apps/login/src/components/session-item.tsx | 57 +++++++--------------- apps/login/src/lib/login.ts | 32 ++++++++++++ apps/login/src/lib/server/password.ts | 28 +++-------- apps/login/src/lib/server/session.ts | 43 +++++++++++++++- 5 files changed, 102 insertions(+), 89 deletions(-) create mode 100644 apps/login/src/lib/login.ts diff --git a/apps/login/src/components/idp-signin.tsx b/apps/login/src/components/idp-signin.tsx index ebee13ae27..6a010f81da 100644 --- a/apps/login/src/components/idp-signin.tsx +++ b/apps/login/src/components/idp-signin.tsx @@ -34,33 +34,10 @@ export function IdpSignin({ idpIntentToken, }, authRequestId, - }) - .then((session) => { - if (authRequestId && session && session.id) { - return router.push( - `/login?` + - new URLSearchParams({ - sessionId: session.id, - authRequest: authRequestId, - }), - ); - } else { - const params = new URLSearchParams({}); - if (session.factors?.user?.loginName) { - params.set("loginName", session.factors?.user?.loginName); - } - - if (authRequestId) { - params.set("authRequestId", authRequestId); - } - - return router.push(`/signedin?` + params); - } - }) - .catch((error) => { - setError(error.message); - return; - }); + }).catch((error) => { + setError(error.message); + return; + }); setLoading(false); }, []); diff --git a/apps/login/src/components/session-item.tsx b/apps/login/src/components/session-item.tsx index a2abfd897a..7bc2e4cab3 100644 --- a/apps/login/src/components/session-item.tsx +++ b/apps/login/src/components/session-item.tsx @@ -1,11 +1,11 @@ "use client"; -import { cleanupSession } from "@/lib/server/session"; +import { sendLoginname } from "@/lib/server/loginname"; +import { cleanupSession, continueWithSession } from "@/lib/server/session"; import { XCircleIcon } from "@heroicons/react/24/outline"; import { Timestamp, timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import moment from "moment"; -import Link from "next/link"; import { useState } from "react"; import { Avatar } from "./avatar"; @@ -53,42 +53,21 @@ export function SessionItem({ const [error, setError] = useState(null); return ( - { + if (valid && session?.factors?.user) { + return continueWithSession({ + ...session, + authRequestId: authRequestId, + }); + } else if (session.factors?.user) { + return sendLoginname({ + loginName: session.factors?.user?.loginName, + organization: session.factors.user.organizationId, + authRequestId: authRequestId, + }); + } + }} className="group flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light hover:shadow-lg dark:hover:bg-white/10 py-2 px-4 rounded-md transition-all" >
    @@ -129,6 +108,6 @@ export function SessionItem({ }} />
    - + ); } diff --git a/apps/login/src/lib/login.ts b/apps/login/src/lib/login.ts new file mode 100644 index 0000000000..39f3396c9f --- /dev/null +++ b/apps/login/src/lib/login.ts @@ -0,0 +1,32 @@ +import { redirect } from "next/navigation"; + +type FinishFlowCommand = + | { + sessionId: string; + authRequestId: string; + } + | { loginName: string }; + +/** + * redirects user back to OIDC application or to a success page + * @param command + * @returns + */ +export function finishFlow( + command: FinishFlowCommand & { organization?: string }, +) { + return "sessionId" in command && "authRequestId" in command + ? redirect( + `/login?` + + new URLSearchParams({ + sessionId: command.sessionId, + authRequest: command.authRequestId, + }), + ) + : redirect( + `/signedin?` + + new URLSearchParams({ + loginName: command.loginName, + }), + ); +} diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 2cb512bb97..1b121b96e6 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -21,6 +21,7 @@ import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_se import { headers } from "next/headers"; import { redirect } from "next/navigation"; import { getSessionCookieByLoginName } from "../cookies"; +import { finishFlow } from "../login"; type ResetPasswordCommand = { loginName: string; @@ -239,36 +240,19 @@ export async function sendPassword(command: UpdateSessionCommand) { // return router.push(`/passkey/set?` + params); // } - else if (command.authRequestId && submitted.sessionId) { - const params = new URLSearchParams({ - sessionId: submitted.sessionId, - authRequest: command.authRequestId, - }); - if (command.organization) { - params.append("organization", command.organization); - } - - return redirect(`/login?` + params); - } - - // without OIDC flow - const params = new URLSearchParams( - command.authRequestId + return finishFlow( + command.authRequestId && submitted.sessionId ? { - loginName: submitted.factors.user.loginName, + sessionId: submitted.sessionId, authRequestId: command.authRequestId, + organization: submitted.factors.user.organizationId, } : { loginName: submitted.factors.user.loginName, + organization: submitted.factors.user.organizationId, }, ); - - if (command.organization) { - params.append("organization", command.organization); - } - - return redirect(`/signedin?` + params); } export async function changePassword(command: { diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 2cd618ffb3..54a13fa149 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -6,6 +6,7 @@ import { } from "@/lib/server/cookie"; import { deleteSession, listAuthenticationMethodTypes } from "@/lib/zitadel"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; +import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { headers } from "next/headers"; import { @@ -14,6 +15,7 @@ import { getSessionCookieByLoginName, removeSessionFromCookie, } from "../cookies"; +import { finishFlow } from "../login"; type CreateNewSessionCommand = { userId: string; @@ -32,7 +34,46 @@ export async function createNewSessionForIdp(options: CreateNewSessionCommand) { if (!userId || !idpIntent) { throw new Error("No userId or loginName provided"); } - return createSessionForIdpAndUpdateCookie(userId, idpIntent, authRequestId); + const session = await createSessionForIdpAndUpdateCookie( + userId, + idpIntent, + authRequestId, + ); + + if (!session || !session.factors?.user) { + return { error: "Could not create session" }; + } + + return finishFlow( + authRequestId && session.id + ? { + sessionId: session.id, + authRequestId: authRequestId, + organization: session.factors.user.organizationId, + } + : { + loginName: session.factors.user.loginName, + organization: session.factors.user.organizationId, + }, + ); +} + +export async function continueWithSession({ + authRequestId, + ...session +}: Session & { authRequestId?: string }) { + return authRequestId && session.id && session.factors?.user + ? finishFlow({ + sessionId: session.id, + authRequestId: authRequestId, + organization: session.factors.user.organizationId, + }) + : session.factors?.user + ? finishFlow({ + loginName: session.factors.user.loginName, + organization: session.factors.user.organizationId, + }) + : null; } export type UpdateSessionCommand = { From c12410034ce8c805bc5007475f3727748cb289be Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Oct 2024 15:44:30 +0100 Subject: [PATCH 370/640] redirects --- apps/login/src/components/sessions-list.tsx | 10 ++++++++ apps/login/src/lib/server/loginname.ts | 1 + apps/login/src/lib/server/register.ts | 26 ++++++++++----------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/apps/login/src/components/sessions-list.tsx b/apps/login/src/components/sessions-list.tsx index 85c67e4b02..63b8fa1102 100644 --- a/apps/login/src/components/sessions-list.tsx +++ b/apps/login/src/components/sessions-list.tsx @@ -1,5 +1,6 @@ "use client"; +import { timestampMs } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { useTranslations } from "next-intl"; import { useState } from "react"; @@ -11,6 +12,14 @@ type Props = { authRequestId?: string; }; +function sortFc(a: Session, b: Session) { + if (a.changeDate && b.changeDate) { + return timestampMs(a.changeDate) - timestampMs(b.changeDate); + } else { + return 0; + } +} + export function SessionsList({ sessions, authRequestId }: Props) { const t = useTranslations("accounts"); const [list, setList] = useState(sessions); @@ -18,6 +27,7 @@ export function SessionsList({ sessions, authRequestId }: Props) {
    {list .filter((session) => session?.factors?.user?.loginName) + .sort(sortFc) .map((session, index) => { return ( Date: Wed, 30 Oct 2024 18:50:32 +0100 Subject: [PATCH 371/640] chore: add data-testid's and some additional testing --- acceptance/tests/login.ts | 23 ++- acceptance/tests/password.ts | 10 +- acceptance/tests/register.spec.ts | 5 +- acceptance/tests/register.ts | 38 ++-- acceptance/tests/user.ts | 174 ++++++++++++------ acceptance/tests/username-passkey.spec.ts | 7 +- .../tests/username-password-changed.spec.ts | 2 +- acceptance/tests/username-password.spec.ts | 2 +- .../src/components/change-password-form.tsx | 5 +- apps/login/src/components/login-otp.tsx | 3 +- apps/login/src/components/login-passkey.tsx | 2 + apps/login/src/components/password-form.tsx | 4 +- .../components/privacy-policy-checkboxes.tsx | 2 + .../register-form-without-password.tsx | 6 +- .../login/src/components/register-passkey.tsx | 1 + apps/login/src/components/register-u2f.tsx | 3 +- .../src/components/set-password-form.tsx | 3 +- .../components/set-register-password-form.tsx | 3 + apps/login/src/components/totp-register.tsx | 2 + apps/login/src/components/username-form.tsx | 5 +- .../src/components/verify-email-form.tsx | 3 + apps/login/src/lib/server/register.ts | 37 ++-- 22 files changed, 227 insertions(+), 113 deletions(-) diff --git a/acceptance/tests/login.ts b/acceptance/tests/login.ts index 3ebcade5f7..20d27e69f9 100644 --- a/acceptance/tests/login.ts +++ b/acceptance/tests/login.ts @@ -2,18 +2,23 @@ import {Page} from "@playwright/test"; export async function loginWithPassword(page: Page, username: string, password: string) { await page.goto("/loginname"); - const loginname = page.getByLabel("Loginname"); - await loginname.pressSequentially(username); - await loginname.press("Enter"); - const pw = page.getByLabel("Password"); - await pw.pressSequentially(password); - await pw.press("Enter"); + await loginnameScreen(page, username) + await page.getByTestId("submit-button").click() + await passwordScreen(page, password) + await page.getByTestId("submit-button").click() } +export async function loginnameScreen(page: Page, username: string) { + await page.getByTestId("username-text-input").pressSequentially(username); +} + +export async function passwordScreen(page: Page, password: string) { + await page.getByTestId("password-text-input").pressSequentially(password); +} export async function loginWithPasskey(page: Page, username: string) { await page.goto("/loginname"); - const loginname = page.getByLabel("Loginname"); - await loginname.pressSequentially(username); - await loginname.press("Enter"); + await loginnameScreen(page, username) + await page.getByTestId("submit-button").click() + await page.getByTestId("submit-button").click() } \ No newline at end of file diff --git a/acceptance/tests/password.ts b/acceptance/tests/password.ts index 360ff65e17..42d7190a10 100644 --- a/acceptance/tests/password.ts +++ b/acceptance/tests/password.ts @@ -2,11 +2,11 @@ import {Page} from "@playwright/test"; export async function changePassword(page: Page, loginname: string, password: string) { await page.goto('password/change?' + new URLSearchParams({loginName: loginname})); - await changePasswordScreen(page, loginname, password, password) - await page.getByRole('button', {name: 'Continue'}).click(); + await changePasswordScreen(page, password, password) + await page.getByTestId("submit-button").click(); } -async function changePasswordScreen(page: Page, loginname: string, password1: string, password2: string) { - await page.getByLabel('New Password *').pressSequentially(password1); - await page.getByLabel('Confirm Password *').pressSequentially(password2); +async function changePasswordScreen(page: Page, password1: string, password2: string) { + await page.getByTestId('password-text-input').pressSequentially(password1); + await page.getByTestId('password-confirm-text-input').pressSequentially(password2); } \ No newline at end of file diff --git a/acceptance/tests/register.spec.ts b/acceptance/tests/register.spec.ts index 932bf3134c..fe6cef1522 100644 --- a/acceptance/tests/register.spec.ts +++ b/acceptance/tests/register.spec.ts @@ -3,11 +3,8 @@ import {registerWithPassword} from './register'; import {loginWithPassword} from "./login"; test("register with password", async ({page}) => { - const firstname = "firstname" - const lastname = "lastname" const username = "register@example.com" const password = "Password1!" - await registerWithPassword(page, firstname, lastname, username, password, password) - await page.getByRole("heading", {name: "Welcome " + lastname + " " + lastname + "!"}).click(); + await registerWithPassword(page, "firstname", "lastname", username, password, password) await loginWithPassword(page, username, password) }); diff --git a/acceptance/tests/register.ts b/acceptance/tests/register.ts index bb403f6999..9700e9b2a1 100644 --- a/acceptance/tests/register.ts +++ b/acceptance/tests/register.ts @@ -2,30 +2,38 @@ import {Page} from "@playwright/test"; export async function registerWithPassword(page: Page, firstname: string, lastname: string, email: string, password1: string, password2: string) { await page.goto('/register'); + await registerUserScreenPassword(page, firstname, lastname, email) + await page.getByTestId('submit-button').click(); + await registerPasswordScreen(page, password1, password2) + await page.getByTestId('submit-button').click(); +} + +async function registerUserScreenPassword(page: Page, firstname: string, lastname: string, email: string) { await registerUserScreen(page, firstname, lastname, email) await page.getByLabel('Password').click(); - await page.getByRole('button', {name: 'Continue'}).click(); - await registerPasswordScreen(page, password1, password2) - await page.getByRole('button', {name: 'Continue'}).click(); +} + +async function registerPasswordScreen(page: Page, password1: string, password2: string) { + await page.getByTestId('password-text-input').fill(password1); + await page.getByTestId('password-confirm-text-input').fill(password2); } export async function registerWithPasskey(page: Page, firstname: string, lastname: string, email: string) { await page.goto('/register'); + await registerUserScreenPasskey(page, firstname, lastname, email) + await page.getByTestId('submit-button').click(); + await page.getByTestId('submit-button').click(); +} + +async function registerUserScreenPasskey(page: Page, firstname: string, lastname: string, email: string) { await registerUserScreen(page, firstname, lastname, email) await page.getByLabel('Passkey').click(); - await page.getByRole('button', {name: 'Continue'}).click(); - await page.getByRole('button', {name: 'Continue'}).click(); } async function registerUserScreen(page: Page, firstname: string, lastname: string, email: string) { - await page.getByLabel('First name *').pressSequentially(firstname); - await page.getByLabel('Last name *').pressSequentially(lastname); - await page.getByLabel('E-mail *').pressSequentially(email); - await page.getByRole('checkbox').first().check(); - await page.getByRole('checkbox').nth(1).check(); -} - -async function registerPasswordScreen(page: Page, password1: string, password2: string) { - await page.getByLabel('Password *', {exact: true}).fill(password1); - await page.getByLabel('Confirm Password *').fill(password2); + await page.getByTestId('firstname-text-input').pressSequentially(firstname); + await page.getByTestId('lastname-text-input').pressSequentially(lastname); + await page.getByTestId('email-text-input').pressSequentially(email); + await page.getByTestId('privacy-policy-checkbox').check(); + await page.getByTestId('tos-checkbox').check(); } \ No newline at end of file diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index 18c2ec55be..4e0b6b17fa 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -1,4 +1,4 @@ -import fetch from 'node-fetch'; +import fetch from "node-fetch"; import {Page} from "@playwright/test"; import {registerWithPasskey} from "./register"; import {loginWithPasskey, loginWithPassword} from "./login"; @@ -20,7 +20,7 @@ class User { this.props = userProps; } - async ensure() { + async ensure(page: Page) { await this.remove() const body = { @@ -72,6 +72,10 @@ class User { return } + public setUserId(userId: string) { + this.user = userId + } + public userId() { return this.user; } @@ -84,6 +88,14 @@ class User { return this.props.password; } + public firstname() { + return this.props.firstName + } + + public lastname() { + return this.props.lastName + } + public fullName() { return this.props.firstName + " " + this.props.lastName } @@ -102,6 +114,73 @@ class User { export class PasswordUser extends User { } +enum OtpType { + time = "time-based", + sms = "sms", + email = "email", +} + +export interface otpUserProps { + email: string; + firstName: string; + lastName: string; + organization: string; + type: OtpType, +} + +export class PasswordUserWithOTP extends User { + private type: OtpType + private code: string + + constructor(props: otpUserProps) { + super({ + email: props.email, + firstName: props.firstName, + lastName: props.lastName, + organization: props.organization, + password: "" + }) + this.type = props.type + } + + async ensure(page: Page) { + await super.ensure(page) + + const body = { + username: this.props.email, + organization: { + orgId: this.props.organization + }, + profile: { + givenName: this.props.firstName, + familyName: this.props.lastName, + }, + email: { + email: this.props.email, + isVerified: true, + }, + password: { + password: this.props.password!, + } + } + + const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/human", { + method: 'POST', + body: JSON.stringify(body), + headers: { + 'Content-Type': 'application/json', + 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! + } + }); + if (response.statusCode >= 400 && response.statusCode != 409) { + const error = 'HTTP Error: ' + response.statusCode + ' - ' + response.statusMessage; + console.error(error); + throw new Error(error); + } + return + } +} + export interface passkeyUserProps { email: string; firstName: string; @@ -109,67 +188,58 @@ export interface passkeyUserProps { organization: string; } -export class PasskeyUser { - private props: passkeyUserProps - +export class PasskeyUser extends User { constructor(props: passkeyUserProps) { - this.props = props + super({ + email: props.email, + firstName: props.firstName, + lastName: props.lastName, + organization: props.organization, + password: "" + }) } - async ensurePasskey(page: Page) { - await registerWithPasskey(page, this.props.firstName, this.props.lastName, this.props.email) + public async ensure(page: Page) { + await this.remove() + await registerWithPasskey(page, this.firstname(), this.lastname(), this.username()) } public async login(page: Page) { - await loginWithPasskey(page, this.props.email) + await loginWithPasskey(page, this.username()) } - public fullName() { - return this.props.firstName + " " + this.props.lastName - } - - async ensurePasskeyRegister() { - const url = new URL(process.env.ZITADEL_API_URL!) - const registerBody = { - domain: url.hostname, + public async remove() { + const resp = await getUserByUsername(this.username()) + if (!resp || !resp.result || !resp.result[0]) { + return } - const userId = "" - const registerResponse = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + userId + "/passkeys", { - method: 'POST', - body: JSON.stringify(registerBody), - headers: { - 'Content-Type': 'application/json', - 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! + this.setUserId(resp.result[0].userId) + await super.remove() + } +} + +async function getUserByUsername(username: string) { + const listUsersBody = { + queries: [{ + userNameQuery: { + userName: username, } - }); - if (registerResponse.statusCode >= 400 && registerResponse.statusCode != 409) { - const error = 'HTTP Error: ' + registerResponse.statusCode + ' - ' + registerResponse.statusMessage; - console.error(error); - throw new Error(error); - } - const respJson = await registerResponse.json() - return respJson + }] } - - async ensurePasskeyVerify(passkeyId: string, credential: Credential) { - const verifyBody = { - publicKeyCredential: credential, - passkeyName: "passkey", + const jsonBody = JSON.stringify(listUsersBody) + const registerResponse = await fetch(process.env.ZITADEL_API_URL! + "/v2/users", { + method: 'POST', + body: jsonBody, + headers: { + 'Content-Type': 'application/json', + 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! } - const userId = "" - const verifyResponse = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + userId + "/passkeys/" + passkeyId, { - method: 'POST', - body: JSON.stringify(verifyBody), - headers: { - 'Content-Type': 'application/json', - 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! - } - }); - if (verifyResponse.statusCode >= 400 && verifyResponse.statusCode != 409) { - const error = 'HTTP Error: ' + verifyResponse.statusCode + ' - ' + verifyResponse.statusMessage; - console.error(error); - throw new Error(error); - } - return + }); + if (registerResponse.statusCode >= 400) { + const error = 'HTTP Error: ' + registerResponse.statusCode + ' - ' + registerResponse.statusMessage; + console.error(error); + throw new Error(error); } + const respJson = await registerResponse.json() + return respJson } \ No newline at end of file diff --git a/acceptance/tests/username-passkey.spec.ts b/acceptance/tests/username-passkey.spec.ts index 0cc98851d9..405a7590b1 100644 --- a/acceptance/tests/username-passkey.spec.ts +++ b/acceptance/tests/username-passkey.spec.ts @@ -1,5 +1,7 @@ +import {test as base} from "@playwright/test"; import path from 'path'; import dotenv from 'dotenv'; +import {PasskeyUser} from "./user"; // Read from ".env" file. dotenv.config({path: path.resolve(__dirname, '.env.local')}); @@ -86,7 +88,7 @@ const test = base.extend<{ user: PasskeyUser }>({ enabled: false, }); }, -}); +});*/ const test = base.extend<{ user: PasskeyUser }>({ user: async ({page}, use) => { @@ -96,7 +98,7 @@ const test = base.extend<{ user: PasskeyUser }>({ lastName: "last", organization: "", }); - await user.ensurePasskey(page); + await user.ensure(page); await use(user) }, }); @@ -105,4 +107,3 @@ test("username and passkey login", async ({user, page}) => { await user.login(page) await page.getByRole("heading", {name: "Welcome " + user.fullName() + "!"}).click(); }); -*/ \ No newline at end of file diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index a3e5343672..e9ade80b12 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -15,7 +15,7 @@ const test = base.extend<{ user: PasswordUser }>({ password: "Password1!", organization: "", }); - await user.ensure(); + await user.ensure(page); await use(user); }, }); diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index f0bb8bddb3..e840382319 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -15,7 +15,7 @@ const test = base.extend<{ user: PasswordUser }>({ password: "Password1!", organization: "", }); - await user.ensure(); + await user.ensure(page); await use(user); }, }); diff --git a/apps/login/src/components/change-password-form.tsx b/apps/login/src/components/change-password-form.tsx index ccdcd79281..c025bfc607 100644 --- a/apps/login/src/components/change-password-form.tsx +++ b/apps/login/src/components/change-password-form.tsx @@ -140,6 +140,7 @@ export function ChangePasswordForm({ })} label="New Password" error={errors.password?.message as string} + data-testid="password-text-input" />
    @@ -152,6 +153,7 @@ export function ChangePasswordForm({ })} label="Confirm Password" error={errors.confirmPassword?.message as string} + data-testid="password-confirm-text-input" />
    @@ -167,7 +169,7 @@ export function ChangePasswordForm({ {error && {error}}
    - + @@ -263,6 +264,7 @@ export function LoginPasskey({ setLoading(false); }); }} + data-testid="submit-button" > {loading && } {t("verify.submit")} diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index 98b0ec853f..d2ec163c13 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -120,6 +120,7 @@ export function PasswordForm({ autoComplete="password" {...register("password", { required: "This field is required" })} label="Password" + data-testid="password-text-input" /> {!loginSettings?.hidePasswordReset && (
    @@ -161,6 +162,7 @@ export function SetRegisterPasswordForm({ })} label="Confirm Password" error={errors.confirmPassword?.message as string} + data-testid="password-confirm-text-input" />
    @@ -187,6 +189,7 @@ export function SetRegisterPasswordForm({ watchPassword !== watchConfirmPassword } onClick={handleSubmit(submitRegister)} + data-testid="submit-button" > {loading && } {t("password.submit")} diff --git a/apps/login/src/components/totp-register.tsx b/apps/login/src/components/totp-register.tsx index 450cb5bfa7..52991d0d6a 100644 --- a/apps/login/src/components/totp-register.tsx +++ b/apps/login/src/components/totp-register.tsx @@ -122,6 +122,7 @@ export function TotpRegister({ type="text" {...register("code", { required: "This field is required" })} label="Code" + data-testid="code-text-input" />
    @@ -139,6 +140,7 @@ export function TotpRegister({ variant={ButtonVariants.Primary} disabled={loading || !formState.isValid} onClick={handleSubmit(continueWithCode)} + data-testid="submit-button" > {loading && } {t("set.submit")} diff --git a/apps/login/src/components/username-form.tsx b/apps/login/src/components/username-form.tsx index 80780a8ede..f0766d77f6 100644 --- a/apps/login/src/components/username-form.tsx +++ b/apps/login/src/components/username-form.tsx @@ -80,6 +80,7 @@ export function UsernameForm({ autoComplete="username" {...register("loginName", { required: "This field is required" })} label="Loginname" + data-testid="username-text-input" /> {allowRegister && ( @@ -112,9 +114,10 @@ export function UsernameForm({
    {children}
    - +
    @@ -125,6 +126,7 @@ export function VerifyEmailForm({ type="button" onClick={() => resendCode()} variant={ButtonVariants.Secondary} + data-testid="resend-button" > {t("resendCode")} @@ -135,6 +137,7 @@ export function VerifyEmailForm({ variant={ButtonVariants.Primary} disabled={loading || !formState.isValid} onClick={handleSubmit(submitCodeAndContinue)} + data-testid="submit-button" > {loading && } {t("submit")} diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 052f4dbd25..3ff47cd358 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -34,20 +34,25 @@ export async function registerUser(command: RegisterUserCommand) { return { error: "Could not create user" }; } - const checks = create(ChecksSchema, { - user: { search: { case: "userId", value: human.userId } }, - password: { password: command.password }, - }); - - return createSessionAndUpdateCookie( - checks, - undefined, - command.authRequestId, - ).then((session) => { - return { - userId: human.userId, - sessionId: session.id, - factors: session.factors, - }; - }); + let checks = create(ChecksSchema, { + user: {search: {case: "userId", value: human.userId}}, + }); + if (command.password) { + checks = create(ChecksSchema, { + user: {search: {case: "userId", value: human.userId}}, + password: {password: command.password} + }); + } + console.log(checks) + return createSessionAndUpdateCookie( + checks, + undefined, + command.authRequestId, + ).then((session) => { + return { + userId: human.userId, + sessionId: session.id, + factors: session.factors, + }; + }); } From be4a20b29b5879cb69861cb73176ee8b1b81f567 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:53:23 +0100 Subject: [PATCH 372/640] chore: passkey register --- acceptance/tests/admin.spec.ts | 4 +- acceptance/tests/login.ts | 19 ++-- acceptance/tests/loginname.ts | 5 ++ acceptance/tests/passkey.ts | 62 +++++++++++++ acceptance/tests/password.ts | 11 ++- acceptance/tests/register.spec.ts | 28 +++++- acceptance/tests/register.ts | 13 ++- acceptance/tests/user.ts | 39 +-------- acceptance/tests/username-passkey.spec.ts | 87 +------------------ .../tests/username-password-changed.spec.ts | 2 + acceptance/tests/username-password.spec.ts | 3 +- acceptance/tests/zitadel.ts | 50 +++++++++++ .../authentication-method-radio.tsx | 1 + .../components/set-register-password-form.tsx | 2 +- apps/login/src/lib/server/password.ts | 2 - apps/login/src/lib/server/register.ts | 1 - playwright.config.ts | 2 +- 17 files changed, 178 insertions(+), 153 deletions(-) create mode 100644 acceptance/tests/loginname.ts create mode 100644 acceptance/tests/passkey.ts create mode 100644 acceptance/tests/zitadel.ts diff --git a/acceptance/tests/admin.spec.ts b/acceptance/tests/admin.spec.ts index e08d7bf82b..929e2a5be4 100644 --- a/acceptance/tests/admin.spec.ts +++ b/acceptance/tests/admin.spec.ts @@ -1,7 +1,7 @@ import {test} from "@playwright/test"; -import {loginWithPassword} from "./login"; +import {checkLogin, loginWithPassword} from "./login"; test("admin login", async ({page}) => { await loginWithPassword(page, "zitadel-admin@zitadel.localhost", "Password1.") - await page.getByRole("heading", {name: "Welcome ZITADEL Admin!"}).click(); + await checkLogin(page, "ZITADEL Admin"); }); diff --git a/acceptance/tests/login.ts b/acceptance/tests/login.ts index 20d27e69f9..58d8572f50 100644 --- a/acceptance/tests/login.ts +++ b/acceptance/tests/login.ts @@ -1,4 +1,7 @@ -import {Page} from "@playwright/test"; +import {expect, Page} from "@playwright/test"; +import {loginnameScreen} from "./loginname"; +import {passwordScreen} from "./password"; +import {passkeyScreen} from "./passkey"; export async function loginWithPassword(page: Page, username: string, password: string) { await page.goto("/loginname"); @@ -8,17 +11,13 @@ export async function loginWithPassword(page: Page, username: string, password: await page.getByTestId("submit-button").click() } -export async function loginnameScreen(page: Page, username: string) { - await page.getByTestId("username-text-input").pressSequentially(username); -} - -export async function passwordScreen(page: Page, password: string) { - await page.getByTestId("password-text-input").pressSequentially(password); -} - export async function loginWithPasskey(page: Page, username: string) { await page.goto("/loginname"); await loginnameScreen(page, username) await page.getByTestId("submit-button").click() - await page.getByTestId("submit-button").click() + await passkeyScreen(page) +} + +export async function checkLogin(page: Page, fullName: string) { + await expect(page.getByRole('heading')).toContainText(fullName); } \ No newline at end of file diff --git a/acceptance/tests/loginname.ts b/acceptance/tests/loginname.ts new file mode 100644 index 0000000000..eb490fbb61 --- /dev/null +++ b/acceptance/tests/loginname.ts @@ -0,0 +1,5 @@ +import {Page} from "@playwright/test"; + +export async function loginnameScreen(page: Page, username: string) { + await page.getByTestId("username-text-input").pressSequentially(username); +} diff --git a/acceptance/tests/passkey.ts b/acceptance/tests/passkey.ts new file mode 100644 index 0000000000..92957cdd8c --- /dev/null +++ b/acceptance/tests/passkey.ts @@ -0,0 +1,62 @@ +import {Page} from "@playwright/test"; + +const BASE64_ENCODED_PK = + "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDbBOu5Lhs4vpowbCnmCyLUpIE7JM9sm9QXzye2G+jr+Kr" + + "MsinWohEce47BFPJlTaDzHSvOW2eeunBO89ZcvvVc8RLz4qyQ8rO98xS1jtgqi1NcBPETDrtzthODu/gd0sjB2Tk3TLuBGV" + + "oPXt54a+Oo4JbBJ6h3s0+5eAfGplCbSNq6hN3Jh9YOTw5ZA6GCEy5l8zBaOgjXytd2v2OdSVoEDNiNQRkjJd2rmS2oi9AyQ" + + "FR3B7BrPSiDlCcITZFOWgLF5C31Wp/PSHwQhlnh7/6YhnE2y9tzsUvzx0wJXrBADW13+oMxrneDK3WGbxTNYgIi1PvSqXlq" + + "GjHtCK+R2QkXAgMBAAECggEAVc6bu7VAnP6v0gDOeX4razv4FX/adCao9ZsHZ+WPX8PQxtmWYqykH5CY4TSfsuizAgyPuQ0" + + "+j4Vjssr9VODLqFoanspT6YXsvaKanncUYbasNgUJnfnLnw3an2XpU2XdmXTNYckCPRX9nsAAURWT3/n9ljc/XYY22ecYxM" + + "8sDWnHu2uKZ1B7M3X60bQYL5T/lVXkKdD6xgSNLeP4AkRx0H4egaop68hoW8FIwmDPVWYVAvo8etzWCtibRXz5FcNld9MgD" + + "/Ai7ycKy4Q1KhX5GBFI79MVVaHkSQfxPHpr7/XcmpQOEAr+BMPon4s4vnKqAGdGB3j/E3d/+4F2swykoQKBgQD8hCsp6FIQ" + + "5umJlk9/j/nGsMl85LgLaNVYpWlPRKPc54YNumtvj5vx1BG+zMbT7qIE3nmUPTCHP7qb5ERZG4CdMCS6S64/qzZEqijLCqe" + + "pwj6j4fV5SyPWEcpxf6ehNdmcfgzVB3Wolfwh1ydhx/96L1jHJcTKchdJJzlfTvq8wwKBgQDeCnKws1t5GapfE1rmC/h4ol" + + "L2qZTth9oQmbrXYohVnoqNFslDa43ePZwL9Jmd9kYb0axOTNMmyrP0NTj41uCfgDS0cJnNTc63ojKjegxHIyYDKRZNVUR/d" + + "xAYB/vPfBYZUS7M89pO6LLsHhzS3qpu3/hppo/Uc/AM/r8PSflNHQKBgDnWgBh6OQncChPUlOLv9FMZPR1ZOfqLCYrjYEqi" + + "uzGm6iKM13zXFO4AGAxu1P/IAd5BovFcTpg79Z8tWqZaUUwvscnl+cRlj+mMXAmdqCeO8VASOmqM1ml667axeZDIR867ZG8" + + "K5V029Wg+4qtX5uFypNAAi6GfHkxIKrD04yOHAoGACdh4wXESi0oiDdkz3KOHPwIjn6BhZC7z8mx+pnJODU3cYukxv3WTct" + + "lUhAsyjJiQ/0bK1yX87ulqFVgO0Knmh+wNajrb9wiONAJTMICG7tiWJOm7fW5cfTJwWkBwYADmkfTRmHDvqzQSSvoC2S7aa" + + "9QulbC3C/qgGFNrcWgcT9kCgYAZTa1P9bFCDU7hJc2mHwJwAW7/FQKEJg8SL33KINpLwcR8fqaYOdAHWWz636osVEqosRrH" + + "zJOGpf9x2RSWzQJ+dq8+6fACgfFZOVpN644+sAHfNPAI/gnNKU5OfUv+eav8fBnzlf1A3y3GIkyMyzFN3DE7e0n/lyqxE4H" + + "BYGpI8g=="; + +export async function passkeyScreen(page: Page) { + const client = await page.context().newCDPSession(page); + await client.send('WebAuthn.enable', {enableUI: true}); + const result = await client.send('WebAuthn.addVirtualAuthenticator', { + options: { + protocol: 'ctap2', + transport: 'internal', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + automaticPresenceSimulation: true, + }, + }); + const authenticatorId = result.authenticatorId; + + // initialize event listeners to wait for a successful passkey input event + const operationCompleted = new Promise(resolve => { + client.on('WebAuthn.credentialAdded', () => resolve()); + client.on('WebAuthn.credentialAsserted', () => resolve()); + }); + + const url = new URL(process.env.ZITADEL_API_URL!) + await client.send('WebAuthn.addCredential', { + credential: { + credentialId: "", + rpId: url.hostname, + privateKey: BASE64_ENCODED_PK, + isResidentCredential: false, + signCount: 0, + }, + authenticatorId: authenticatorId + }); + + + // triggers passkey check + await page.getByTestId("submit-button").click() + + // wait for successfully verified + await operationCompleted; +} \ No newline at end of file diff --git a/acceptance/tests/password.ts b/acceptance/tests/password.ts index 42d7190a10..7d2286d858 100644 --- a/acceptance/tests/password.ts +++ b/acceptance/tests/password.ts @@ -9,4 +9,13 @@ export async function changePassword(page: Page, loginname: string, password: st async function changePasswordScreen(page: Page, password1: string, password2: string) { await page.getByTestId('password-text-input').pressSequentially(password1); await page.getByTestId('password-confirm-text-input').pressSequentially(password2); -} \ No newline at end of file +} + +export async function passwordScreen(page: Page, password: string) { + await page.getByTestId("password-text-input").pressSequentially(password); +} + +export async function registerPasswordScreen(page: Page, password1: string, password2: string) { + await page.getByTestId('password-text-input').pressSequentially(password1); + await page.getByTestId('password-confirm-text-input').pressSequentially(password2); +} diff --git a/acceptance/tests/register.spec.ts b/acceptance/tests/register.spec.ts index fe6cef1522..c482dd6874 100644 --- a/acceptance/tests/register.spec.ts +++ b/acceptance/tests/register.spec.ts @@ -1,10 +1,30 @@ import {test} from "@playwright/test"; -import {registerWithPassword} from './register'; -import {loginWithPassword} from "./login"; +import {registerWithPasskey, registerWithPassword} from './register'; +import {checkLogin} from "./login"; +import {removeUserByUsername} from './zitadel'; +import path from 'path'; +import dotenv from 'dotenv'; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); test("register with password", async ({page}) => { const username = "register@example.com" const password = "Password1!" - await registerWithPassword(page, "firstname", "lastname", username, password, password) - await loginWithPassword(page, username, password) + const firstname = "firstname" + const lastname = "lastname" + + await removeUserByUsername(username) + await registerWithPassword(page, firstname, lastname, username, password, password) + await checkLogin(page, firstname + " " + lastname); +}); + +test("register with passkey", async ({page}) => { + const username = "register@example.com" + const firstname = "firstname" + const lastname = "lastname" + + await removeUserByUsername(username) + await registerWithPasskey(page, firstname, lastname, username) + await checkLogin(page, firstname + " " + lastname); }); diff --git a/acceptance/tests/register.ts b/acceptance/tests/register.ts index 9700e9b2a1..e045588f1d 100644 --- a/acceptance/tests/register.ts +++ b/acceptance/tests/register.ts @@ -1,4 +1,6 @@ import {Page} from "@playwright/test"; +import {passkeyScreen} from './passkey'; +import {registerPasswordScreen} from './password'; export async function registerWithPassword(page: Page, firstname: string, lastname: string, email: string, password1: string, password2: string) { await page.goto('/register'); @@ -10,24 +12,19 @@ export async function registerWithPassword(page: Page, firstname: string, lastna async function registerUserScreenPassword(page: Page, firstname: string, lastname: string, email: string) { await registerUserScreen(page, firstname, lastname, email) - await page.getByLabel('Password').click(); -} - -async function registerPasswordScreen(page: Page, password1: string, password2: string) { - await page.getByTestId('password-text-input').fill(password1); - await page.getByTestId('password-confirm-text-input').fill(password2); + await page.getByTestId('Password-radio').click(); } export async function registerWithPasskey(page: Page, firstname: string, lastname: string, email: string) { await page.goto('/register'); await registerUserScreenPasskey(page, firstname, lastname, email) await page.getByTestId('submit-button').click(); - await page.getByTestId('submit-button').click(); + await passkeyScreen(page) } async function registerUserScreenPasskey(page: Page, firstname: string, lastname: string, email: string) { await registerUserScreen(page, firstname, lastname, email) - await page.getByLabel('Passkey').click(); + await page.getByTestId('Passkeys-radio').click(); } async function registerUserScreen(page: Page, firstname: string, lastname: string, email: string) { diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index 4e0b6b17fa..a2a18787b1 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -3,6 +3,7 @@ import {Page} from "@playwright/test"; import {registerWithPasskey} from "./register"; import {loginWithPasskey, loginWithPassword} from "./login"; import {changePassword} from "./password"; +import {removeUser, getUserByUsername} from './zitadel'; export interface userProps { email: string; @@ -58,17 +59,7 @@ class User { } async remove() { - const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + this.userId(), { - method: 'DELETE', - headers: { - 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! - } - }); - if (response.statusCode >= 400 && response.statusCode != 404) { - const error = 'HTTP Error: ' + response.statusCode + ' - ' + response.statusMessage; - console.error(error); - throw new Error(error); - } + await removeUser(this.userId()) return } @@ -217,29 +208,3 @@ export class PasskeyUser extends User { await super.remove() } } - -async function getUserByUsername(username: string) { - const listUsersBody = { - queries: [{ - userNameQuery: { - userName: username, - } - }] - } - const jsonBody = JSON.stringify(listUsersBody) - const registerResponse = await fetch(process.env.ZITADEL_API_URL! + "/v2/users", { - method: 'POST', - body: jsonBody, - headers: { - 'Content-Type': 'application/json', - 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! - } - }); - if (registerResponse.statusCode >= 400) { - const error = 'HTTP Error: ' + registerResponse.statusCode + ' - ' + registerResponse.statusMessage; - console.error(error); - throw new Error(error); - } - const respJson = await registerResponse.json() - return respJson -} \ No newline at end of file diff --git a/acceptance/tests/username-passkey.spec.ts b/acceptance/tests/username-passkey.spec.ts index 405a7590b1..bf56f61d63 100644 --- a/acceptance/tests/username-passkey.spec.ts +++ b/acceptance/tests/username-passkey.spec.ts @@ -2,94 +2,11 @@ import {test as base} from "@playwright/test"; import path from 'path'; import dotenv from 'dotenv'; import {PasskeyUser} from "./user"; +import {checkLogin} from "./login"; // Read from ".env" file. dotenv.config({path: path.resolve(__dirname, '.env.local')}); -/* -const BASE64_ENCODED_PK = - "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDbBOu5Lhs4vpowbCnmCyLUpIE7JM9sm9QXzye2G+jr+Kr" + - "MsinWohEce47BFPJlTaDzHSvOW2eeunBO89ZcvvVc8RLz4qyQ8rO98xS1jtgqi1NcBPETDrtzthODu/gd0sjB2Tk3TLuBGV" + - "oPXt54a+Oo4JbBJ6h3s0+5eAfGplCbSNq6hN3Jh9YOTw5ZA6GCEy5l8zBaOgjXytd2v2OdSVoEDNiNQRkjJd2rmS2oi9AyQ" + - "FR3B7BrPSiDlCcITZFOWgLF5C31Wp/PSHwQhlnh7/6YhnE2y9tzsUvzx0wJXrBADW13+oMxrneDK3WGbxTNYgIi1PvSqXlq" + - "GjHtCK+R2QkXAgMBAAECggEAVc6bu7VAnP6v0gDOeX4razv4FX/adCao9ZsHZ+WPX8PQxtmWYqykH5CY4TSfsuizAgyPuQ0" + - "+j4Vjssr9VODLqFoanspT6YXsvaKanncUYbasNgUJnfnLnw3an2XpU2XdmXTNYckCPRX9nsAAURWT3/n9ljc/XYY22ecYxM" + - "8sDWnHu2uKZ1B7M3X60bQYL5T/lVXkKdD6xgSNLeP4AkRx0H4egaop68hoW8FIwmDPVWYVAvo8etzWCtibRXz5FcNld9MgD" + - "/Ai7ycKy4Q1KhX5GBFI79MVVaHkSQfxPHpr7/XcmpQOEAr+BMPon4s4vnKqAGdGB3j/E3d/+4F2swykoQKBgQD8hCsp6FIQ" + - "5umJlk9/j/nGsMl85LgLaNVYpWlPRKPc54YNumtvj5vx1BG+zMbT7qIE3nmUPTCHP7qb5ERZG4CdMCS6S64/qzZEqijLCqe" + - "pwj6j4fV5SyPWEcpxf6ehNdmcfgzVB3Wolfwh1ydhx/96L1jHJcTKchdJJzlfTvq8wwKBgQDeCnKws1t5GapfE1rmC/h4ol" + - "L2qZTth9oQmbrXYohVnoqNFslDa43ePZwL9Jmd9kYb0axOTNMmyrP0NTj41uCfgDS0cJnNTc63ojKjegxHIyYDKRZNVUR/d" + - "xAYB/vPfBYZUS7M89pO6LLsHhzS3qpu3/hppo/Uc/AM/r8PSflNHQKBgDnWgBh6OQncChPUlOLv9FMZPR1ZOfqLCYrjYEqi" + - "uzGm6iKM13zXFO4AGAxu1P/IAd5BovFcTpg79Z8tWqZaUUwvscnl+cRlj+mMXAmdqCeO8VASOmqM1ml667axeZDIR867ZG8" + - "K5V029Wg+4qtX5uFypNAAi6GfHkxIKrD04yOHAoGACdh4wXESi0oiDdkz3KOHPwIjn6BhZC7z8mx+pnJODU3cYukxv3WTct" + - "lUhAsyjJiQ/0bK1yX87ulqFVgO0Knmh+wNajrb9wiONAJTMICG7tiWJOm7fW5cfTJwWkBwYADmkfTRmHDvqzQSSvoC2S7aa" + - "9QulbC3C/qgGFNrcWgcT9kCgYAZTa1P9bFCDU7hJc2mHwJwAW7/FQKEJg8SL33KINpLwcR8fqaYOdAHWWz636osVEqosRrH" + - "zJOGpf9x2RSWzQJ+dq8+6fACgfFZOVpN644+sAHfNPAI/gnNKU5OfUv+eav8fBnzlf1A3y3GIkyMyzFN3DE7e0n/lyqxE4H" + - "BYGpI8g=="; - -const test = base.extend<{ user: PasskeyUser }>({ - user: async ({page}, use) => { - - // Initialize a CDP session for the current page - const client = await page.context().newCDPSession(page); - // Enable WebAuthn environment in this session - await client.send('WebAuthn.enable', {enableUI: true}); - - // Attach a virtual authenticator with specific options - const result = await client.send('WebAuthn.addVirtualAuthenticator', { - options: { - protocol: 'ctap2', - transport: 'usb', - hasResidentKey: true, - hasUserVerification: true, - isUserVerified: true, - }, - }); - const authenticatorId = result.authenticatorId; - - const url = new URL(process.env.ZITADEL_API_URL!) - await client.send('WebAuthn.addCredential', { - credential: { - credentialId: "", - rpId: url.hostname, - privateKey: BASE64_ENCODED_PK, - isResidentCredential: false, - signCount: 0, - }, - authenticatorId: authenticatorId - }); - - await client.send('WebAuthn.setUserVerified', { - authenticatorId: authenticatorId, - isUserVerified: true, - }); - await client.send('WebAuthn.setAutomaticPresenceSimulation', { - authenticatorId: authenticatorId, - enabled: true, - }); - - const user = new PasskeyUser({ - email: "password@example.com", - firstName: "first", - lastName: "last", - organization: "", - }); - await user.ensure(); - const respJson = await user.ensurePasskeyRegister(); - - const credential = await navigator.credentials.create({ - publicKey: respJson.publicKeyCredentialCreationOptions - }); - - await user.ensurePasskeyVerify(respJson.passkeyId, respJson.publicKeyCredentialCreationOptions) - use(user); - await client.send('WebAuthn.setAutomaticPresenceSimulation', { - authenticatorId, - enabled: false, - }); - }, -});*/ - const test = base.extend<{ user: PasskeyUser }>({ user: async ({page}, use) => { const user = new PasskeyUser({ @@ -105,5 +22,5 @@ const test = base.extend<{ user: PasskeyUser }>({ test("username and passkey login", async ({user, page}) => { await user.login(page) - await page.getByRole("heading", {name: "Welcome " + user.fullName() + "!"}).click(); + await checkLogin(page, user.fullName()); }); diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index e9ade80b12..0607ad15f7 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -2,6 +2,7 @@ import {test as base} from "@playwright/test"; import {PasswordUser} from './user'; import path from 'path'; import dotenv from 'dotenv'; +import {checkLogin} from "./login"; // Read from ".env" file. dotenv.config({path: path.resolve(__dirname, '.env.local')}); @@ -23,4 +24,5 @@ const test = base.extend<{ user: PasswordUser }>({ test("username and password changed login", async ({user, page}) => { await user.changePassword(page, "ChangedPw1!") await user.login(page) + await checkLogin(page, user.fullName()); }); diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index e840382319..e24e4b50ee 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -2,6 +2,7 @@ import {test as base} from "@playwright/test"; import {PasswordUser} from './user'; import path from 'path'; import dotenv from 'dotenv'; +import {checkLogin} from "./login"; // Read from ".env" file. dotenv.config({path: path.resolve(__dirname, '.env.local')}); @@ -22,7 +23,7 @@ const test = base.extend<{ user: PasswordUser }>({ test("username and password login", async ({user, page}) => { await user.login(page) - await page.getByRole("heading", {name: "Welcome " + user.fullName() + "!"}).click(); + await checkLogin(page, user.fullName()); }); diff --git a/acceptance/tests/zitadel.ts b/acceptance/tests/zitadel.ts new file mode 100644 index 0000000000..8a1935ad57 --- /dev/null +++ b/acceptance/tests/zitadel.ts @@ -0,0 +1,50 @@ +import fetch from "node-fetch"; + +export async function removeUserByUsername(username: string) { + const resp = await getUserByUsername(username) + if (!resp || !resp.result || !resp.result[0]) { + return + } + await removeUser(resp.result[0].userId) +} + +export async function removeUser(id: string) { + const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + id, { + method: 'DELETE', + headers: { + 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! + } + }); + if (response.statusCode >= 400 && response.statusCode != 404) { + const error = 'HTTP Error: ' + response.statusCode + ' - ' + response.statusMessage; + console.error(error); + throw new Error(error); + } + return +} + +export async function getUserByUsername(username: string) { + const listUsersBody = { + queries: [{ + userNameQuery: { + userName: username, + } + }] + } + const jsonBody = JSON.stringify(listUsersBody) + const registerResponse = await fetch(process.env.ZITADEL_API_URL! + "/v2/users", { + method: 'POST', + body: jsonBody, + headers: { + 'Content-Type': 'application/json', + 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! + } + }); + if (registerResponse.statusCode >= 400) { + const error = 'HTTP Error: ' + registerResponse.statusCode + ' - ' + registerResponse.statusMessage; + console.error(error); + throw new Error(error); + } + const respJson = await registerResponse.json() + return respJson +} \ No newline at end of file diff --git a/apps/login/src/components/authentication-method-radio.tsx b/apps/login/src/components/authentication-method-radio.tsx index d5c52068ec..b01f8efdec 100644 --- a/apps/login/src/components/authentication-method-radio.tsx +++ b/apps/login/src/components/authentication-method-radio.tsx @@ -30,6 +30,7 @@ export function AuthenticationMethodRadio({ `${ active diff --git a/apps/login/src/components/set-register-password-form.tsx b/apps/login/src/components/set-register-password-form.tsx index 91cc698a8f..551429c9a4 100644 --- a/apps/login/src/components/set-register-password-form.tsx +++ b/apps/login/src/components/set-register-password-form.tsx @@ -178,7 +178,7 @@ export function SetRegisterPasswordForm({ {error && {error}}
    - + +
    diff --git a/apps/login/src/app/global-error.tsx b/apps/login/src/app/global-error.tsx index b790dc3070..0fea5d0117 100644 --- a/apps/login/src/app/global-error.tsx +++ b/apps/login/src/app/global-error.tsx @@ -3,6 +3,7 @@ import { Boundary } from "@/components/boundary"; import { Button } from "@/components/button"; import { ThemeWrapper } from "@/components/theme-wrapper"; +import { useTranslations } from "next-intl"; export default function GlobalError({ error, @@ -11,6 +12,8 @@ export default function GlobalError({ error: Error & { digest?: string }; reset: () => void; }) { + const t = useTranslations("error"); + return ( // global-error must include html and body tags @@ -22,7 +25,7 @@ export default function GlobalError({ Error: {error?.message}
    - +
    diff --git a/apps/login/src/components/register-form-without-password.tsx b/apps/login/src/components/register-form-without-password.tsx index 288ef7133a..14b65704d5 100644 --- a/apps/login/src/components/register-form-without-password.tsx +++ b/apps/login/src/components/register-form-without-password.tsx @@ -67,20 +67,20 @@ export function RegisterFormWithoutPassword({ lastName: values.lastname, organization: organization, authRequestId: authRequestId, - }).catch((error) => { - setError("Could not register user"); - setLoading(false); - return; - }); + }) + .catch(() => { + setError("Could not register user"); + return; + }) + .finally(() => { + setLoading(false); + }); if (response && "error" in response) { setError(response.error); - setLoading(false); return; } - setLoading(false); - return response; } From 771befced7000bc6a65bf591b2e82a3754f13f48 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 11 Nov 2024 10:35:01 +0100 Subject: [PATCH 377/640] simplify --- apps/login/src/components/register-form-without-password.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/login/src/components/register-form-without-password.tsx b/apps/login/src/components/register-form-without-password.tsx index 14b65704d5..52b6c3b9a4 100644 --- a/apps/login/src/components/register-form-without-password.tsx +++ b/apps/login/src/components/register-form-without-password.tsx @@ -173,10 +173,7 @@ export function RegisterFormWithoutPassword({ variant={ButtonVariants.Primary} disabled={loading || !formState.isValid || !tosAndPolicyAccepted} onClick={handleSubmit((values) => - submitAndContinue( - values, - selected.name === methods[0].name ? false : true, - ), + submitAndContinue(values, !(selected.name === methods[0].name)), )} > {loading && } From 7cbdb09939737d755f710e9ccfb5f9134685d56f Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 11 Nov 2024 10:45:32 +0100 Subject: [PATCH 378/640] cleanup loading states --- .../src/components/change-password-form.tsx | 27 ++++++------ apps/login/src/components/idp-signin.tsx | 5 ++- apps/login/src/components/invite-form.tsx | 15 ++++--- apps/login/src/components/login-otp.tsx | 38 ++++++++-------- apps/login/src/components/login-passkey.tsx | 43 +++++++++++-------- apps/login/src/components/password-form.tsx | 29 +++++++------ .../login/src/components/register-passkey.tsx | 22 ++++++---- apps/login/src/components/register-u2f.tsx | 28 ++++++------ apps/login/src/components/session-item.tsx | 11 +++-- .../src/components/set-password-form.tsx | 30 +++++++------ .../components/set-register-password-form.tsx | 12 +++--- .../login/src/components/sign-in-with-idp.tsx | 12 +++--- apps/login/src/components/totp-register.tsx | 5 ++- apps/login/src/components/username-form.tsx | 12 +++--- apps/login/src/components/verify-form.tsx | 29 +++++++------ .../src/components/verify-redirect-button.tsx | 15 ++++--- 16 files changed, 186 insertions(+), 147 deletions(-) diff --git a/apps/login/src/components/change-password-form.tsx b/apps/login/src/components/change-password-form.tsx index 5fc9368ace..b203019a7a 100644 --- a/apps/login/src/components/change-password-form.tsx +++ b/apps/login/src/components/change-password-form.tsx @@ -61,11 +61,13 @@ export function ChangePasswordForm({ const changeResponse = await setMyPassword({ sessionId: sessionId, password: values.password, - }).catch(() => { - setError("Could not change password"); - }); - - setLoading(false); + }) + .catch(() => { + setError("Could not change password"); + }) + .finally(() => { + setLoading(false); + }); if (changeResponse && "error" in changeResponse) { setError(changeResponse.error); @@ -86,13 +88,14 @@ export function ChangePasswordForm({ password: { password: values.password }, }), authRequestId, - }).catch(() => { - setLoading(false); - setError("Could not verify password"); - return; - }); - - setLoading(false); + }) + .catch(() => { + setError("Could not verify password"); + return; + }) + .finally(() => { + setLoading(false); + }); if ( passwordResponse && diff --git a/apps/login/src/components/idp-signin.tsx b/apps/login/src/components/idp-signin.tsx index ebee13ae27..dfed09839f 100644 --- a/apps/login/src/components/idp-signin.tsx +++ b/apps/login/src/components/idp-signin.tsx @@ -60,9 +60,10 @@ export function IdpSignin({ .catch((error) => { setError(error.message); return; + }) + .finally(() => { + setLoading(false); }); - - setLoading(false); }, []); return ( diff --git a/apps/login/src/components/invite-form.tsx b/apps/login/src/components/invite-form.tsx index a8a84e8150..35c0bec028 100644 --- a/apps/login/src/components/invite-form.tsx +++ b/apps/login/src/components/invite-form.tsx @@ -55,13 +55,14 @@ export function InviteForm({ firstName: values.firstname, lastName: values.lastname, organization: organization, - }).catch(() => { - setError("Could not create invitation Code"); - setLoading(false); - return; - }); - - setLoading(false); + }) + .catch(() => { + setError("Could not create invitation Code"); + return; + }) + .finally(() => { + setLoading(false); + }); if (response && typeof response === "object" && "error" in response) { setError(response.error); diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 3d0d190045..7dfed9bfd1 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -57,11 +57,10 @@ export function LoginOTP({ initialized.current = true; setLoading(true); updateSessionForOTPChallenge() - .then((response) => { - setLoading(false); - }) .catch((error) => { setError(error); + }) + .finally(() => { setLoading(false); }); } @@ -89,12 +88,13 @@ export function LoginOTP({ organization, challenges, authRequestId, - }).catch((error) => { - setError(error.message ?? "Could not request OTP challenge"); - setLoading(false); - }); - - setLoading(false); + }) + .catch((error) => { + setError(error.message ?? "Could not request OTP challenge"); + }) + .finally(() => { + setLoading(false); + }); return response; } @@ -139,13 +139,14 @@ export function LoginOTP({ organization, checks, authRequestId, - }).catch(() => { - setError("Could not verify OTP code"); - setLoading(false); - return; - }); - - setLoading(false); + }) + .catch(() => { + setError("Could not verify OTP code"); + return; + }) + .finally(() => { + setLoading(false); + }); return response; } @@ -206,11 +207,10 @@ export function LoginOTP({ onClick={() => { setLoading(true); updateSessionForOTPChallenge() - .then((response) => { - setLoading(false); - }) .catch((error) => { setError(error); + }) + .finally(() => { setLoading(false); }); }} diff --git a/apps/login/src/components/login-passkey.tsx b/apps/login/src/components/login-passkey.tsx index 54898ce835..db4246eda0 100644 --- a/apps/login/src/components/login-passkey.tsx +++ b/apps/login/src/components/login-passkey.tsx @@ -61,16 +61,17 @@ export function LoginPasskey({ } return submitLoginAndContinue(pK) - .then(() => { - setLoading(false); - }) .catch((error) => { setError(error); + }) + .finally(() => { setLoading(false); }); }) .catch((error) => { setError(error); + }) + .finally(() => { setLoading(false); }); } @@ -94,10 +95,13 @@ export function LoginPasskey({ }, }), authRequestId, - }).catch(() => { - setError("Could not request passkey challenge"); - }); - setLoading(false); + }) + .catch(() => { + setError("Could not request passkey challenge"); + }) + .finally(() => { + setLoading(false); + }); return session; } @@ -112,11 +116,13 @@ export function LoginPasskey({ webAuthN: { credentialAssertionData: data }, } as Checks, authRequestId, - }).catch(() => { - setError("Could not verify passkey"); - }); - - setLoading(false); + }) + .catch(() => { + setError("Could not verify passkey"); + }) + .finally(() => { + setLoading(false); + }); return response; } @@ -243,7 +249,9 @@ export function LoginPasskey({ variant={ButtonVariants.Primary} disabled={loading} onClick={async () => { - const response = await updateSessionForChallenge(); + const response = await updateSessionForChallenge().finally(() => { + setLoading(false); + }); const pK = response?.challenges?.webAuthN?.publicKeyCredentialRequestOptions @@ -251,15 +259,16 @@ export function LoginPasskey({ if (!pK) { setError("Could not request passkey challenge"); - setLoading(false); + return; } + setLoading(true); + return submitLoginAndContinue(pK) - .then(() => { - setLoading(false); - }) .catch((error) => { setError(error); + }) + .finally(() => { setLoading(false); }); }} diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index 98b0ec853f..1f0e504af3 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -60,13 +60,14 @@ export function PasswordForm({ }), authRequestId, forceMfa: loginSettings?.forceMfa, - }).catch(() => { - setLoading(false); - setError("Could not verify password"); - return; - }); - - setLoading(false); + }) + .catch(() => { + setError("Could not verify password"); + return; + }) + .finally(() => { + setLoading(false); + }); if (response && "error" in response && response.error) { setError(response.error); @@ -83,12 +84,14 @@ export function PasswordForm({ const response = await resetPassword({ loginName, organization, - }).catch(() => { - setError("Could not reset password"); - return; - }); - - setLoading(false); + }) + .catch(() => { + setError("Could not reset password"); + return; + }) + .finally(() => { + setLoading(false); + }); if (response && "error" in response) { setError(response.error); diff --git a/apps/login/src/components/register-passkey.tsx b/apps/login/src/components/register-passkey.tsx index e067b38743..3ddc0ddc90 100644 --- a/apps/login/src/components/register-passkey.tsx +++ b/apps/login/src/components/register-passkey.tsx @@ -50,11 +50,14 @@ export function RegisterPasskey({ passkeyName, publicKeyCredential, sessionId, - }).catch(() => { - setError("Could not verify Passkey"); - }); + }) + .catch(() => { + setError("Could not verify Passkey"); + }) + .finally(() => { + setLoading(false); + }); - setLoading(false); return response; } @@ -62,10 +65,13 @@ export function RegisterPasskey({ setLoading(true); const resp = await registerPasskeyLink({ sessionId, - }).catch(() => { - setError("Could not register passkey"); - }); - setLoading(false); + }) + .catch(() => { + setError("Could not register passkey"); + }) + .finally(() => { + setLoading(false); + }); if (!resp) { setError("An error on registering passkey"); diff --git a/apps/login/src/components/register-u2f.tsx b/apps/login/src/components/register-u2f.tsx index 2f294ea070..ea0cdc1f58 100644 --- a/apps/login/src/components/register-u2f.tsx +++ b/apps/login/src/components/register-u2f.tsx @@ -47,16 +47,18 @@ export function RegisterU2f({ passkeyName, publicKeyCredential, sessionId, - }).catch(() => { - setError("An error on verifying passkey occurred"); - }); + }) + .catch(() => { + setError("An error on verifying passkey occurred"); + }) + .finally(() => { + setLoading(false); + }); if (response && "error" in response && response?.error) { setError(response?.error); } - setLoading(false); - return response; } @@ -65,11 +67,13 @@ export function RegisterU2f({ setLoading(true); const response = await addU2F({ sessionId, - }).catch(() => { - setError("An error on registering passkey"); - }); - - setLoading(false); + }) + .catch(() => { + setError("An error on registering passkey"); + }) + .finally(() => { + setLoading(false); + }); if (response && "error" in response && response?.error) { setError(response?.error); @@ -115,7 +119,6 @@ export function RegisterU2f({ !(resp as any).rawId ) { setError("An error on registering passkey"); - setLoading(false); return; } @@ -139,7 +142,6 @@ export function RegisterU2f({ const submitResponse = await submitVerify(u2fId, "", data, sessionId); if (!submitResponse) { - setLoading(false); setError("An error on verifying passkey"); return; } @@ -177,8 +179,6 @@ export function RegisterU2f({ router.push(urlToContinue); } - - setLoading(false); } return ( diff --git a/apps/login/src/components/session-item.tsx b/apps/login/src/components/session-item.tsx index a2abfd897a..69ddedf19f 100644 --- a/apps/login/src/components/session-item.tsx +++ b/apps/login/src/components/session-item.tsx @@ -40,11 +40,14 @@ export function SessionItem({ setLoading(true); const response = await cleanupSession({ sessionId: id, - }).catch((error) => { - setError(error.message); - }); + }) + .catch((error) => { + setError(error.message); + }) + .finally(() => { + setLoading(false); + }); - setLoading(false); return response; } diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 1fb3acda90..7bd0b7c488 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -71,13 +71,14 @@ export function SetPasswordForm({ payload = { ...payload, code: values.code }; } - const changeResponse = await changePassword(payload).catch(() => { - setError("Could not set password"); - setLoading(false); - return; - }); - - setLoading(false); + const changeResponse = await changePassword(payload) + .catch(() => { + setError("Could not set password"); + return; + }) + .finally(() => { + setLoading(false); + }); if (changeResponse && "error" in changeResponse) { setError(changeResponse.error); @@ -107,13 +108,14 @@ export function SetPasswordForm({ password: { password: values.password }, }), authRequestId, - }).catch((error) => { - setLoading(false); - setError("Could not verify password"); - return; - }); - - setLoading(false); + }) + .catch(() => { + setError("Could not verify password"); + return; + }) + .finally(() => { + setLoading(false); + }); if ( passwordResponse && diff --git a/apps/login/src/components/set-register-password-form.tsx b/apps/login/src/components/set-register-password-form.tsx index b46711a1b7..22002f5e94 100644 --- a/apps/login/src/components/set-register-password-form.tsx +++ b/apps/login/src/components/set-register-password-form.tsx @@ -65,11 +65,13 @@ export function SetRegisterPasswordForm({ organization: organization, authRequestId: authRequestId, password: values.password, - }).catch(() => { - setError("Could not register user"); - }); - - setLoading(false); + }) + .catch(() => { + setError("Could not register user"); + }) + .finally(() => { + setLoading(false); + }); if (response && "error" in response) { setError(response.error); diff --git a/apps/login/src/components/sign-in-with-idp.tsx b/apps/login/src/components/sign-in-with-idp.tsx index df5b13afe4..90520d449b 100644 --- a/apps/login/src/components/sign-in-with-idp.tsx +++ b/apps/login/src/components/sign-in-with-idp.tsx @@ -53,11 +53,13 @@ export function SignInWithIdp({ `${host}/idp/${provider}/success?` + new URLSearchParams(params), failureUrl: `${host}/idp/${provider}/failure?` + new URLSearchParams(params), - }).catch(() => { - setError("Could not start IDP flow"); - }); - - setLoading(false); + }) + .catch(() => { + setError("Could not start IDP flow"); + }) + .finally(() => { + setLoading(false); + }); return response; } diff --git a/apps/login/src/components/totp-register.tsx b/apps/login/src/components/totp-register.tsx index 450cb5bfa7..c3f9b03e2a 100644 --- a/apps/login/src/components/totp-register.tsx +++ b/apps/login/src/components/totp-register.tsx @@ -51,7 +51,6 @@ export function TotpRegister({ setLoading(true); return verifyTOTP(values.code, loginName, organization) .then((response) => { - setLoading(false); // if attribute is set, validate MFA after it is setup, otherwise proceed as usual (when mfa is enforced to login) if (checkAfter) { const params = new URLSearchParams({}); @@ -96,8 +95,10 @@ export function TotpRegister({ } }) .catch((e) => { - setLoading(false); setError(e.message); + }) + .finally(() => { + setLoading(false); }); } diff --git a/apps/login/src/components/username-form.tsx b/apps/login/src/components/username-form.tsx index 80780a8ede..cd1732210d 100644 --- a/apps/login/src/components/username-form.tsx +++ b/apps/login/src/components/username-form.tsx @@ -52,11 +52,13 @@ export function UsernameForm({ loginName: values.loginName, organization, authRequestId, - }).catch(() => { - setError("An internal error occurred"); - }); - - setLoading(false); + }) + .catch(() => { + setError("An internal error occurred"); + }) + .finally(() => { + setLoading(false); + }); if (res?.error) { setError(res.error); diff --git a/apps/login/src/components/verify-form.tsx b/apps/login/src/components/verify-form.tsx index b43c0eaa42..54976ac32a 100644 --- a/apps/login/src/components/verify-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -41,13 +41,15 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { const response = await resendVerification({ userId, isInvite: isInvite, - }).catch(() => { - setError("Could not resend email"); - setLoading(false); - return; - }); + }) + .catch(() => { + setError("Could not resend email"); + return; + }) + .finally(() => { + setLoading(false); + }); - setLoading(false); return response; } @@ -61,13 +63,14 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { code: value.code, userId, isInvite: isInvite, - }).catch((error) => { - setError("Could not verify user"); - setLoading(false); - return; - }); - - setLoading(false); + }) + .catch(() => { + setError("Could not verify user"); + return; + }) + .finally(() => { + setLoading(false); + }); }, [isInvite, userId], ); diff --git a/apps/login/src/components/verify-redirect-button.tsx b/apps/login/src/components/verify-redirect-button.tsx index 0c09c835d8..4fe313cd1d 100644 --- a/apps/login/src/components/verify-redirect-button.tsx +++ b/apps/login/src/components/verify-redirect-button.tsx @@ -29,13 +29,14 @@ export function VerifyRedirectButton({ await sendVerificationRedirectWithoutCheck({ userId, authRequestId, - }).catch((error) => { - setError("Could not verify user"); - setLoading(false); - return; - }); - - setLoading(false); + }) + .catch((error) => { + setError("Could not verify user"); + return; + }) + .finally(() => { + setLoading(false); + }); } return ( From a88c1144fa33bade81c4a509f23d9fdb953b8e49 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 11 Nov 2024 10:48:34 +0100 Subject: [PATCH 379/640] rm comment --- apps/login/src/lib/server/loginname.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 0f4d392ef2..7081be4d88 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -185,11 +185,6 @@ export async function sendLoginname(command: SendLoginnameCommand) { redirect("/verify?" + paramsVerify); } - // what to do with users with valid email but no auth methods? redirect to /authenticator/set? - // return { - // error: - // "User has no available authentication methods. Contact your administrator to setup authentication for the requested user.", - // }; const paramsAuthenticatorSetup = new URLSearchParams({ loginName: session.factors?.user?.loginName, From a2890877f10a898fdba4cb00fdc09706906c71c6 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 12 Nov 2024 11:55:01 +0100 Subject: [PATCH 380/640] add logs to login handler --- apps/login/src/app/login/route.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index af8a74f54c..41e1e87b8d 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -102,6 +102,7 @@ export async function GET(request: NextRequest) { // works not with _rsc request try { + console.log("create callack for provided session"); const { callbackUrl } = await createCallback( create(CreateCallbackRequestSchema, { authRequestId, @@ -268,6 +269,7 @@ export async function GET(request: NextRequest) { sessionId: cookie?.id, sessionToken: cookie?.token, }; + console.log("create callack with prompt none"); const { callbackUrl } = await createCallback( create(CreateCallbackRequestSchema, { authRequestId, @@ -305,6 +307,8 @@ export async function GET(request: NextRequest) { sessionToken: cookie?.token, }; try { + console.log("create callack - default"); + const { callbackUrl } = await createCallback( create(CreateCallbackRequestSchema, { authRequestId, From 8d969449e2f298f18b085eacec7d7082d1f8f95e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 12 Nov 2024 12:07:31 +0100 Subject: [PATCH 381/640] handle password redirect from browser --- apps/login/src/components/change-password-form.tsx | 6 ++++++ apps/login/src/components/password-form.tsx | 9 +++++++-- apps/login/src/components/set-password-form.tsx | 8 ++++++++ apps/login/src/lib/server/password.ts | 3 ++- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/login/src/components/change-password-form.tsx b/apps/login/src/components/change-password-form.tsx index b203019a7a..0ffd79a7a3 100644 --- a/apps/login/src/components/change-password-form.tsx +++ b/apps/login/src/components/change-password-form.tsx @@ -12,6 +12,7 @@ import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { useTranslations } from "next-intl"; +import { redirect } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; import { Alert } from "./alert"; @@ -103,6 +104,11 @@ export function ChangePasswordForm({ passwordResponse.error ) { setError(passwordResponse.error); + return; + } + + if (passwordResponse && passwordResponse.nextStep) { + return redirect(passwordResponse.nextStep); } return; diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index 1f0e504af3..8621ac8d1c 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -5,7 +5,7 @@ import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { useTranslations } from "next-intl"; -import { useRouter } from "next/navigation"; +import { redirect, useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { Alert, AlertType } from "./alert"; @@ -71,9 +71,14 @@ export function PasswordForm({ if (response && "error" in response && response.error) { setError(response.error); + return; } - return response; + if (response && response.nextStep) { + return redirect(response.nextStep); + } + + return; } async function resetPasswordAndContinue() { diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 7bd0b7c488..e0414b9d58 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -11,6 +11,7 @@ import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { useTranslations } from "next-intl"; +import { redirect } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; import { Alert } from "./alert"; @@ -123,7 +124,14 @@ export function SetPasswordForm({ passwordResponse.error ) { setError(passwordResponse.error); + return; } + + if (passwordResponse && passwordResponse.nextStep) { + return redirect(passwordResponse.nextStep); + } + + return; } const { errors } = formState; diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 2cb512bb97..067517abe2 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -249,7 +249,8 @@ export async function sendPassword(command: UpdateSessionCommand) { params.append("organization", command.organization); } - return redirect(`/login?` + params); + // move this to browser + return { nextStep: `/login?${params}` }; } // without OIDC flow From 9017268e04d747425ac266df4245cf91ed6028e5 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 12 Nov 2024 12:17:16 +0100 Subject: [PATCH 382/640] cleanup password api --- apps/login/src/lib/server/password.ts | 35 +++++++++------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 067517abe2..ce2e410199 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -124,23 +124,11 @@ export async function sendPassword(command: UpdateSessionCommand) { } } - const submitted = { - sessionId: session.id, - factors: session.factors, - challenges: session.challenges, - authMethods, - userState: user.state, - }; - - if ( - !submitted || - !submitted.authMethods || - !submitted.factors?.user?.loginName - ) { + if (!authMethods || !session.factors?.user?.loginName) { return { error: "Could not verify password!" }; } - const availableSecondFactors = submitted?.authMethods?.filter( + const availableSecondFactors = authMethods?.filter( (m: AuthenticationMethodType) => m !== AuthenticationMethodType.PASSWORD && m !== AuthenticationMethodType.PASSKEY, @@ -148,7 +136,7 @@ export async function sendPassword(command: UpdateSessionCommand) { if (availableSecondFactors?.length == 1) { const params = new URLSearchParams({ - loginName: submitted.factors?.user.loginName, + loginName: session.factors?.user.loginName, }); if (command.authRequestId) { @@ -172,7 +160,7 @@ export async function sendPassword(command: UpdateSessionCommand) { } } else if (availableSecondFactors?.length >= 1) { const params = new URLSearchParams({ - loginName: submitted.factors.user.loginName, + loginName: session.factors.user.loginName, }); if (command.authRequestId) { @@ -184,9 +172,9 @@ export async function sendPassword(command: UpdateSessionCommand) { } return redirect(`/mfa?` + params); - } else if (submitted.userState === UserState.INITIAL) { + } else if (user.state === UserState.INITIAL) { const params = new URLSearchParams({ - loginName: submitted.factors.user.loginName, + loginName: session.factors.user.loginName, }); if (command.authRequestId) { @@ -200,7 +188,7 @@ export async function sendPassword(command: UpdateSessionCommand) { return redirect(`/password/change?` + params); } else if (command.forceMfa && !availableSecondFactors.length) { const params = new URLSearchParams({ - loginName: submitted.factors.user.loginName, + loginName: session.factors.user.loginName, force: "true", // this defines if the mfa is forced in the settings checkAfter: "true", // this defines if the check is directly made after the setup }); @@ -239,9 +227,9 @@ export async function sendPassword(command: UpdateSessionCommand) { // return router.push(`/passkey/set?` + params); // } - else if (command.authRequestId && submitted.sessionId) { + else if (command.authRequestId && session.id) { const params = new URLSearchParams({ - sessionId: submitted.sessionId, + sessionId: session.id, authRequest: command.authRequestId, }); @@ -249,7 +237,6 @@ export async function sendPassword(command: UpdateSessionCommand) { params.append("organization", command.organization); } - // move this to browser return { nextStep: `/login?${params}` }; } @@ -257,11 +244,11 @@ export async function sendPassword(command: UpdateSessionCommand) { const params = new URLSearchParams( command.authRequestId ? { - loginName: submitted.factors.user.loginName, + loginName: session.factors.user.loginName, authRequestId: command.authRequestId, } : { - loginName: submitted.factors.user.loginName, + loginName: session.factors.user.loginName, }, ); From 096da5e47ba2a2cc8b7598f4a9977129d000234a Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 12 Nov 2024 14:16:53 +0100 Subject: [PATCH 383/640] log --- apps/login/src/components/password-form.tsx | 2 -- apps/login/src/lib/server/password.ts | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index 8621ac8d1c..27350d5b22 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -77,8 +77,6 @@ export function PasswordForm({ if (response && response.nextStep) { return redirect(response.nextStep); } - - return; } async function resetPasswordAndContinue() { diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index ce2e410199..085fca450b 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -237,6 +237,8 @@ export async function sendPassword(command: UpdateSessionCommand) { params.append("organization", command.organization); } + console.log("nextStep", `/login?${params}`); + return { nextStep: `/login?${params}` }; } From 602dbf55eeeae0021e1120ada423e8fe5671b392 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 12 Nov 2024 14:25:33 +0100 Subject: [PATCH 384/640] router push on client --- apps/login/src/components/password-form.tsx | 4 ++-- apps/login/src/lib/server/password.ts | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index 27350d5b22..897cdb80f1 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -5,7 +5,7 @@ import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { useTranslations } from "next-intl"; -import { redirect, useRouter } from "next/navigation"; +import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { Alert, AlertType } from "./alert"; @@ -75,7 +75,7 @@ export function PasswordForm({ } if (response && response.nextStep) { - return redirect(response.nextStep); + return router.push(response.nextStep); } } diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 085fca450b..ce2e410199 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -237,8 +237,6 @@ export async function sendPassword(command: UpdateSessionCommand) { params.append("organization", command.organization); } - console.log("nextStep", `/login?${params}`); - return { nextStep: `/login?${params}` }; } From 269c8071e2228b45acdaff7fb48a6f3667c18e32 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 12 Nov 2024 14:41:34 +0100 Subject: [PATCH 385/640] load default org on loginname --- apps/login/src/app/(login)/loginname/page.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 928bf1389d..f0873cf393 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -3,11 +3,12 @@ import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { UsernameForm } from "@/components/username-form"; import { getBrandingSettings, - getLegalAndSupportSettings, + getDefaultOrg, getLoginSettings, settingsService, } from "@/lib/zitadel"; import { makeReqCtx } from "@zitadel/client/v2"; +import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { getLocale, getTranslations } from "next-intl/server"; function getIdentityProviders(orgId?: string) { @@ -28,11 +29,22 @@ export default async function Page({ const loginName = searchParams?.loginName; const authRequestId = searchParams?.authRequestId; - const organization = searchParams?.organization; + let organization = searchParams?.organization; const submit: boolean = searchParams?.submit === "true"; + if (!organization) { + const org: Organization | null = await getDefaultOrg().catch((error) => { + console.warn(error); + return null; + }); + if (!org) { + console.warn("No default organization found"); + } else { + organization = org.id; + } + } + const loginSettings = await getLoginSettings(organization); - const legal = await getLegalAndSupportSettings(); const identityProviders = await getIdentityProviders(organization); @@ -55,7 +67,7 @@ export default async function Page({ submit={submit} allowRegister={!!loginSettings?.allowRegister} > - {legal && identityProviders && process.env.ZITADEL_API_URL && ( + {identityProviders && process.env.ZITADEL_API_URL && ( Date: Tue, 12 Nov 2024 15:30:57 +0100 Subject: [PATCH 386/640] loginname / password page to load default org context if no id is provided --- apps/login/src/app/(login)/loginname/page.tsx | 25 +++++++---- apps/login/src/app/(login)/password/page.tsx | 32 +++++++++++--- apps/login/src/lib/server/password.ts | 42 +++++++++++++------ 3 files changed, 73 insertions(+), 26 deletions(-) diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index f0873cf393..b6341a523d 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -29,9 +29,10 @@ export default async function Page({ const loginName = searchParams?.loginName; const authRequestId = searchParams?.authRequestId; - let organization = searchParams?.organization; + const organization = searchParams?.organization; const submit: boolean = searchParams?.submit === "true"; + let defaultOrganization; if (!organization) { const org: Organization | null = await getDefaultOrg().catch((error) => { console.warn(error); @@ -40,19 +41,25 @@ export default async function Page({ if (!org) { console.warn("No default organization found"); } else { - organization = org.id; + defaultOrganization = org.id; } } - const loginSettings = await getLoginSettings(organization); - - const identityProviders = await getIdentityProviders(organization); - const host = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "http://localhost:3000"; - const branding = await getBrandingSettings(organization); + const loginSettings = await getLoginSettings( + organization ?? defaultOrganization, + ); + + const identityProviders = await getIdentityProviders( + organization ?? defaultOrganization, + ); + + const branding = await getBrandingSettings( + organization ?? defaultOrganization, + ); return ( @@ -63,7 +70,7 @@ export default async function Page({ @@ -72,7 +79,7 @@ export default async function Page({ host={host} identityProviders={identityProviders} authRequestId={authRequestId} - organization={organization} + organization={organization ?? defaultOrganization} // use the organization from the searchParams here otherwise fallback to the default organization > )} diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 1f752850e6..222571854a 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -3,7 +3,12 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { PasswordForm } from "@/components/password-form"; import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; -import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; +import { + getBrandingSettings, + getDefaultOrg, + getLoginSettings, +} from "@/lib/zitadel"; +import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { getLocale, getTranslations } from "next-intl/server"; @@ -16,7 +21,20 @@ export default async function Page({ const t = await getTranslations({ locale, namespace: "password" }); const tError = await getTranslations({ locale, namespace: "error" }); - const { loginName, organization, authRequestId, alt } = searchParams; + let { loginName, organization, authRequestId, alt } = searchParams; + + let defaultOrganization; + if (!organization) { + const org: Organization | null = await getDefaultOrg().catch((error) => { + console.warn(error); + return null; + }); + if (!org) { + console.warn("No default organization found"); + } else { + defaultOrganization = org.id; + } + } // also allow no session to be found (ignoreUnkownUsername) let sessionFactors; @@ -30,8 +48,12 @@ export default async function Page({ console.warn(error); } - const branding = await getBrandingSettings(organization); - const loginSettings = await getLoginSettings(organization); + const branding = await getBrandingSettings( + organization ?? defaultOrganization, + ); + const loginSettings = await getLoginSettings( + organization ?? defaultOrganization, + ); return ( @@ -62,7 +84,7 @@ export default async function Page({ Date: Tue, 12 Nov 2024 15:35:13 +0100 Subject: [PATCH 387/640] cleanup logs --- apps/login/src/app/login/route.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 41e1e87b8d..af8a74f54c 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -102,7 +102,6 @@ export async function GET(request: NextRequest) { // works not with _rsc request try { - console.log("create callack for provided session"); const { callbackUrl } = await createCallback( create(CreateCallbackRequestSchema, { authRequestId, @@ -269,7 +268,6 @@ export async function GET(request: NextRequest) { sessionId: cookie?.id, sessionToken: cookie?.token, }; - console.log("create callack with prompt none"); const { callbackUrl } = await createCallback( create(CreateCallbackRequestSchema, { authRequestId, @@ -307,8 +305,6 @@ export async function GET(request: NextRequest) { sessionToken: cookie?.token, }; try { - console.log("create callack - default"); - const { callbackUrl } = await createCallback( create(CreateCallbackRequestSchema, { authRequestId, From eef1728a1867f7a98fb6ba12a79e0a44a31103a6 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 12 Nov 2024 16:54:42 +0100 Subject: [PATCH 388/640] cleanup def loading --- apps/login/src/app/(login)/loginname/page.tsx | 9 ++------- apps/login/src/app/(login)/password/page.tsx | 9 ++------- apps/login/src/app/(login)/register/page.tsx | 9 ++------- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index b6341a523d..68928755d3 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -34,13 +34,8 @@ export default async function Page({ let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg().catch((error) => { - console.warn(error); - return null; - }); - if (!org) { - console.warn("No default organization found"); - } else { + const org: Organization | null = await getDefaultOrg(); + if (org) { defaultOrganization = org.id; } } diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 222571854a..659488043b 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -25,13 +25,8 @@ export default async function Page({ let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg().catch((error) => { - console.warn(error); - return null; - }); - if (!org) { - console.warn("No default organization found"); - } else { + const org: Organization | null = await getDefaultOrg(); + if (org) { defaultOrganization = org.id; } } diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 931cad0e08..f23582ae3f 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -22,13 +22,8 @@ export default async function Page({ searchParams; if (!organization) { - const org: Organization | null = await getDefaultOrg().catch((error) => { - console.warn(error); - return null; - }); - if (!org) { - console.warn("No default organization found"); - } else { + const org: Organization | null = await getDefaultOrg(); + if (org) { organization = org.id; } } From 657418d51bd6089286bc8e32730b551cd4519e40 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 12 Nov 2024 17:02:47 +0100 Subject: [PATCH 389/640] login integration test --- apps/login/cypress/integration/login.cy.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/login/cypress/integration/login.cy.ts b/apps/login/cypress/integration/login.cy.ts index bb83ca375a..f293653af1 100644 --- a/apps/login/cypress/integration/login.cy.ts +++ b/apps/login/cypress/integration/login.cy.ts @@ -2,6 +2,14 @@ import { stub } from "../support/mock"; describe("login", () => { beforeEach(() => { + stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { + data: { + details: { + totalResult: 1, + }, + result: [{ id: "256088834543534543" }], + }, + }); stub("zitadel.session.v2.SessionService", "CreateSession", { data: { details: { From 802c7af991ff469fb9005658ae6093802cc50255 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 02:02:47 +0000 Subject: [PATCH 390/640] Bump the dev group across 1 directory with 24 updates Bumps the dev group with 24 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@changesets/cli](https://github.com/changesets/changesets) | `2.27.8` | `2.27.9` | | [@playwright/test](https://github.com/microsoft/playwright) | `1.48.1` | `1.48.2` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.5.5` | `22.9.0` | | [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `4.3.1` | `4.3.3` | | [prettier-plugin-organize-imports](https://github.com/simonhaenisch/prettier-plugin-organize-imports) | `4.0.0` | `4.1.0` | | [tsup](https://github.com/egoist/tsup) | `8.3.0` | `8.3.5` | | [turbo](https://github.com/vercel/turborepo) | `2.1.2` | `2.2.3` | | [typescript](https://github.com/microsoft/TypeScript) | `5.6.2` | `5.6.3` | | [vite-tsconfig-paths](https://github.com/aleclarson/vite-tsconfig-paths) | `5.0.1` | `5.1.2` | | [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `2.1.1` | `2.1.4` | | [@bufbuild/buf](https://github.com/bufbuild/buf) | `1.41.0` | `1.46.0` | | [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) | `6.5.0` | `6.6.3` | | [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `18.3.7` | `18.3.12` | | [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `18.3.0` | `18.3.1` | | [concurrently](https://github.com/open-cli-tools/concurrently) | `9.0.1` | `9.1.0` | | [cypress](https://github.com/cypress-io/cypress) | `13.14.2` | `13.15.2` | | [del-cli](https://github.com/sindresorhus/del-cli) | `5.1.0` | `6.0.0` | | [jsdom](https://github.com/jsdom/jsdom) | `25.0.0` | `25.0.1` | | [nodemon](https://github.com/remy/nodemon) | `3.1.5` | `3.1.7` | | [postcss](https://github.com/postcss/postcss) | `8.4.47` | `8.4.49` | | [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) | `0.6.6` | `0.6.8` | | [sass](https://github.com/sass/dart-sass) | `1.79.1` | `1.80.7` | | [tailwindcss](https://github.com/tailwindlabs/tailwindcss) | `3.4.12` | `3.4.14` | | [ts-proto](https://github.com/stephenh/ts-proto) | `2.2.0` | `2.2.7` | Updates `@changesets/cli` from 2.27.8 to 2.27.9 - [Release notes](https://github.com/changesets/changesets/releases) - [Changelog](https://github.com/changesets/changesets/blob/main/docs/modifying-changelog-format.md) - [Commits](https://github.com/changesets/changesets/compare/@changesets/cli@2.27.8...@changesets/cli@2.27.9) Updates `@playwright/test` from 1.48.1 to 1.48.2 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.48.1...v1.48.2) Updates `@types/node` from 22.5.5 to 22.9.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@vitejs/plugin-react` from 4.3.1 to 4.3.3 - [Release notes](https://github.com/vitejs/vite-plugin-react/releases) - [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite-plugin-react/commits/v4.3.3/packages/plugin-react) Updates `prettier-plugin-organize-imports` from 4.0.0 to 4.1.0 - [Release notes](https://github.com/simonhaenisch/prettier-plugin-organize-imports/releases) - [Changelog](https://github.com/simonhaenisch/prettier-plugin-organize-imports/blob/master/changelog.md) - [Commits](https://github.com/simonhaenisch/prettier-plugin-organize-imports/compare/v4.0.0...v4.1.0) Updates `tsup` from 8.3.0 to 8.3.5 - [Release notes](https://github.com/egoist/tsup/releases) - [Commits](https://github.com/egoist/tsup/compare/v8.3.0...v8.3.5) Updates `turbo` from 2.1.2 to 2.2.3 - [Release notes](https://github.com/vercel/turborepo/releases) - [Changelog](https://github.com/vercel/turborepo/blob/main/release.md) - [Commits](https://github.com/vercel/turborepo/compare/v2.1.2...v2.2.3) Updates `typescript` from 5.6.2 to 5.6.3 - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.6.2...v5.6.3) Updates `vite-tsconfig-paths` from 5.0.1 to 5.1.2 - [Release notes](https://github.com/aleclarson/vite-tsconfig-paths/releases) - [Commits](https://github.com/aleclarson/vite-tsconfig-paths/compare/v5.0.1...v5.1.2) Updates `vitest` from 2.1.1 to 2.1.4 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.4/packages/vitest) Updates `@bufbuild/buf` from 1.41.0 to 1.46.0 - [Release notes](https://github.com/bufbuild/buf/releases) - [Changelog](https://github.com/bufbuild/buf/blob/main/CHANGELOG.md) - [Commits](https://github.com/bufbuild/buf/compare/v1.41.0...v1.46.0) Updates `@testing-library/jest-dom` from 6.5.0 to 6.6.3 - [Release notes](https://github.com/testing-library/jest-dom/releases) - [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md) - [Commits](https://github.com/testing-library/jest-dom/compare/v6.5.0...v6.6.3) Updates `@types/react` from 18.3.7 to 18.3.12 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `@types/react-dom` from 18.3.0 to 18.3.1 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) Updates `concurrently` from 9.0.1 to 9.1.0 - [Release notes](https://github.com/open-cli-tools/concurrently/releases) - [Commits](https://github.com/open-cli-tools/concurrently/compare/v9.0.1...v9.1.0) Updates `cypress` from 13.14.2 to 13.15.2 - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md) - [Commits](https://github.com/cypress-io/cypress/compare/v13.14.2...v13.15.2) Updates `del-cli` from 5.1.0 to 6.0.0 - [Release notes](https://github.com/sindresorhus/del-cli/releases) - [Commits](https://github.com/sindresorhus/del-cli/compare/v5.1.0...v6.0.0) Updates `jsdom` from 25.0.0 to 25.0.1 - [Release notes](https://github.com/jsdom/jsdom/releases) - [Changelog](https://github.com/jsdom/jsdom/blob/main/Changelog.md) - [Commits](https://github.com/jsdom/jsdom/compare/25.0.0...25.0.1) Updates `nodemon` from 3.1.5 to 3.1.7 - [Release notes](https://github.com/remy/nodemon/releases) - [Commits](https://github.com/remy/nodemon/compare/v3.1.5...v3.1.7) Updates `postcss` from 8.4.47 to 8.4.49 - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.47...8.4.49) Updates `prettier-plugin-tailwindcss` from 0.6.6 to 0.6.8 - [Release notes](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/compare/v0.6.6...v0.6.8) Updates `sass` from 1.79.1 to 1.80.7 - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.79.1...1.80.7) Updates `tailwindcss` from 3.4.12 to 3.4.14 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/v3.4.14/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v3.4.12...v3.4.14) Updates `ts-proto` from 2.2.0 to 2.2.7 - [Release notes](https://github.com/stephenh/ts-proto/releases) - [Changelog](https://github.com/stephenh/ts-proto/blob/main/CHANGELOG.md) - [Commits](https://github.com/stephenh/ts-proto/compare/v2.2.0...v2.2.7) --- updated-dependencies: - dependency-name: "@changesets/cli" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: "@playwright/test" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: "@vitejs/plugin-react" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: prettier-plugin-organize-imports dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: tsup dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: turbo dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: vite-tsconfig-paths dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: vitest dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: "@bufbuild/buf" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: "@testing-library/jest-dom" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: "@types/react" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: "@types/react-dom" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: concurrently dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: cypress dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: del-cli dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev - dependency-name: jsdom dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: nodemon dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: postcss dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: prettier-plugin-tailwindcss dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: sass dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: tailwindcss dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: ts-proto dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev ... Signed-off-by: dependabot[bot] --- apps/login/package.json | 32 +- package.json | 20 +- packages/zitadel-node/package.json | 2 +- packages/zitadel-proto/package.json | 2 +- packages/zitadel-tailwind-config/package.json | 2 +- pnpm-lock.yaml | 2330 ++++++++--------- 6 files changed, 1077 insertions(+), 1311 deletions(-) diff --git a/apps/login/package.json b/apps/login/package.json index 541766325f..13927b5168 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -57,36 +57,36 @@ "tinycolor2": "1.4.2" }, "devDependencies": { - "@bufbuild/buf": "^1.41.0", - "@testing-library/jest-dom": "^6.4.5", + "@bufbuild/buf": "^1.46.0", + "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.0.1", "@types/ms": "0.7.34", - "@types/node": "22.5.5", - "@types/react": "18.3.7", - "@types/react-dom": "18.3.0", + "@types/node": "22.9.0", + "@types/react": "18.3.12", + "@types/react-dom": "18.3.1", "@types/tinycolor2": "1.4.3", "@types/uuid": "^10.0.0", "@vercel/git-hooks": "1.0.0", "@zitadel/prettier-config": "workspace:*", "@zitadel/tsconfig": "workspace:*", "autoprefixer": "10.4.20", - "concurrently": "^9.0.1", - "cypress": "^13.14.2", - "del-cli": "5.1.0", + "concurrently": "^9.1.0", + "cypress": "^13.15.2", + "del-cli": "6.0.0", "env-cmd": "^10.1.0", "eslint-config-zitadel": "workspace:*", "grpc-tools": "1.12.4", - "jsdom": "^25.0.0", + "jsdom": "^25.0.1", "lint-staged": "15.2.10", "make-dir-cli": "4.0.0", - "nodemon": "^3.1.5", - "postcss": "8.4.47", - "prettier-plugin-tailwindcss": "0.6.6", - "sass": "^1.79.1", + "nodemon": "^3.1.7", + "postcss": "8.4.49", + "prettier-plugin-tailwindcss": "0.6.8", + "sass": "^1.80.7", "start-server-and-test": "^2.0.8", - "tailwindcss": "3.4.13", - "ts-proto": "^2.2.0", - "typescript": "^5.6.2", + "tailwindcss": "3.4.14", + "ts-proto": "^2.2.7", + "typescript": "^5.6.3", "zitadel-tailwind-config": "workspace:*" } } diff --git a/package.json b/package.json index 6b5e27cc7d..2266c62bbc 100644 --- a/package.json +++ b/package.json @@ -29,19 +29,19 @@ } }, "devDependencies": { - "@changesets/cli": "^2.27.8", - "@playwright/test": "^1.48.1", - "@types/node": "^22.7.5", - "@vitejs/plugin-react": "^4.2.1", + "@changesets/cli": "^2.27.9", + "@playwright/test": "^1.48.2", + "@types/node": "^22.9.0", + "@vitejs/plugin-react": "^4.3.3", "@zitadel/prettier-config": "workspace:*", "eslint": "8.57.1", "eslint-config-zitadel": "workspace:*", "prettier": "^3.2.5", - "prettier-plugin-organize-imports": "^4.0.0", - "tsup": "^8.3.0", - "turbo": "2.1.2", - "typescript": "^5.6.2", - "vite-tsconfig-paths": "^5.0.1", - "vitest": "^2.1.1" + "prettier-plugin-organize-imports": "^4.1.0", + "tsup": "^8.3.5", + "turbo": "2.2.3", + "typescript": "^5.6.3", + "vite-tsconfig-paths": "^5.1.2", + "vitest": "^2.1.4" } } diff --git a/packages/zitadel-node/package.json b/packages/zitadel-node/package.json index ef8046f5e0..fccf13d69d 100644 --- a/packages/zitadel-node/package.json +++ b/packages/zitadel-node/package.json @@ -38,7 +38,7 @@ }, "devDependencies": { "@connectrpc/connect": "^2.0.0-alpha.1", - "@types/node": "^22.5.5", + "@types/node": "^22.9.0", "@zitadel/client": "workspace:*", "@zitadel/tsconfig": "workspace:*", "eslint-config-zitadel": "workspace:*" diff --git a/packages/zitadel-proto/package.json b/packages/zitadel-proto/package.json index 5412b3f371..ecbf4ac5de 100644 --- a/packages/zitadel-proto/package.json +++ b/packages/zitadel-proto/package.json @@ -18,6 +18,6 @@ "@bufbuild/protobuf": "^2.0.0" }, "devDependencies": { - "@bufbuild/buf": "^1.41.0" + "@bufbuild/buf": "^1.46.0" } } diff --git a/packages/zitadel-tailwind-config/package.json b/packages/zitadel-tailwind-config/package.json index 03e895a041..2d0f81075e 100644 --- a/packages/zitadel-tailwind-config/package.json +++ b/packages/zitadel-tailwind-config/package.json @@ -4,7 +4,7 @@ "private": true, "main": "index.js", "devDependencies": { - "tailwindcss": "^3.4.12", + "tailwindcss": "^3.4.14", "@tailwindcss/forms": "0.5.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e023aac1b5..b73dee807c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,17 +12,17 @@ importers: .: devDependencies: '@changesets/cli': - specifier: ^2.27.8 - version: 2.27.8 + specifier: ^2.27.9 + version: 2.27.9 '@playwright/test': - specifier: ^1.48.1 - version: 1.48.1 + specifier: ^1.48.2 + version: 1.48.2 '@types/node': - specifier: ^22.7.5 - version: 22.7.6 + specifier: ^22.9.0 + version: 22.9.0 '@vitejs/plugin-react': - specifier: ^4.2.1 - version: 4.3.1(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1)) + specifier: ^4.3.3 + version: 4.3.3(vite@5.4.11(@types/node@22.9.0)(sass@1.80.7)) '@zitadel/prettier-config': specifier: workspace:* version: link:packages/zitadel-prettier-config @@ -36,23 +36,23 @@ importers: specifier: ^3.2.5 version: 3.3.3 prettier-plugin-organize-imports: - specifier: ^4.0.0 - version: 4.0.0(prettier@3.3.3)(typescript@5.6.2) + specifier: ^4.1.0 + version: 4.1.0(prettier@3.3.3)(typescript@5.6.3) tsup: - specifier: ^8.3.0 - version: 8.3.0(jiti@1.21.6)(postcss@8.4.47)(typescript@5.6.2)(yaml@2.5.0) + specifier: ^8.3.5 + version: 8.3.5(jiti@1.21.6)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.5.0) turbo: - specifier: 2.1.2 - version: 2.1.2 + specifier: 2.2.3 + version: 2.2.3 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 vite-tsconfig-paths: - specifier: ^5.0.1 - version: 5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1)) + specifier: ^5.1.2 + version: 5.1.2(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(sass@1.80.7)) vitest: - specifier: ^2.1.1 - version: 2.1.1(@types/node@22.7.6)(jsdom@25.0.0)(sass@1.79.1) + specifier: ^2.1.4 + version: 2.1.4(@types/node@22.9.0)(jsdom@25.0.1)(sass@1.80.7) apps/login: dependencies: @@ -64,10 +64,10 @@ importers: version: 2.1.3(react@18.3.1) '@tailwindcss/forms': specifier: 0.5.7 - version: 0.5.7(tailwindcss@3.4.13) + version: 0.5.7(tailwindcss@3.4.14) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) + version: 1.3.1(next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7))(react@18.3.1) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -91,13 +91,13 @@ importers: version: 2.30.1 next: specifier: 14.2.14 - version: 14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + version: 14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7) next-intl: specifier: ^3.20.0 - version: 3.20.0(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) + version: 3.20.0(next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7))(react@18.3.1) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.2.1(next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nice-grpc: specifier: 2.0.1 version: 2.0.1 @@ -121,26 +121,26 @@ importers: version: 1.4.2 devDependencies: '@bufbuild/buf': - specifier: ^1.41.0 - version: 1.41.0 + specifier: ^1.46.0 + version: 1.46.0 '@testing-library/jest-dom': - specifier: ^6.4.5 - version: 6.5.0 + specifier: ^6.6.3 + version: 6.6.3 '@testing-library/react': specifier: ^16.0.1 - version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/ms': specifier: 0.7.34 version: 0.7.34 '@types/node': - specifier: 22.5.5 - version: 22.5.5 + specifier: 22.9.0 + version: 22.9.0 '@types/react': - specifier: 18.3.7 - version: 18.3.7 + specifier: 18.3.12 + version: 18.3.12 '@types/react-dom': - specifier: 18.3.0 - version: 18.3.0 + specifier: 18.3.1 + version: 18.3.1 '@types/tinycolor2': specifier: 1.4.3 version: 1.4.3 @@ -158,16 +158,16 @@ importers: version: link:../../packages/zitadel-tsconfig autoprefixer: specifier: 10.4.20 - version: 10.4.20(postcss@8.4.47) + version: 10.4.20(postcss@8.4.49) concurrently: - specifier: ^9.0.1 - version: 9.0.1 + specifier: ^9.1.0 + version: 9.1.0 cypress: - specifier: ^13.14.2 - version: 13.14.2 + specifier: ^13.15.2 + version: 13.15.2 del-cli: - specifier: 5.1.0 - version: 5.1.0 + specifier: 6.0.0 + version: 6.0.0 env-cmd: specifier: ^10.1.0 version: 10.1.0 @@ -178,8 +178,8 @@ importers: specifier: 1.12.4 version: 1.12.4 jsdom: - specifier: ^25.0.0 - version: 25.0.0 + specifier: ^25.0.1 + version: 25.0.1 lint-staged: specifier: 15.2.10 version: 15.2.10 @@ -187,29 +187,29 @@ importers: specifier: 4.0.0 version: 4.0.0 nodemon: - specifier: ^3.1.5 - version: 3.1.5 + specifier: ^3.1.7 + version: 3.1.7 postcss: - specifier: 8.4.47 - version: 8.4.47 + specifier: 8.4.49 + version: 8.4.49 prettier-plugin-tailwindcss: - specifier: 0.6.6 - version: 0.6.6(prettier-plugin-organize-imports@4.0.0(prettier@3.3.3)(typescript@5.6.2))(prettier@3.3.3) + specifier: 0.6.8 + version: 0.6.8(prettier-plugin-organize-imports@4.1.0(prettier@3.3.3)(typescript@5.6.3))(prettier@3.3.3) sass: - specifier: ^1.79.1 - version: 1.79.1 + specifier: ^1.80.7 + version: 1.80.7 start-server-and-test: specifier: ^2.0.8 version: 2.0.8 tailwindcss: - specifier: 3.4.13 - version: 3.4.13 + specifier: 3.4.14 + version: 3.4.14 ts-proto: - specifier: ^2.2.0 - version: 2.2.0 + specifier: ^2.2.7 + version: 2.2.7 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 zitadel-tailwind-config: specifier: workspace:* version: link:../../packages/zitadel-tailwind-config @@ -218,10 +218,10 @@ importers: dependencies: '@typescript-eslint/parser': specifier: ^7.9.0 - version: 7.18.0(eslint@8.57.1)(typescript@5.6.2) + version: 7.18.0(eslint@8.57.1)(typescript@5.6.3) eslint-config-next: specifier: ^14.2.3 - version: 14.2.7(eslint@8.57.1)(typescript@5.6.2) + version: 14.2.7(eslint@8.57.1)(typescript@5.6.3) eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@8.57.1) @@ -246,7 +246,7 @@ importers: devDependencies: '@bufbuild/protocompile': specifier: ^0.0.1 - version: 0.0.1(@bufbuild/buf@1.41.0) + version: 0.0.1(@bufbuild/buf@1.46.0) '@zitadel/tsconfig': specifier: workspace:* version: link:../zitadel-tsconfig @@ -270,8 +270,8 @@ importers: specifier: ^2.0.0-alpha.1 version: 2.0.0-alpha.1(@bufbuild/protobuf@2.2.0) '@types/node': - specifier: ^22.5.5 - version: 22.5.5 + specifier: ^22.9.0 + version: 22.9.0 '@zitadel/client': specifier: workspace:* version: link:../zitadel-client @@ -291,17 +291,17 @@ importers: version: 2.0.0 devDependencies: '@bufbuild/buf': - specifier: ^1.41.0 - version: 1.41.0 + specifier: ^1.46.0 + version: 1.46.0 packages/zitadel-tailwind-config: devDependencies: '@tailwindcss/forms': specifier: 0.5.3 - version: 0.5.3(tailwindcss@3.4.12) + version: 0.5.3(tailwindcss@3.4.14) tailwindcss: - specifier: ^3.4.12 - version: 3.4.12 + specifier: ^3.4.14 + version: 3.4.14 packages/zitadel-tsconfig: {} @@ -318,89 +318,69 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@babel/code-frame@7.24.7': - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} - '@babel/code-frame@7.25.7': - resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==, tarball: https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz} + '@babel/compat-data@7.26.2': + resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.25.4': - resolution: {integrity: sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==} + '@babel/core@7.26.0': + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} engines: {node: '>=6.9.0'} - '@babel/core@7.25.2': - resolution: {integrity: sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==} + '@babel/generator@7.26.2': + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.25.6': - resolution: {integrity: sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==} + '@babel/helper-compilation-targets@7.25.9': + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.25.2': - resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==} + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.24.7': - resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.25.2': - resolution: {integrity: sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==} + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.24.8': - resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==} + '@babel/helper-plugin-utils@7.25.9': + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} engines: {node: '>=6.9.0'} - '@babel/helper-simple-access@7.24.7': - resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.8': - resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.7': - resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==, tarball: https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz} + '@babel/helpers@7.26.0': + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.24.8': - resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.25.6': - resolution: {integrity: sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==} - engines: {node: '>=6.9.0'} - - '@babel/highlight@7.24.7': - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} - engines: {node: '>=6.9.0'} - - '@babel/highlight@7.25.7': - resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==, tarball: https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.25.6': - resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-transform-react-jsx-self@7.24.7': - resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} + '@babel/plugin-transform-react-jsx-self@7.25.9': + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.24.7': - resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} + '@babel/plugin-transform-react-jsx-source@7.25.9': + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -409,60 +389,60 @@ packages: resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.25.7': - resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==} + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} - '@babel/template@7.25.0': - resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.25.6': - resolution: {integrity: sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==} + '@babel/traverse@7.25.9': + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} engines: {node: '>=6.9.0'} - '@babel/types@7.25.6': - resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} + '@babel/types@7.26.0': + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} engines: {node: '>=6.9.0'} - '@bufbuild/buf-darwin-arm64@1.41.0': - resolution: {integrity: sha512-+G5DwpIgnm0AkqgxORxoYXVT0RGDcw8P4SXFXcovgvDBkk9rPvEI1dbPF83n3SUxzcu2A2OxC7DxlXszWIh2Gw==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.41.0.tgz} + '@bufbuild/buf-darwin-arm64@1.46.0': + resolution: {integrity: sha512-lSmTKyRhg+71acXp9QeX/wm+vjkf0J3n38wph7KOwMfCEeK4A2AkqsGOkoXSiaIvidA2pRU9RJRQYfryzCA9Pg==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - '@bufbuild/buf-darwin-x64@1.41.0': - resolution: {integrity: sha512-qjkJ/LAWqNk3HX65n+JTt18WtKrhrrAhIu3Dpfbe0eujsxafFZKoPzlWJYybxvsaF9CdEyMMm/OalBPpoosMOA==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.41.0.tgz} + '@bufbuild/buf-darwin-x64@1.46.0': + resolution: {integrity: sha512-Oa9XTLJshsEjzowyt2mH9XrXW38DRFdz7ml+IYKXVQPotNLr04ix7QES7A1eOBJtxLwuTiri4ScXuBLQGNX8+A==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - '@bufbuild/buf-linux-aarch64@1.41.0': - resolution: {integrity: sha512-5E+MLAF4QHPwAjwVVRRP3Is2U3zpIpQQR7S3di9HlKACbgvefJEBrUfRqQZvHrMuuynQRqjFuZD16Sfvxn9rCQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.41.0.tgz} + '@bufbuild/buf-linux-aarch64@1.46.0': + resolution: {integrity: sha512-CbxbLH5sQCRjEKVEcWJySvCKyAPAUhX0vCTifT/eQyZ70FUsqCJKJ6+dKl6Ajk0CgUHqf8jkU/wX/+aQFYXyaA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - '@bufbuild/buf-linux-x64@1.41.0': - resolution: {integrity: sha512-W4T+uqmdtypzzatv6OXjUzGacZiNzGECogr+qDkJF38MSZd3jHXhTEN2KhRckl3i9rRAnfHBwG68BjCTxxBCOQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.41.0.tgz} + '@bufbuild/buf-linux-x64@1.46.0': + resolution: {integrity: sha512-bMqp+Q+16KPbuwX34/OLDeiimnwt5sfvHqyeMeRz4LLwLshbmM3m+8dGCSHZRo3Lr+4gW1PfunrfaEmcGqPHLQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - '@bufbuild/buf-win32-arm64@1.41.0': - resolution: {integrity: sha512-OsRVoTZHJZYGIphAwaRqcCeYR9Sk5VEMjpCJiFt/dkHxx2acKH4u/7O+633gcCxQL8EnsU2l8AfdbW7sQaOvlg==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.41.0.tgz} + '@bufbuild/buf-win32-arm64@1.46.0': + resolution: {integrity: sha512-geVYXp1PWJiAAFpwhgP8Cnct0+Rdr89BF/WZoIh5WwFGYITGiu5Hb1Ui9DTrEYwDzahPCyPxgIVwzzW6kPWSag==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - '@bufbuild/buf-win32-x64@1.41.0': - resolution: {integrity: sha512-2KJLp7Py0GsfRjDxwBzS17RMpaYFGCvzkwY5CtxfPMw8cg6cE7E36r+vcjHh5dBOj/CumaiXLTwxhCSBtp0V1g==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.41.0.tgz} + '@bufbuild/buf-win32-x64@1.46.0': + resolution: {integrity: sha512-6nsxkzj5a1L41NOJFKjli8j6GB/NkPHLIr0T/b27Y3GfprVYQawOComYD5HfojvBLuAiE2cD/kEQIWKK1YRcng==} engines: {node: '>=12'} cpu: [x64] os: [win32] - '@bufbuild/buf@1.41.0': - resolution: {integrity: sha512-6pN2fqMrPqnIkrC1q9KpXpu7fv3Rul0ZPhT4MSYYj+8VcyR3kbLVk6K+CzzPvYhr4itfotnI3ZVGQ/X/vupECg==} + '@bufbuild/buf@1.46.0': + resolution: {integrity: sha512-uN3NKuAKvcQcZc1hn9+njSCusL7NAILqQI7mlkDDa4kRy7cTsiw53ggddfAs0YXbQ8zZnmudwwWjKRDIUaRqXQ==} engines: {node: '>=12'} hasBin: true @@ -470,10 +450,10 @@ packages: resolution: {integrity: sha512-sw2JhwJyvyL0zlhG61aDzOVryEfJg2PDZFSV7i7IdC7nAE41WuXCru3QWLGiP87At0BMzKOoKO/FqEGoKygGZQ==} '@bufbuild/protobuf@2.2.0': - resolution: {integrity: sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ==, tarball: https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.0.tgz} + resolution: {integrity: sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ==} '@bufbuild/protocompile@0.0.1': - resolution: {integrity: sha512-cOTMtjcWLcbjF17dPYgeMtVC5jZyS0bSjz3jy8kDPjOgjgSYMD2u2It7w8aCc2z23hTPIKl/2SNdMnz0Jzu3Xg==, tarball: https://registry.npmjs.org/@bufbuild/protocompile/-/protocompile-0.0.1.tgz} + resolution: {integrity: sha512-cOTMtjcWLcbjF17dPYgeMtVC5jZyS0bSjz3jy8kDPjOgjgSYMD2u2It7w8aCc2z23hTPIKl/2SNdMnz0Jzu3Xg==} peerDependencies: '@bufbuild/buf': ^1.22.0 @@ -486,8 +466,8 @@ packages: '@changesets/changelog-git@0.2.0': resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} - '@changesets/cli@2.27.8': - resolution: {integrity: sha512-gZNyh+LdSsI82wBSHLQ3QN5J30P4uHKJ4fXgoGwQxfXwYFTJzDdvIJasZn8rYQtmKhyQuiBj4SSnLuKlxKWq4w==} + '@changesets/cli@2.27.9': + resolution: {integrity: sha512-q42a/ZbDnxPpCb5Wkm6tMVIxgeI9C/bexntzTeCFBrQEdpisQqk8kCHllYZMDjYtEc1ZzumbMJAG8H0Z4rdvjg==} hasBin: true '@changesets/config@3.0.3': @@ -533,7 +513,7 @@ packages: resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==, tarball: https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz} + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} '@connectrpc/connect-node@2.0.0-alpha.1': @@ -550,295 +530,295 @@ packages: '@connectrpc/connect': 2.0.0-alpha.1 '@connectrpc/connect@2.0.0-alpha.1': - resolution: {integrity: sha512-eAjezEy1m+7bDtWt/TeXY2qIayq7R+WW8Jqwq3p8Q7ZTHpFJu0SBQivhRGQbNXSYR+QmqMk+j/FLAVlgB6CfHA==, tarball: https://registry.npmjs.org/@connectrpc/connect/-/connect-2.0.0-alpha.1.tgz} + resolution: {integrity: sha512-eAjezEy1m+7bDtWt/TeXY2qIayq7R+WW8Jqwq3p8Q7ZTHpFJu0SBQivhRGQbNXSYR+QmqMk+j/FLAVlgB6CfHA==} peerDependencies: '@bufbuild/protobuf': ^2.0.0-beta.2 - '@cypress/request@3.0.1': - resolution: {integrity: sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==} + '@cypress/request@3.0.6': + resolution: {integrity: sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==} engines: {node: '>= 6'} '@cypress/xvfb@1.2.4': resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz} + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.23.1': - resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz} + '@esbuild/aix-ppc64@0.24.0': + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz} + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.23.1': - resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz} + '@esbuild/android-arm64@0.24.0': + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} engines: {node: '>=18'} cpu: [arm64] os: [android] '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz} + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] - '@esbuild/android-arm@0.23.1': - resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz} + '@esbuild/android-arm@0.24.0': + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} engines: {node: '>=18'} cpu: [arm] os: [android] '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz} + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] - '@esbuild/android-x64@0.23.1': - resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz} + '@esbuild/android-x64@0.24.0': + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} engines: {node: '>=18'} cpu: [x64] os: [android] '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz} + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.23.1': - resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz} + '@esbuild/darwin-arm64@0.24.0': + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz} + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.23.1': - resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz} + '@esbuild/darwin-x64@0.24.0': + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz} + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.23.1': - resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz} + '@esbuild/freebsd-arm64@0.24.0': + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.23.1': - resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz} + '@esbuild/freebsd-x64@0.24.0': + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz} + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.23.1': - resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz} + '@esbuild/linux-arm64@0.24.0': + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz} + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.23.1': - resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz} + '@esbuild/linux-arm@0.24.0': + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} engines: {node: '>=18'} cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz} + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.23.1': - resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz} + '@esbuild/linux-ia32@0.24.0': + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz} + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.23.1': - resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz} + '@esbuild/linux-loong64@0.24.0': + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} engines: {node: '>=18'} cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz} + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.23.1': - resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz} + '@esbuild/linux-mips64el@0.24.0': + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz} + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.23.1': - resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz} + '@esbuild/linux-ppc64@0.24.0': + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz} + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.23.1': - resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz} + '@esbuild/linux-riscv64@0.24.0': + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz} + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.23.1': - resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz} + '@esbuild/linux-s390x@0.24.0': + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} engines: {node: '>=18'} cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz} + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.23.1': - resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz} + '@esbuild/linux-x64@0.24.0': + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} engines: {node: '>=18'} cpu: [x64] os: [linux] '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.23.1': - resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz} + '@esbuild/netbsd-x64@0.24.0': + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.23.1': - resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz} + '@esbuild/openbsd-arm64@0.24.0': + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.23.1': - resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz} + '@esbuild/openbsd-x64@0.24.0': + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz} + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.23.1': - resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz} + '@esbuild/sunos-x64@0.24.0': + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz} + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.23.1': - resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz} + '@esbuild/win32-arm64@0.24.0': + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz} + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.23.1': - resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz} + '@esbuild/win32-ia32@0.24.0': + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz} + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.23.1': - resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz} + '@esbuild/win32-x64@0.24.0': + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -983,55 +963,55 @@ packages: resolution: {integrity: sha512-+7xh142AdhZGjY9/L0iFo7mqRBMJHe+q+uOL+hto1Lfo9DeWCGcR6no4StlFbVSVcA6fQLKEX6y6qhMsSKbgNQ==} '@next/swc-darwin-arm64@14.2.14': - resolution: {integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==, tarball: https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.14.tgz} + resolution: {integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] '@next/swc-darwin-x64@14.2.14': - resolution: {integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==, tarball: https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.14.tgz} + resolution: {integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] '@next/swc-linux-arm64-gnu@14.2.14': - resolution: {integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==, tarball: https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.14.tgz} + resolution: {integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] '@next/swc-linux-arm64-musl@14.2.14': - resolution: {integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==, tarball: https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.14.tgz} + resolution: {integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] '@next/swc-linux-x64-gnu@14.2.14': - resolution: {integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==, tarball: https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.14.tgz} + resolution: {integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] '@next/swc-linux-x64-musl@14.2.14': - resolution: {integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==, tarball: https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.14.tgz} + resolution: {integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] '@next/swc-win32-arm64-msvc@14.2.14': - resolution: {integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==, tarball: https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.14.tgz} + resolution: {integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] '@next/swc-win32-ia32-msvc@14.2.14': - resolution: {integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==, tarball: https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.14.tgz} + resolution: {integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] '@next/swc-win32-x64-msvc@14.2.14': - resolution: {integrity: sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg==, tarball: https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.14.tgz} + resolution: {integrity: sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1052,12 +1032,94 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@parcel/watcher-android-arm64@2.5.0': + resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.0': + resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.0': + resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.0': + resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.0': + resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.0': + resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.0': + resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.0': + resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.0': + resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.0': + resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.5.0': + resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.0': + resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.0': + resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.0': + resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==} + engines: {node: '>= 10.0.0'} + '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, tarball: https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz} + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.48.1': - resolution: {integrity: sha512-s9RtWoxkOLmRJdw3oFvhFbs9OJS0BzrLUc8Hf6l2UdCNd1rqeEyD4BhCJkvzeEoD1FsK4mirsWwGerhVmYKtZg==} + '@playwright/test@1.48.2': + resolution: {integrity: sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==} engines: {node: '>=18'} hasBin: true @@ -1122,83 +1184,93 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - '@rollup/rollup-android-arm-eabi@4.21.3': - resolution: {integrity: sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz} + '@rollup/rollup-android-arm-eabi@4.25.0': + resolution: {integrity: sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.21.3': - resolution: {integrity: sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz} + '@rollup/rollup-android-arm64@4.25.0': + resolution: {integrity: sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.21.3': - resolution: {integrity: sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz} + '@rollup/rollup-darwin-arm64@4.25.0': + resolution: {integrity: sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.21.3': - resolution: {integrity: sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz} + '@rollup/rollup-darwin-x64@4.25.0': + resolution: {integrity: sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.21.3': - resolution: {integrity: sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz} + '@rollup/rollup-freebsd-arm64@4.25.0': + resolution: {integrity: sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.25.0': + resolution: {integrity: sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.25.0': + resolution: {integrity: sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.21.3': - resolution: {integrity: sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz} + '@rollup/rollup-linux-arm-musleabihf@4.25.0': + resolution: {integrity: sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.21.3': - resolution: {integrity: sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz} + '@rollup/rollup-linux-arm64-gnu@4.25.0': + resolution: {integrity: sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.21.3': - resolution: {integrity: sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz} + '@rollup/rollup-linux-arm64-musl@4.25.0': + resolution: {integrity: sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.21.3': - resolution: {integrity: sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz} + '@rollup/rollup-linux-powerpc64le-gnu@4.25.0': + resolution: {integrity: sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.21.3': - resolution: {integrity: sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz} + '@rollup/rollup-linux-riscv64-gnu@4.25.0': + resolution: {integrity: sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.21.3': - resolution: {integrity: sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz} + '@rollup/rollup-linux-s390x-gnu@4.25.0': + resolution: {integrity: sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.21.3': - resolution: {integrity: sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz} + '@rollup/rollup-linux-x64-gnu@4.25.0': + resolution: {integrity: sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.21.3': - resolution: {integrity: sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz} + '@rollup/rollup-linux-x64-musl@4.25.0': + resolution: {integrity: sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.21.3': - resolution: {integrity: sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz} + '@rollup/rollup-win32-arm64-msvc@4.25.0': + resolution: {integrity: sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.21.3': - resolution: {integrity: sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz} + '@rollup/rollup-win32-ia32-msvc@4.25.0': + resolution: {integrity: sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.21.3': - resolution: {integrity: sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz} + '@rollup/rollup-win32-x64-msvc@4.25.0': + resolution: {integrity: sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==} cpu: [x64] os: [win32] @@ -1214,6 +1286,10 @@ packages: '@sideway/pinpoint@2.0.0': resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + '@sindresorhus/merge-streams@2.3.0': + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} + engines: {node: '>=18'} + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -1240,11 +1316,11 @@ packages: resolution: {integrity: sha512-1giLc4dzgEKLMx5pgKjL6HlG5fjZMgCjzlKAlpr7yoUtetVPELgER1NtephAI910nMwfPTHNyWKSFmJdHkz2Cw==} '@testing-library/dom@10.4.0': - resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==, tarball: https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz} + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} - '@testing-library/jest-dom@6.5.0': - resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} + '@testing-library/jest-dom@6.6.3': + resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} '@testing-library/react@16.0.1': @@ -1263,7 +1339,7 @@ packages: optional: true '@types/aria-query@5.0.4': - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==, tarball: https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz} + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1277,41 +1353,29 @@ packages: '@types/babel__traverse@7.20.6': resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@22.5.5': - resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} - - '@types/node@22.7.6': - resolution: {integrity: sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==} - - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/node@22.9.0': + resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - '@types/react-dom@18.3.0': - resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + '@types/react-dom@18.3.1': + resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} - '@types/react@18.3.7': - resolution: {integrity: sha512-KUnDCJF5+AiZd8owLIeVHqmW9yM4sqmDVf2JRJiBMFkGvkoZ4/WyV2lL4zVsoinmRS/W3FeEdZLEWFRofnT2FQ==} - - '@types/semver@7.5.8': - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + '@types/react@18.3.12': + resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} '@types/sinonjs__fake-timers@8.1.1': resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==} @@ -1326,7 +1390,7 @@ packages: resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==, tarball: https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz} + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} '@typescript-eslint/parser@7.18.0': resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} @@ -1376,20 +1440,19 @@ packages: '@vercel/git-hooks@1.0.0': resolution: {integrity: sha512-OxDFAAdyiJ/H0b8zR9rFCu3BIb78LekBXOphOYG3snV4ULhKFX387pBPpqZ9HLiRTejBWBxYEahkw79tuIgdAA==} - '@vitejs/plugin-react@4.3.1': - resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} + '@vitejs/plugin-react@4.3.3': + resolution: {integrity: sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 - '@vitest/expect@2.1.1': - resolution: {integrity: sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==} + '@vitest/expect@2.1.4': + resolution: {integrity: sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==} - '@vitest/mocker@2.1.1': - resolution: {integrity: sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==} + '@vitest/mocker@2.1.4': + resolution: {integrity: sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==} peerDependencies: - '@vitest/spy': 2.1.1 - msw: ^2.3.5 + msw: ^2.4.9 vite: ^5.0.0 peerDependenciesMeta: msw: @@ -1397,20 +1460,20 @@ packages: vite: optional: true - '@vitest/pretty-format@2.1.1': - resolution: {integrity: sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==} + '@vitest/pretty-format@2.1.4': + resolution: {integrity: sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==} - '@vitest/runner@2.1.1': - resolution: {integrity: sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==} + '@vitest/runner@2.1.4': + resolution: {integrity: sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==} - '@vitest/snapshot@2.1.1': - resolution: {integrity: sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==} + '@vitest/snapshot@2.1.4': + resolution: {integrity: sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==} - '@vitest/spy@2.1.1': - resolution: {integrity: sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==} + '@vitest/spy@2.1.4': + resolution: {integrity: sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==} - '@vitest/utils@2.1.1': - resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==} + '@vitest/utils@2.1.4': + resolution: {integrity: sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==} abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -1440,10 +1503,6 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - aggregate-error@4.0.1: - resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} - engines: {node: '>=12'} - ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1467,16 +1526,12 @@ packages: resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==, tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz} + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} ansi-styles@6.2.1: @@ -1552,10 +1607,6 @@ packages: resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} @@ -1649,6 +1700,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.24.2: + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -1685,20 +1741,15 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - camelcase-keys@7.0.2: - resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==} - engines: {node: '>=12'} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - caniuse-lite@1.0.30001654: resolution: {integrity: sha512-wLJc602fW0OdrUR+PqsBUH3dgrjDcT+mWs/Kw86zPvgjiqOiI2TXMkBFK4KihYzZclmJxrFwgYhZDSEogFai/g==} caniuse-lite@1.0.30001666: resolution: {integrity: sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==} + caniuse-lite@1.0.30001680: + resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==} + case-anything@2.1.13: resolution: {integrity: sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==} engines: {node: '>=12.13'} @@ -1706,14 +1757,10 @@ packages: caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} - chai@5.1.1: - resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} engines: {node: '>=12'} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - chalk@3.0.0: resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} engines: {node: '>=8'} @@ -1741,8 +1788,8 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - chokidar@4.0.0: - resolution: {integrity: sha512-mxIojEAQcuEvT/lyXq+jf/3cO/KoA6z4CeNDGGevTybECPOMFCnQy3OPahluUkbqgPNGw5Bi78UC7Po6Lhy+NA==} + chokidar@4.0.1: + resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==} engines: {node: '>= 14.16.0'} chownr@2.0.0: @@ -1753,14 +1800,14 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} + ci-info@4.1.0: + resolution: {integrity: sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==} + engines: {node: '>=8'} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - clean-stack@4.2.0: - resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} - engines: {node: '>=12'} - cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -1796,16 +1843,10 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -1839,8 +1880,8 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concurrently@9.0.1: - resolution: {integrity: sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==} + concurrently@9.1.0: + resolution: {integrity: sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==} engines: {node: '>=18'} hasBin: true @@ -1867,6 +1908,10 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + cross-spawn@7.0.5: + resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==} + engines: {node: '>= 8'} + css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} @@ -1875,15 +1920,15 @@ packages: engines: {node: '>=4'} hasBin: true - cssstyle@4.0.1: - resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} + cssstyle@4.1.0: + resolution: {integrity: sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==} engines: {node: '>=18'} csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - cypress@13.14.2: - resolution: {integrity: sha512-lsiQrN17vHMB2fnvxIrKLAjOr9bPwsNbPZNrWf99s4u+DVmCY6U+w7O3GGG9FvP4EUVYaDu+guWeNLiUzBrqvA==} + cypress@13.15.2: + resolution: {integrity: sha512-ARbnUorjcCM3XiPwgHKuqsyr5W9Qn+pIIBPaoilnoBkLdSC2oLQjV1BUpnmc7KR+b7Avah3Ly2RMFnfxr96E/A==} engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -1939,18 +1984,6 @@ packages: supports-color: optional: true - decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - - decamelize@5.0.1: - resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} - engines: {node: '>=10'} - decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} @@ -1977,14 +2010,14 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - del-cli@5.1.0: - resolution: {integrity: sha512-xwMeh2acluWeccsfzE7VLsG3yTr7nWikbfw+xhMnpRrF15pGSkw+3/vJZWlGoE4I86UiLRNHicmKt4tkIX9Jtg==} - engines: {node: '>=14.16'} + del-cli@6.0.0: + resolution: {integrity: sha512-9nitGV2W6KLFyya4qYt4+9AKQFL+c0Ehj5K7V7IwlxTc6RMCfQUGY9E9pLG6e8TQjtwXpuiWIGGZb3mfVxyZkw==} + engines: {node: '>=18'} hasBin: true - del@7.1.0: - resolution: {integrity: sha512-v2KyNk7efxhlyHpjEvfyxaAihKKK0nWCuf6ZtqZcFFpQRG0bJ12Qsr0RpvsICMjAAZ8DOVCxrlqpxISlMHC4Kg==} - engines: {node: '>=14.16'} + del@8.0.0: + resolution: {integrity: sha512-R6ep6JJ+eOBZsBr9esiNN1gxFbZE4Q2cULkUSFumGYecAiS6qodDvcPx/sFuWHMNul7DWmrtoEOpYSm7o6tbSA==} + engines: {node: '>=18'} delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} @@ -2029,7 +2062,7 @@ packages: engines: {node: '>=6.0.0'} dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==, tarball: https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz} + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} @@ -2053,6 +2086,9 @@ packages: electron-to-chromium@1.5.13: resolution: {integrity: sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==} + electron-to-chromium@1.5.57: + resolution: {integrity: sha512-xS65H/tqgOwUBa5UmOuNSLuslDo7zho0y/lgQw35pnrqiZh7UOWHCeL/Bt6noJATbA6tpQJGCifsFsIRZj1Fqg==} + emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} @@ -2086,9 +2122,6 @@ packages: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-abstract@1.23.3: resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} engines: {node: '>= 0.4'} @@ -2128,8 +2161,8 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.23.1: - resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + esbuild@0.24.0: + resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} engines: {node: '>=18'} hasBin: true @@ -2145,10 +2178,6 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - eslint-config-next@14.2.7: resolution: {integrity: sha512-ppmy+QdQ7qkuCHGDlPjWaoSbJvjGpWSBD4zEW8f1eWlxYXYpZK7QzBOer1EcHKT3uKhlY1JjUus9g7Kvv712rw==} peerDependencies: @@ -2306,6 +2335,10 @@ packages: resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} engines: {node: '>=4'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -2344,8 +2377,8 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - fdir@6.3.0: - resolution: {integrity: sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ==} + fdir@6.4.2: + resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -2353,7 +2386,7 @@ packages: optional: true fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==, tarball: https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz} + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} @@ -2401,10 +2434,6 @@ packages: forever-agent@0.6.1: resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} - form-data@2.3.3: - resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} - engines: {node: '>= 0.12'} - form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -2440,7 +2469,7 @@ packages: os: [darwin] fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz} + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] @@ -2471,9 +2500,6 @@ packages: resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} engines: {node: '>=18'} - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -2544,9 +2570,9 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - globby@13.2.2: - resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + globby@14.0.2: + resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==} + engines: {node: '>=18'} globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -2564,10 +2590,6 @@ packages: resolution: {integrity: sha512-5+mLAJJma3BjnW/KQp6JBjUMgvu7Mu3dBvBPd1dcbNIb+qiR0817zDpgPjS7gRb+l/8EVNIa3cB02xI9JLToKg==} hasBin: true - hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -2601,10 +2623,6 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} @@ -2613,8 +2631,8 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} - http-signature@1.3.6: - resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==} + http-signature@1.4.0: + resolution: {integrity: sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==} engines: {node: '>=0.10'} https-proxy-agent@5.0.1: @@ -2658,8 +2676,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - immutable@4.3.7: - resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} + immutable@5.0.2: + resolution: {integrity: sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw==} import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} @@ -2673,10 +2691,6 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} - inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -2703,9 +2717,6 @@ packages: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-async-function@2.0.0: resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} engines: {node: '>= 0.4'} @@ -2728,10 +2739,6 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-ci@3.0.1: - resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} - hasBin: true - is-core-module@2.15.1: resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} engines: {node: '>= 0.4'} @@ -2803,10 +2810,6 @@ packages: resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} engines: {node: '>=12'} - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} @@ -2915,8 +2918,8 @@ packages: jsbn@0.1.1: resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} - jsdom@25.0.0: - resolution: {integrity: sha512-OhoFVT59T7aEq75TVw9xxEfkXgacpqAhQaYgP9y/fDqWQCMB/b1H66RfmPm/MaeaAIU9nDwMOVTlPN51+ao6CQ==} + jsdom@25.0.1: + resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==} engines: {node: '>=18'} peerDependencies: canvas: ^2.11.2 @@ -2924,17 +2927,14 @@ packages: canvas: optional: true - jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} hasBin: true json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -2973,10 +2973,6 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -3070,8 +3066,8 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - loupe@3.1.1: - resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3082,16 +3078,12 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==, tarball: https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz} + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true - magic-string@0.30.11: - resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + magic-string@0.30.12: + resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} make-dir-cli@4.0.0: resolution: {integrity: sha512-9BBC2CaGH0hUAx+tQthgxqYypwkTs+7oXmPdiWyDpHGo4mGB3kdudUKQGivK59C1aJroo4QLlXF7Chu/kdhYiw==} @@ -3106,21 +3098,9 @@ packages: resolution: {integrity: sha512-G0yBotnlWVonPClw+tq+xi4K7DZC9n96HjGTBDdHkstAVsDkfZhi1sTvZypXLpyQTbISBkDtK0E5XlUqDsShQg==} engines: {node: '>=18'} - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - - map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - map-stream@0.1.0: resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} - meow@10.1.5: - resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - meow@13.2.0: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} @@ -3171,10 +3151,6 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -3264,6 +3240,9 @@ packages: nice-grpc@2.0.1: resolution: {integrity: sha512-Q5CGXO08STsv+HAkXeFgRayANT62X1LnIDhNXdCf+LP0XaP7EiHM0Cr3QefnoFjDZAx/Kxq+qiQfY66BrtKcNQ==} + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -3276,8 +3255,8 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - nodemon@3.1.5: - resolution: {integrity: sha512-V5UtfYc7hjFD4SI3EzD5TR8ChAHEZ+Ns7Z5fBk8fAbTVAj+q3G+w7sHJrHxXBkVn6ApLVTljau8wfHwqmGUjMw==} + nodemon@3.1.7: + resolution: {integrity: sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==} engines: {node: '>=10'} hasBin: true @@ -3286,10 +3265,6 @@ packages: engines: {node: '>=6'} hasBin: true - normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -3410,28 +3385,24 @@ packages: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} - p-map@5.5.0: - resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} - engines: {node: '>=12'} + p-map@7.0.2: + resolution: {integrity: sha512-z4cYYMMdKHzw4O5UkWJImbZynVIo0lSGTXc7bzB1e/rrDqkgGUNysK/o4bTr+0+xKvvLoTyGqYC4Fgljy9qe1Q==} + engines: {node: '>=18'} p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-json-from-dist@1.0.0: - resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@0.2.0: - resolution: {integrity: sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==} + package-manager-detector@0.2.2: + resolution: {integrity: sha512-VgXbyrSNsml4eHWIvxxG/nTL4wgybMTXCV2Un/+yEc3aDKKU6nQBZjbeP3Pl3qm9Qg92X/1ng4ffvCeD/zwHgg==} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} @@ -3462,6 +3433,10 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + path-type@5.0.0: + resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} + engines: {node: '>=12'} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -3481,8 +3456,8 @@ packages: picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} - picocolors@1.1.0: - resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -3509,13 +3484,13 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - playwright-core@1.48.1: - resolution: {integrity: sha512-Yw/t4VAFX/bBr1OzwCuOMZkY1Cnb4z/doAFSwf4huqAGWmf9eMNjmK7NiOljCdLmxeRYcGPPmcDgU0zOlzP0YA==} + playwright-core@1.48.2: + resolution: {integrity: sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==} engines: {node: '>=18'} hasBin: true - playwright@1.48.1: - resolution: {integrity: sha512-j8CiHW/V6HxmbntOfyB4+T/uk08tBy6ph0MpBXwuoofkSnLmlfdYNNkFTYD6ofzzlSqLA1fwH4vwvVFvJgLN0w==} + playwright@1.48.2: + resolution: {integrity: sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==} engines: {node: '>=18'} hasBin: true @@ -3582,29 +3557,26 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.4.47: - resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier-plugin-organize-imports@4.0.0: - resolution: {integrity: sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==} + prettier-plugin-organize-imports@4.1.0: + resolution: {integrity: sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==} peerDependencies: - '@vue/language-plugin-pug': ^2.0.24 prettier: '>=2.0' typescript: '>=2.9' - vue-tsc: ^2.0.24 + vue-tsc: ^2.1.0 peerDependenciesMeta: - '@vue/language-plugin-pug': - optional: true vue-tsc: optional: true - prettier-plugin-tailwindcss@0.6.6: - resolution: {integrity: sha512-OPva5S7WAsPLEsOuOWXATi13QrCKACCiIonFgIR6V4lYv4QLp++UXVhZSzRbZxXGimkQtQT86CC6fQqTOybGng==} + prettier-plugin-tailwindcss@0.6.8: + resolution: {integrity: sha512-dGu3kdm7SXPkiW4nzeWKCl3uoImdd5CTZEJGxyypEPL37Wj0HT2pLqjrvSei1nTeuQfO4PUfjeW5cTUNRLZ4sA==} engines: {node: '>=14.21.3'} peerDependencies: '@ianvs/prettier-plugin-sort-imports': '*' @@ -3673,7 +3645,7 @@ packages: engines: {node: '>=6'} pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==, tarball: https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz} + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} process@0.11.10: @@ -3701,9 +3673,6 @@ packages: pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} - psl@1.9.0: - resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} - pstree.remy@1.1.8: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} @@ -3719,20 +3688,13 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 - qs@6.10.4: - resolution: {integrity: sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==} + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -3748,7 +3710,7 @@ packages: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==, tarball: https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz} + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} @@ -3761,14 +3723,6 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - read-pkg-up@8.0.0: - resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} - engines: {node: '>=12'} - - read-pkg@6.0.0: - resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==} - engines: {node: '>=12'} - read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -3781,18 +3735,14 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - readdirp@4.0.1: - resolution: {integrity: sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==} + readdirp@4.0.2: + resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==} engines: {node: '>= 14.16.0'} redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} - redent@4.0.0: - resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} - engines: {node: '>=12'} - reflect.getprototypeof@1.0.6: resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} engines: {node: '>= 0.4'} @@ -3811,9 +3761,6 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3853,14 +3800,11 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.21.3: - resolution: {integrity: sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==} + rollup@4.25.0: + resolution: {integrity: sha512-uVbClXmR6wvx5R1M3Od4utyLUxrmOcEm3pAtMphn73Apq19PDtHpgZoEvqH2YnnaNUuvKmg2DgRd2Sqv+odyqg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rrweb-cssom@0.6.0: - resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} - rrweb-cssom@0.7.1: resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} @@ -3884,8 +3828,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.79.1: - resolution: {integrity: sha512-+mA7svoNKeL0DiJqZGeR/ZGUu8he4I8o3jyUcOFyo4eBJrwNgIMmAEwCMo/N2Y3wdjOBcRzoNxZIOtrtMX8EXg==} + sass@1.80.7: + resolution: {integrity: sha512-MVWvN0u5meytrSjsU7AWsbhoXi1sc58zADXFllfZzbsBT1GHjjar6JwBINYPRrkx/zqnQ6uqbQuHgE95O+C+eQ==} engines: {node: '>=14.0.0'} hasBin: true @@ -3960,9 +3904,9 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - slash@4.0.0: - resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} - engines: {node: '>=12'} + slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} slice-ansi@3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} @@ -3991,18 +3935,6 @@ packages: spawndamnit@2.0.0: resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.20: - resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} - split@0.3.3: resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} @@ -4022,8 +3954,8 @@ packages: engines: {node: '>=16'} hasBin: true - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} @@ -4100,10 +4032,6 @@ packages: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} - strip-indent@4.0.0: - resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} - engines: {node: '>=12'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -4153,13 +4081,8 @@ packages: tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - tailwindcss@3.4.12: - resolution: {integrity: sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==} - engines: {node: '>=14.0.0'} - hasBin: true - - tailwindcss@3.4.13: - resolution: {integrity: sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==} + tailwindcss@3.4.14: + resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==} engines: {node: '>=14.0.0'} hasBin: true @@ -4197,11 +4120,11 @@ packages: tinycolor2@1.4.2: resolution: {integrity: sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==} - tinyexec@0.3.0: - resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} + tinyexec@0.3.1: + resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} - tinyglobby@0.2.6: - resolution: {integrity: sha512-NbBoFBpqfcgd1tCiO8Lkfdk+xrA7mlLR9zgvZcZWQQwU63XAfUePyd6wZBaU93Hqw347lHnwFzttAkemHzzz4g==} + tinyglobby@0.2.10: + resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} engines: {node: '>=12.0.0'} tinypool@1.0.1: @@ -4216,6 +4139,13 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tldts-core@6.1.60: + resolution: {integrity: sha512-XHjoxak8SFQnHnmYHb3PcnW5TZ+9ErLZemZei3azuIRhQLw4IExsVbL3VZJdHcLeNaXq6NqawgpDPpjBOg4B5g==} + + tldts@6.1.60: + resolution: {integrity: sha512-TYVHm7G9NCnhgqOsFalbX6MG1Po5F4efF+tLfoeiOGQq48Oqgwcgz8upY2R1BHWa4aDrj28RYx0dkYJ63qCFMg==} + hasBin: true + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -4224,10 +4154,6 @@ packages: resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} engines: {node: '>=14.14'} - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -4239,9 +4165,9 @@ packages: resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} hasBin: true - tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} - engines: {node: '>=6'} + tough-cookie@5.0.0: + resolution: {integrity: sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==} + engines: {node: '>=16'} tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -4257,10 +4183,6 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - trim-newlines@4.1.1: - resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==} - engines: {node: '>=12'} - ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -4279,12 +4201,12 @@ packages: ts-proto-descriptors@2.0.0: resolution: {integrity: sha512-wHcTH3xIv11jxgkX5OyCSFfw27agpInAd6yh89hKG6zqIXnjW9SYqSER2CVQxdPj4czeOhGagNvZBEbJPy7qkw==} - ts-proto@2.2.0: - resolution: {integrity: sha512-xzmnyrarUjPnY+Py4RyTh3lYmL9w5t/oTtRTo2rKF8laAAahpGZ/ELxkXFEZns5JVbgkYke3C17HN5iNvZOs4g==} + ts-proto@2.2.7: + resolution: {integrity: sha512-O3LcgGzx/lVfjp6mPm15mYZH6tHIX6PXReXKEQqs1B2yWI8oquUxzpgxA98sa1XrdOjm/n+BYyei3NruXZyiFg==} hasBin: true - tsconfck@3.1.1: - resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==} + tsconfck@3.1.4: + resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==} engines: {node: ^18 || >=20} hasBin: true peerDependencies: @@ -4299,8 +4221,8 @@ packages: tslib@2.7.0: resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} - tsup@8.3.0: - resolution: {integrity: sha512-ALscEeyS03IomcuNdFdc0YWGVIkwH1Ws7nfTbAPuoILvEV2hpGQAY72LIOjglGo4ShWpZfpBqP/jpQVCzqYQag==} + tsup@8.3.5: + resolution: {integrity: sha512-Tunf6r6m6tnZsG9GYWndg0z8dEV7fD733VBFzFJ5Vcm1FtlXB8xBD/rtrBi2a3YKEV7hHtxiZtW5EAVADoe1pA==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -4321,38 +4243,38 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - turbo-darwin-64@2.1.2: - resolution: {integrity: sha512-3TEBxHWh99h2yIzkuIigMEOXt/ItYQp0aPiJjPd1xN4oDcsKK5AxiFKPH9pdtfIBzYsY59kQhZiFj0ELnSP7Bw==, tarball: https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.1.2.tgz} + turbo-darwin-64@2.2.3: + resolution: {integrity: sha512-Rcm10CuMKQGcdIBS3R/9PMeuYnv6beYIHqfZFeKWVYEWH69sauj4INs83zKMTUiZJ3/hWGZ4jet9AOwhsssLyg==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@2.1.2: - resolution: {integrity: sha512-he0miWNq2WxJzsH82jS2Z4MXpnkzn9SH8a79iPXiJkq25QREImucscM4RPasXm8wARp91pyysJMq6aasD45CeA==, tarball: https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.1.2.tgz} + turbo-darwin-arm64@2.2.3: + resolution: {integrity: sha512-+EIMHkuLFqUdJYsA3roj66t9+9IciCajgj+DVek+QezEdOJKcRxlvDOS2BUaeN8kEzVSsNiAGnoysFWYw4K0HA==} cpu: [arm64] os: [darwin] - turbo-linux-64@2.1.2: - resolution: {integrity: sha512-fKUBcc0rK8Vdqv5a/E3CSpMBLG1bzwv+Q0Q83F8fG2ZfNCNKGbcEYABdonNZkkx141Rj03cZQFCgxu3MVEGU+A==, tarball: https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.1.2.tgz} + turbo-linux-64@2.2.3: + resolution: {integrity: sha512-UBhJCYnqtaeOBQLmLo8BAisWbc9v9daL9G8upLR+XGj6vuN/Nz6qUAhverN4Pyej1g4Nt1BhROnj6GLOPYyqxQ==} cpu: [x64] os: [linux] - turbo-linux-arm64@2.1.2: - resolution: {integrity: sha512-sV8Bpmm0WiuxgbhxymcC7wSsuxfBBieI98GegSwbr/bs1ANAgzCg93urIrdKdQ3/b31zZxQwcaP4FBF1wx1Qdg==, tarball: https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.1.2.tgz} + turbo-linux-arm64@2.2.3: + resolution: {integrity: sha512-hJYT9dN06XCQ3jBka/EWvvAETnHRs3xuO/rb5bESmDfG+d9yQjeTMlhRXKrr4eyIMt6cLDt1LBfyi+6CQ+VAwQ==} cpu: [arm64] os: [linux] - turbo-windows-64@2.1.2: - resolution: {integrity: sha512-wcmIJZI9ORT9ykHGliFE6kWRQrlH930QGSjSgWC8uFChFFuOyUlvC7ttcxuSvU9VqC7NF4C+GVAcFJQ8lTjN7g==, tarball: https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.1.2.tgz} + turbo-windows-64@2.2.3: + resolution: {integrity: sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==} cpu: [x64] os: [win32] - turbo-windows-arm64@2.1.2: - resolution: {integrity: sha512-zdnXjrhk7YO6CP+Q5wPueEvOCLH4lDa6C4rrwiakcWcPgcQGbVozJlo4uaQ6awo8HLWQEvOwu84RkWTdLAc/Hw==, tarball: https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.1.2.tgz} + turbo-windows-arm64@2.2.3: + resolution: {integrity: sha512-fnNrYBCqn6zgKPKLHu4sOkihBI/+0oYFr075duRxqUZ+1aLWTAGfHZLgjVeLh3zR37CVzuerGIPWAEkNhkWEIw==} cpu: [arm64] os: [win32] - turbo@2.1.2: - resolution: {integrity: sha512-Jb0rbU4iHEVQ18An/YfakdIv9rKnd3zUfSE117EngrfWXFHo3RndVH96US3GsT8VHpwTncPePDBT2t06PaFLrw==} + turbo@2.2.3: + resolution: {integrity: sha512-5lDvSqIxCYJ/BAd6rQGK/AzFRhBkbu4JHVMLmGh/hCb7U3CqSnr5Tjwfy9vc+/5wG2DJ6wttgAaA7MoCgvBKZQ==} hasBin: true tweetnacl@0.14.5: @@ -4370,10 +4292,6 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - type-fest@1.4.0: - resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} - engines: {node: '>=10'} - typed-array-buffer@1.0.2: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} @@ -4390,8 +4308,8 @@ packages: resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} - typescript@5.6.2: - resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true @@ -4408,14 +4326,14 @@ packages: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} engines: {node: '>=14.0'} + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} - universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -4430,12 +4348,15 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - use-intl@3.20.0: resolution: {integrity: sha512-5WQs6yZVWI9K7vw3134P0bhKNp4mi8NbmqKOCuhD9nQUMTKdmpBXwjk62+axwvEbj4XrZxj4X93mQMLXU5ZsCg==} peerDependencies: @@ -4453,28 +4374,25 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - verror@1.10.0: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} - vite-node@2.1.1: - resolution: {integrity: sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==} + vite-node@2.1.4: + resolution: {integrity: sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite-tsconfig-paths@5.0.1: - resolution: {integrity: sha512-yqwv+LstU7NwPeNqajZzLEBVpUFU6Dugtb2P84FXuvaoYA+/70l9MHE+GYfYAycVyPSDYZ7mjOFuYBRqlEpTig==} + vite-tsconfig-paths@5.1.2: + resolution: {integrity: sha512-gEIbKfJzSEv0yR3XS2QEocKetONoWkbROj6hGx0FHM18qKUojhvcokQsxQx5nMkelZq2n37zbSGCJn+FSODSjA==} peerDependencies: vite: '*' peerDependenciesMeta: vite: optional: true - vite@5.4.6: - resolution: {integrity: sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==} + vite@5.4.11: + resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -4504,15 +4422,15 @@ packages: terser: optional: true - vitest@2.1.1: - resolution: {integrity: sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==} + vitest@2.1.4: + resolution: {integrity: sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.1 - '@vitest/ui': 2.1.1 + '@vitest/browser': 2.1.4 + '@vitest/ui': 2.1.4 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -4658,10 +4576,6 @@ packages: engines: {node: '>= 14'} hasBin: true - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -4688,30 +4602,26 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - '@babel/code-frame@7.24.7': + '@babel/code-frame@7.26.2': dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.1.0 + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 - '@babel/code-frame@7.25.7': - dependencies: - '@babel/highlight': 7.25.7 - picocolors: 1.1.0 + '@babel/compat-data@7.26.2': {} - '@babel/compat-data@7.25.4': {} - - '@babel/core@7.25.2': + '@babel/core@7.26.0': dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.6 - '@babel/helper-compilation-targets': 7.25.2 - '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) - '@babel/helpers': 7.25.6 - '@babel/parser': 7.25.6 - '@babel/template': 7.25.0 - '@babel/traverse': 7.25.6 - '@babel/types': 7.25.6 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 convert-source-map: 2.0.0 debug: 4.3.7(supports-color@5.5.0) gensync: 1.0.0-beta.2 @@ -4720,154 +4630,130 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.25.6': + '@babel/generator@7.26.2': dependencies: - '@babel/types': 7.25.6 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 + jsesc: 3.0.2 - '@babel/helper-compilation-targets@7.25.2': + '@babel/helper-compilation-targets@7.25.9': dependencies: - '@babel/compat-data': 7.25.4 - '@babel/helper-validator-option': 7.24.8 - browserslist: 4.23.3 + '@babel/compat-data': 7.26.2 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.2 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-module-imports@7.24.7': + '@babel/helper-module-imports@7.25.9': dependencies: - '@babel/traverse': 7.25.6 - '@babel/types': 7.25.6 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2)': + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - '@babel/traverse': 7.25.6 + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.24.8': {} + '@babel/helper-plugin-utils@7.25.9': {} - '@babel/helper-simple-access@7.24.7': + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.26.0': dependencies: - '@babel/traverse': 7.25.6 - '@babel/types': 7.25.6 - transitivePeerDependencies: - - supports-color + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 - '@babel/helper-string-parser@7.24.8': {} - - '@babel/helper-validator-identifier@7.24.7': {} - - '@babel/helper-validator-identifier@7.25.7': {} - - '@babel/helper-validator-option@7.24.8': {} - - '@babel/helpers@7.25.6': + '@babel/parser@7.26.2': dependencies: - '@babel/template': 7.25.0 - '@babel/types': 7.25.6 + '@babel/types': 7.26.0 - '@babel/highlight@7.24.7': + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/helper-validator-identifier': 7.24.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.1.0 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/highlight@7.25.7': + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/helper-validator-identifier': 7.25.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.1.0 - - '@babel/parser@7.25.6': - dependencies: - '@babel/types': 7.25.6 - - '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - - '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 '@babel/runtime@7.25.6': dependencies: regenerator-runtime: 0.14.1 - '@babel/runtime@7.25.7': + '@babel/runtime@7.26.0': dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.25.0': + '@babel/template@7.25.9': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 - '@babel/traverse@7.25.6': + '@babel/traverse@7.25.9': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.6 - '@babel/parser': 7.25.6 - '@babel/template': 7.25.0 - '@babel/types': 7.25.6 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 debug: 4.3.7(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.25.6': + '@babel/types@7.26.0': dependencies: - '@babel/helper-string-parser': 7.24.8 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 - '@bufbuild/buf-darwin-arm64@1.41.0': + '@bufbuild/buf-darwin-arm64@1.46.0': optional: true - '@bufbuild/buf-darwin-x64@1.41.0': + '@bufbuild/buf-darwin-x64@1.46.0': optional: true - '@bufbuild/buf-linux-aarch64@1.41.0': + '@bufbuild/buf-linux-aarch64@1.46.0': optional: true - '@bufbuild/buf-linux-x64@1.41.0': + '@bufbuild/buf-linux-x64@1.46.0': optional: true - '@bufbuild/buf-win32-arm64@1.41.0': + '@bufbuild/buf-win32-arm64@1.46.0': optional: true - '@bufbuild/buf-win32-x64@1.41.0': + '@bufbuild/buf-win32-x64@1.46.0': optional: true - '@bufbuild/buf@1.41.0': + '@bufbuild/buf@1.46.0': optionalDependencies: - '@bufbuild/buf-darwin-arm64': 1.41.0 - '@bufbuild/buf-darwin-x64': 1.41.0 - '@bufbuild/buf-linux-aarch64': 1.41.0 - '@bufbuild/buf-linux-x64': 1.41.0 - '@bufbuild/buf-win32-arm64': 1.41.0 - '@bufbuild/buf-win32-x64': 1.41.0 + '@bufbuild/buf-darwin-arm64': 1.46.0 + '@bufbuild/buf-darwin-x64': 1.46.0 + '@bufbuild/buf-linux-aarch64': 1.46.0 + '@bufbuild/buf-linux-x64': 1.46.0 + '@bufbuild/buf-win32-arm64': 1.46.0 + '@bufbuild/buf-win32-x64': 1.46.0 '@bufbuild/protobuf@2.0.0': {} '@bufbuild/protobuf@2.2.0': {} - '@bufbuild/protocompile@0.0.1(@bufbuild/buf@1.41.0)': + '@bufbuild/protocompile@0.0.1(@bufbuild/buf@1.46.0)': dependencies: - '@bufbuild/buf': 1.41.0 + '@bufbuild/buf': 1.46.0 '@bufbuild/protobuf': 2.2.0 fflate: 0.8.2 @@ -4900,7 +4786,7 @@ snapshots: dependencies: '@changesets/types': 6.0.0 - '@changesets/cli@2.27.8': + '@changesets/cli@2.27.9': dependencies: '@changesets/apply-release-plan': 7.0.5 '@changesets/assemble-release-plan': 6.0.4 @@ -4917,17 +4803,15 @@ snapshots: '@changesets/types': 6.0.0 '@changesets/write': 0.3.2 '@manypkg/get-packages': 1.1.3 - '@types/semver': 7.5.8 ansi-colors: 4.1.3 ci-info: 3.9.0 enquirer: 2.4.1 external-editor: 3.1.0 fs-extra: 7.0.1 mri: 1.2.0 - outdent: 0.5.0 p-limit: 2.3.0 - package-manager-detector: 0.2.0 - picocolors: 1.1.0 + package-manager-detector: 0.2.2 + picocolors: 1.1.1 resolve-from: 5.0.0 semver: 7.6.3 spawndamnit: 2.0.0 @@ -4951,7 +4835,7 @@ snapshots: dependencies: '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 - picocolors: 1.1.0 + picocolors: 1.1.1 semver: 7.6.3 '@changesets/get-release-plan@4.0.4': @@ -4975,7 +4859,7 @@ snapshots: '@changesets/logger@0.1.1': dependencies: - picocolors: 1.1.0 + picocolors: 1.1.1 '@changesets/parse@0.4.0': dependencies: @@ -4997,7 +4881,7 @@ snapshots: '@changesets/types': 6.0.0 fs-extra: 7.0.1 p-filter: 2.1.0 - picocolors: 1.1.0 + picocolors: 1.1.1 '@changesets/should-skip-package@0.1.1': dependencies: @@ -5037,7 +4921,7 @@ snapshots: dependencies: '@bufbuild/protobuf': 2.2.0 - '@cypress/request@3.0.1': + '@cypress/request@3.0.6': dependencies: aws-sign2: 0.7.0 aws4: 1.13.2 @@ -5045,16 +4929,16 @@ snapshots: combined-stream: 1.0.8 extend: 3.0.2 forever-agent: 0.6.1 - form-data: 2.3.3 - http-signature: 1.3.6 + form-data: 4.0.0 + http-signature: 1.4.0 is-typedarray: 1.0.0 isstream: 0.1.2 json-stringify-safe: 5.0.1 mime-types: 2.1.35 performance-now: 2.1.0 - qs: 6.10.4 + qs: 6.13.0 safe-buffer: 5.2.1 - tough-cookie: 4.1.4 + tough-cookie: 5.0.0 tunnel-agent: 0.6.0 uuid: 8.3.2 @@ -5068,142 +4952,142 @@ snapshots: '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/aix-ppc64@0.23.1': + '@esbuild/aix-ppc64@0.24.0': optional: true '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm64@0.23.1': + '@esbuild/android-arm64@0.24.0': optional: true '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-arm@0.23.1': + '@esbuild/android-arm@0.24.0': optional: true '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/android-x64@0.23.1': + '@esbuild/android-x64@0.24.0': optional: true '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.23.1': + '@esbuild/darwin-arm64@0.24.0': optional: true '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/darwin-x64@0.23.1': + '@esbuild/darwin-x64@0.24.0': optional: true '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.23.1': + '@esbuild/freebsd-arm64@0.24.0': optional: true '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.23.1': + '@esbuild/freebsd-x64@0.24.0': optional: true '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm64@0.23.1': + '@esbuild/linux-arm64@0.24.0': optional: true '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-arm@0.23.1': + '@esbuild/linux-arm@0.24.0': optional: true '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-ia32@0.23.1': + '@esbuild/linux-ia32@0.24.0': optional: true '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-loong64@0.23.1': + '@esbuild/linux-loong64@0.24.0': optional: true '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-mips64el@0.23.1': + '@esbuild/linux-mips64el@0.24.0': optional: true '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-ppc64@0.23.1': + '@esbuild/linux-ppc64@0.24.0': optional: true '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.23.1': + '@esbuild/linux-riscv64@0.24.0': optional: true '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-s390x@0.23.1': + '@esbuild/linux-s390x@0.24.0': optional: true '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/linux-x64@0.23.1': + '@esbuild/linux-x64@0.24.0': optional: true '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/netbsd-x64@0.23.1': + '@esbuild/netbsd-x64@0.24.0': optional: true - '@esbuild/openbsd-arm64@0.23.1': + '@esbuild/openbsd-arm64@0.24.0': optional: true '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.23.1': + '@esbuild/openbsd-x64@0.24.0': optional: true '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.23.1': + '@esbuild/sunos-x64@0.24.0': optional: true '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-arm64@0.23.1': + '@esbuild/win32-arm64@0.24.0': optional: true '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-ia32@0.23.1': + '@esbuild/win32-ia32@0.24.0': optional: true '@esbuild/win32-x64@0.21.5': optional: true - '@esbuild/win32-x64@0.23.1': + '@esbuild/win32-x64@0.24.0': optional: true '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)': @@ -5354,14 +5238,14 @@ snapshots: '@manypkg/find-root@1.1.0': dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -5430,12 +5314,73 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@parcel/watcher-android-arm64@2.5.0': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.0': + optional: true + + '@parcel/watcher-darwin-x64@2.5.0': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.0': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.0': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.0': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.0': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.0': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.0': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.0': + optional: true + + '@parcel/watcher-win32-arm64@2.5.0': + optional: true + + '@parcel/watcher-win32-ia32@2.5.0': + optional: true + + '@parcel/watcher-win32-x64@2.5.0': + optional: true + + '@parcel/watcher@2.5.0': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.0 + '@parcel/watcher-darwin-arm64': 2.5.0 + '@parcel/watcher-darwin-x64': 2.5.0 + '@parcel/watcher-freebsd-x64': 2.5.0 + '@parcel/watcher-linux-arm-glibc': 2.5.0 + '@parcel/watcher-linux-arm-musl': 2.5.0 + '@parcel/watcher-linux-arm64-glibc': 2.5.0 + '@parcel/watcher-linux-arm64-musl': 2.5.0 + '@parcel/watcher-linux-x64-glibc': 2.5.0 + '@parcel/watcher-linux-x64-musl': 2.5.0 + '@parcel/watcher-win32-arm64': 2.5.0 + '@parcel/watcher-win32-ia32': 2.5.0 + '@parcel/watcher-win32-x64': 2.5.0 + optional: true + '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.48.1': + '@playwright/test@1.48.2': dependencies: - playwright: 1.48.1 + playwright: 1.48.2 '@protobufjs/aspromise@1.1.2': {} @@ -5500,52 +5445,58 @@ snapshots: dependencies: react: 18.3.1 - '@rollup/rollup-android-arm-eabi@4.21.3': + '@rollup/rollup-android-arm-eabi@4.25.0': optional: true - '@rollup/rollup-android-arm64@4.21.3': + '@rollup/rollup-android-arm64@4.25.0': optional: true - '@rollup/rollup-darwin-arm64@4.21.3': + '@rollup/rollup-darwin-arm64@4.25.0': optional: true - '@rollup/rollup-darwin-x64@4.21.3': + '@rollup/rollup-darwin-x64@4.25.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.21.3': + '@rollup/rollup-freebsd-arm64@4.25.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.21.3': + '@rollup/rollup-freebsd-x64@4.25.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.21.3': + '@rollup/rollup-linux-arm-gnueabihf@4.25.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.21.3': + '@rollup/rollup-linux-arm-musleabihf@4.25.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.21.3': + '@rollup/rollup-linux-arm64-gnu@4.25.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.21.3': + '@rollup/rollup-linux-arm64-musl@4.25.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.21.3': + '@rollup/rollup-linux-powerpc64le-gnu@4.25.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.21.3': + '@rollup/rollup-linux-riscv64-gnu@4.25.0': optional: true - '@rollup/rollup-linux-x64-musl@4.21.3': + '@rollup/rollup-linux-s390x-gnu@4.25.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.21.3': + '@rollup/rollup-linux-x64-gnu@4.25.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.21.3': + '@rollup/rollup-linux-x64-musl@4.25.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.21.3': + '@rollup/rollup-win32-arm64-msvc@4.25.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.25.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.25.0': optional: true '@rushstack/eslint-patch@1.10.4': {} @@ -5558,6 +5509,8 @@ snapshots: '@sideway/pinpoint@2.0.0': {} + '@sindresorhus/merge-streams@2.3.0': {} + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.5': @@ -5565,15 +5518,15 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.7.0 - '@tailwindcss/forms@0.5.3(tailwindcss@3.4.12)': + '@tailwindcss/forms@0.5.3(tailwindcss@3.4.14)': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.12 + tailwindcss: 3.4.14 - '@tailwindcss/forms@0.5.7(tailwindcss@3.4.13)': + '@tailwindcss/forms@0.5.7(tailwindcss@3.4.14)': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.13 + tailwindcss: 3.4.14 '@tanstack/react-virtual@3.10.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -5585,8 +5538,8 @@ snapshots: '@testing-library/dom@10.4.0': dependencies: - '@babel/code-frame': 7.25.7 - '@babel/runtime': 7.25.7 + '@babel/code-frame': 7.26.2 + '@babel/runtime': 7.26.0 '@types/aria-query': 5.0.4 aria-query: 5.3.0 chalk: 4.1.2 @@ -5594,7 +5547,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.5.0': + '@testing-library/jest-dom@6.6.3': dependencies: '@adobe/css-tools': 4.4.0 aria-query: 5.3.0 @@ -5604,72 +5557,62 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.25.6 '@testing-library/dom': 10.4.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.25.6 + '@babel/types': 7.26.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.25.6 + '@babel/types': 7.26.0 - '@types/estree@1.0.5': {} + '@types/estree@1.0.6': {} '@types/json5@0.0.29': {} - '@types/minimist@1.2.5': {} - '@types/ms@0.7.34': {} '@types/node@12.20.55': {} - '@types/node@22.5.5': + '@types/node@22.9.0': dependencies: undici-types: 6.19.8 - '@types/node@22.7.6': - dependencies: - undici-types: 6.19.8 - - '@types/normalize-package-data@2.4.4': {} - '@types/prop-types@15.7.12': {} - '@types/react-dom@18.3.0': + '@types/react-dom@18.3.1': dependencies: - '@types/react': 18.3.7 + '@types/react': 18.3.12 - '@types/react@18.3.7': + '@types/react@18.3.12': dependencies: '@types/prop-types': 15.7.12 csstype: 3.1.3 - '@types/semver@7.5.8': {} - '@types/sinonjs__fake-timers@8.1.1': {} '@types/sizzle@2.3.8': {} @@ -5680,19 +5623,19 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.7.6 + '@types/node': 22.9.0 optional: true - '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2)': + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3)': dependencies: '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 7.18.0 debug: 4.3.6 eslint: 8.57.1 optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -5703,7 +5646,7 @@ snapshots: '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.6.2)': + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.6.3)': dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 @@ -5712,9 +5655,9 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -5725,64 +5668,64 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/analytics@1.3.1(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1)': + '@vercel/analytics@1.3.1(next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7))(react@18.3.1)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next: 14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7) react: 18.3.1 '@vercel/git-hooks@1.0.0': {} - '@vitejs/plugin-react@4.3.1(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1))': + '@vitejs/plugin-react@4.3.3(vite@5.4.11(@types/node@22.9.0)(sass@1.80.7))': dependencies: - '@babel/core': 7.25.2 - '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) + '@babel/core': 7.26.0 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) + vite: 5.4.11(@types/node@22.9.0)(sass@1.80.7) transitivePeerDependencies: - supports-color - '@vitest/expect@2.1.1': + '@vitest/expect@2.1.4': dependencies: - '@vitest/spy': 2.1.1 - '@vitest/utils': 2.1.1 - chai: 5.1.1 + '@vitest/spy': 2.1.4 + '@vitest/utils': 2.1.4 + chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1))': + '@vitest/mocker@2.1.4(vite@5.4.11(@types/node@22.9.0)(sass@1.80.7))': dependencies: - '@vitest/spy': 2.1.1 + '@vitest/spy': 2.1.4 estree-walker: 3.0.3 - magic-string: 0.30.11 + magic-string: 0.30.12 optionalDependencies: - vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) + vite: 5.4.11(@types/node@22.9.0)(sass@1.80.7) - '@vitest/pretty-format@2.1.1': + '@vitest/pretty-format@2.1.4': dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.1.1': + '@vitest/runner@2.1.4': dependencies: - '@vitest/utils': 2.1.1 + '@vitest/utils': 2.1.4 pathe: 1.1.2 - '@vitest/snapshot@2.1.1': + '@vitest/snapshot@2.1.4': dependencies: - '@vitest/pretty-format': 2.1.1 - magic-string: 0.30.11 + '@vitest/pretty-format': 2.1.4 + magic-string: 0.30.12 pathe: 1.1.2 - '@vitest/spy@2.1.1': + '@vitest/spy@2.1.4': dependencies: tinyspy: 3.0.2 - '@vitest/utils@2.1.1': + '@vitest/utils@2.1.4': dependencies: - '@vitest/pretty-format': 2.1.1 - loupe: 3.1.1 + '@vitest/pretty-format': 2.1.4 + loupe: 3.1.2 tinyrainbow: 1.2.0 abbrev@1.1.1: {} @@ -5812,11 +5755,6 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 - aggregate-error@4.0.1: - dependencies: - clean-stack: 4.2.0 - indent-string: 5.0.0 - ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -5838,10 +5776,6 @@ snapshots: ansi-regex@6.1.0: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -5949,8 +5883,6 @@ snapshots: is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 - arrify@1.0.1: {} - asn1@0.2.6: dependencies: safer-buffer: 2.1.2 @@ -5969,14 +5901,14 @@ snapshots: at-least-node@1.0.0: {} - autoprefixer@10.4.20(postcss@8.4.47): + autoprefixer@10.4.20(postcss@8.4.49): dependencies: browserslist: 4.23.3 caniuse-lite: 1.0.30001654 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.1 - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: @@ -6039,6 +5971,13 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) + browserslist@4.24.2: + dependencies: + caniuse-lite: 1.0.30001680 + electron-to-chromium: 1.5.57 + node-releases: 2.0.18 + update-browserslist-db: 1.1.1(browserslist@4.24.2) + buffer-crc32@0.2.13: {} buffer@5.7.1: @@ -6046,9 +5985,9 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - bundle-require@5.0.0(esbuild@0.23.1): + bundle-require@5.0.0(esbuild@0.24.0): dependencies: - esbuild: 0.23.1 + esbuild: 0.24.0 load-tsconfig: 0.2.5 busboy@1.6.0: @@ -6071,37 +6010,24 @@ snapshots: camelcase-css@2.0.1: {} - camelcase-keys@7.0.2: - dependencies: - camelcase: 6.3.0 - map-obj: 4.3.0 - quick-lru: 5.1.1 - type-fest: 1.4.0 - - camelcase@6.3.0: {} - caniuse-lite@1.0.30001654: {} caniuse-lite@1.0.30001666: {} + caniuse-lite@1.0.30001680: {} + case-anything@2.1.13: {} caseless@0.12.0: {} - chai@5.1.1: + chai@5.1.2: dependencies: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.1 + loupe: 3.1.2 pathval: 2.0.0 - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - chalk@3.0.0: dependencies: ansi-styles: 4.3.0 @@ -6132,19 +6058,17 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - chokidar@4.0.0: + chokidar@4.0.1: dependencies: - readdirp: 4.0.1 + readdirp: 4.0.2 chownr@2.0.0: {} ci-info@3.9.0: {} - clean-stack@2.2.0: {} + ci-info@4.1.0: {} - clean-stack@4.2.0: - dependencies: - escape-string-regexp: 5.0.0 + clean-stack@2.2.0: {} cli-cursor@3.1.0: dependencies: @@ -6182,16 +6106,10 @@ snapshots: clsx@2.1.1: {} - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} color-support@1.1.3: {} @@ -6212,7 +6130,7 @@ snapshots: concat-map@0.0.1: {} - concurrently@9.0.1: + concurrently@9.1.0: dependencies: chalk: 4.1.2 lodash: 4.17.21 @@ -6246,19 +6164,25 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + cross-spawn@7.0.5: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + css.escape@1.5.1: {} cssesc@3.0.0: {} - cssstyle@4.0.1: + cssstyle@4.1.0: dependencies: - rrweb-cssom: 0.6.0 + rrweb-cssom: 0.7.1 csstype@3.1.3: {} - cypress@13.14.2: + cypress@13.15.2: dependencies: - '@cypress/request': 3.0.1 + '@cypress/request': 3.0.6 '@cypress/xvfb': 1.2.4(supports-color@8.1.1) '@types/sinonjs__fake-timers': 8.1.1 '@types/sizzle': 2.3.8 @@ -6269,6 +6193,7 @@ snapshots: cachedir: 2.4.0 chalk: 4.1.2 check-more-types: 2.24.0 + ci-info: 4.1.0 cli-cursor: 3.1.0 cli-table3: 0.6.5 commander: 6.2.1 @@ -6283,7 +6208,6 @@ snapshots: figures: 3.2.0 fs-extra: 9.1.0 getos: 3.2.1 - is-ci: 3.0.1 is-installed-globally: 0.4.0 lazy-ass: 1.6.0 listr2: 3.14.0(enquirer@2.4.1) @@ -6298,6 +6222,7 @@ snapshots: semver: 7.6.3 supports-color: 8.1.1 tmp: 0.2.3 + tree-kill: 1.2.2 untildify: 4.0.0 yauzl: 2.10.0 @@ -6354,15 +6279,6 @@ snapshots: optionalDependencies: supports-color: 8.1.1 - decamelize-keys@1.1.1: - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - - decamelize@1.2.0: {} - - decamelize@5.0.1: {} - decimal.js@10.4.3: {} deep-eql@5.0.2: {} @@ -6404,21 +6320,19 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - del-cli@5.1.0: + del-cli@6.0.0: dependencies: - del: 7.1.0 - meow: 10.1.5 + del: 8.0.0 + meow: 13.2.0 - del@7.1.0: + del@8.0.0: dependencies: - globby: 13.2.2 - graceful-fs: 4.2.11 + globby: 14.0.2 is-glob: 4.0.3 is-path-cwd: 3.0.0 is-path-inside: 4.0.0 - p-map: 5.5.0 - rimraf: 3.0.2 - slash: 4.0.0 + p-map: 7.0.2 + slash: 5.1.0 delayed-stream@1.0.0: {} @@ -6469,6 +6383,8 @@ snapshots: electron-to-chromium@1.5.13: {} + electron-to-chromium@1.5.57: {} + emoji-regex@10.4.0: {} emoji-regex@8.0.0: {} @@ -6498,10 +6414,6 @@ snapshots: environment@1.1.0: {} - error-ex@1.3.2: - dependencies: - is-arrayish: 0.2.1 - es-abstract@1.23.3: dependencies: array-buffer-byte-length: 1.0.1 @@ -6632,32 +6544,32 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - esbuild@0.23.1: + esbuild@0.24.0: optionalDependencies: - '@esbuild/aix-ppc64': 0.23.1 - '@esbuild/android-arm': 0.23.1 - '@esbuild/android-arm64': 0.23.1 - '@esbuild/android-x64': 0.23.1 - '@esbuild/darwin-arm64': 0.23.1 - '@esbuild/darwin-x64': 0.23.1 - '@esbuild/freebsd-arm64': 0.23.1 - '@esbuild/freebsd-x64': 0.23.1 - '@esbuild/linux-arm': 0.23.1 - '@esbuild/linux-arm64': 0.23.1 - '@esbuild/linux-ia32': 0.23.1 - '@esbuild/linux-loong64': 0.23.1 - '@esbuild/linux-mips64el': 0.23.1 - '@esbuild/linux-ppc64': 0.23.1 - '@esbuild/linux-riscv64': 0.23.1 - '@esbuild/linux-s390x': 0.23.1 - '@esbuild/linux-x64': 0.23.1 - '@esbuild/netbsd-x64': 0.23.1 - '@esbuild/openbsd-arm64': 0.23.1 - '@esbuild/openbsd-x64': 0.23.1 - '@esbuild/sunos-x64': 0.23.1 - '@esbuild/win32-arm64': 0.23.1 - '@esbuild/win32-ia32': 0.23.1 - '@esbuild/win32-x64': 0.23.1 + '@esbuild/aix-ppc64': 0.24.0 + '@esbuild/android-arm': 0.24.0 + '@esbuild/android-arm64': 0.24.0 + '@esbuild/android-x64': 0.24.0 + '@esbuild/darwin-arm64': 0.24.0 + '@esbuild/darwin-x64': 0.24.0 + '@esbuild/freebsd-arm64': 0.24.0 + '@esbuild/freebsd-x64': 0.24.0 + '@esbuild/linux-arm': 0.24.0 + '@esbuild/linux-arm64': 0.24.0 + '@esbuild/linux-ia32': 0.24.0 + '@esbuild/linux-loong64': 0.24.0 + '@esbuild/linux-mips64el': 0.24.0 + '@esbuild/linux-ppc64': 0.24.0 + '@esbuild/linux-riscv64': 0.24.0 + '@esbuild/linux-s390x': 0.24.0 + '@esbuild/linux-x64': 0.24.0 + '@esbuild/netbsd-x64': 0.24.0 + '@esbuild/openbsd-arm64': 0.24.0 + '@esbuild/openbsd-x64': 0.24.0 + '@esbuild/sunos-x64': 0.24.0 + '@esbuild/win32-arm64': 0.24.0 + '@esbuild/win32-ia32': 0.24.0 + '@esbuild/win32-x64': 0.24.0 escalade@3.2.0: {} @@ -6665,22 +6577,20 @@ snapshots: escape-string-regexp@4.0.0: {} - escape-string-regexp@5.0.0: {} - - eslint-config-next@14.2.7(eslint@8.57.1)(typescript@5.6.2): + eslint-config-next@14.2.7(eslint@8.57.1)(typescript@5.6.3): dependencies: '@next/eslint-plugin-next': 14.2.7 '@rushstack/eslint-patch': 1.10.4 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.2) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.1) eslint-plugin-react: 7.35.0(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - eslint-import-resolver-webpack - eslint-plugin-import-x @@ -6703,37 +6613,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.2) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -6743,7 +6653,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -6754,7 +6664,7 @@ snapshots: semver: 6.3.1 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.2) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -6881,7 +6791,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 esutils@2.0.3: {} @@ -6901,7 +6811,7 @@ snapshots: execa@4.1.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.5 get-stream: 5.2.0 human-signals: 1.1.1 is-stream: 2.0.1 @@ -6939,6 +6849,8 @@ snapshots: dependencies: pify: 2.3.0 + expect-type@1.1.0: {} + extend@3.0.2: {} extendable-error@0.1.7: {} @@ -6983,7 +6895,7 @@ snapshots: dependencies: pend: 1.2.0 - fdir@6.3.0(picomatch@4.0.2): + fdir@6.4.2(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -7029,17 +6941,11 @@ snapshots: foreground-child@3.3.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.5 signal-exit: 4.1.0 forever-agent@0.6.1: {} - form-data@2.3.3: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - form-data@4.0.0: dependencies: asynckit: 0.4.0 @@ -7110,8 +7016,6 @@ snapshots: get-east-asian-width@1.2.0: {} - get-func-name@2.0.2: {} - get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -7168,7 +7072,7 @@ snapshots: jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 - package-json-from-dist: 1.0.0 + package-json-from-dist: 1.0.1 path-scurry: 1.11.1 glob@7.2.3: @@ -7204,13 +7108,14 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - globby@13.2.2: + globby@14.0.2: dependencies: - dir-glob: 3.0.1 + '@sindresorhus/merge-streams': 2.3.0 fast-glob: 3.3.2 ignore: 5.3.2 - merge2: 1.4.1 - slash: 4.0.0 + path-type: 5.0.0 + slash: 5.1.0 + unicorn-magic: 0.1.0 globrex@0.1.2: {} @@ -7229,8 +7134,6 @@ snapshots: - encoding - supports-color - hard-rejection@2.1.0: {} - has-bigints@1.0.2: {} has-flag@3.0.0: {} @@ -7255,10 +7158,6 @@ snapshots: dependencies: function-bind: 1.1.2 - hosted-git-info@4.1.0: - dependencies: - lru-cache: 6.0.0 - html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 @@ -7270,7 +7169,7 @@ snapshots: transitivePeerDependencies: - supports-color - http-signature@1.3.6: + http-signature@1.4.0: dependencies: assert-plus: 1.0.0 jsprim: 2.0.2 @@ -7312,7 +7211,7 @@ snapshots: ignore@5.3.2: {} - immutable@4.3.7: {} + immutable@5.0.2: {} import-fresh@3.3.0: dependencies: @@ -7323,8 +7222,6 @@ snapshots: indent-string@4.0.0: {} - indent-string@5.0.0: {} - inflight@1.0.6: dependencies: once: 1.4.0 @@ -7357,8 +7254,6 @@ snapshots: call-bind: 1.0.7 get-intrinsic: 1.2.4 - is-arrayish@0.2.1: {} - is-async-function@2.0.0: dependencies: has-tostringtag: 1.0.2 @@ -7382,10 +7277,6 @@ snapshots: is-callable@1.2.7: {} - is-ci@3.0.1: - dependencies: - ci-info: 3.9.0 - is-core-module@2.15.1: dependencies: hasown: 2.0.2 @@ -7441,8 +7332,6 @@ snapshots: is-path-inside@4.0.0: {} - is-plain-obj@1.1.0: {} - is-potential-custom-element-name@1.0.1: {} is-regex@1.1.4: @@ -7546,9 +7435,9 @@ snapshots: jsbn@0.1.1: {} - jsdom@25.0.0: + jsdom@25.0.1: dependencies: - cssstyle: 4.0.1 + cssstyle: 4.1.0 data-urls: 5.0.0 decimal.js: 10.4.3 form-data: 4.0.0 @@ -7561,7 +7450,7 @@ snapshots: rrweb-cssom: 0.7.1 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 4.1.4 + tough-cookie: 5.0.0 w3c-xmlserializer: 5.0.0 webidl-conversions: 7.0.0 whatwg-encoding: 3.1.1 @@ -7574,12 +7463,10 @@ snapshots: - supports-color - utf-8-validate - jsesc@2.5.2: {} + jsesc@3.0.2: {} json-buffer@3.0.1: {} - json-parse-even-better-errors@2.3.1: {} - json-schema-traverse@0.4.1: {} json-schema@0.4.0: {} @@ -7622,8 +7509,6 @@ snapshots: dependencies: json-buffer: 3.0.1 - kind-of@6.0.3: {} - language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -7728,9 +7613,7 @@ snapshots: dependencies: js-tokens: 4.0.0 - loupe@3.1.1: - dependencies: - get-func-name: 2.0.2 + loupe@3.1.2: {} lru-cache@10.4.3: {} @@ -7743,13 +7626,9 @@ snapshots: dependencies: yallist: 3.1.1 - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - lz-string@1.5.0: {} - magic-string@0.30.11: + magic-string@0.30.12: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -7764,27 +7643,8 @@ snapshots: make-dir@5.0.0: {} - map-obj@1.0.1: {} - - map-obj@4.3.0: {} - map-stream@0.1.0: {} - meow@10.1.5: - dependencies: - '@types/minimist': 1.2.5 - camelcase-keys: 7.0.2 - decamelize: 5.0.1 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 8.0.0 - redent: 4.0.0 - trim-newlines: 4.1.1 - type-fest: 1.4.0 - yargs-parser: 20.2.9 - meow@13.2.0: {} merge-stream@2.0.0: {} @@ -7820,12 +7680,6 @@ snapshots: dependencies: brace-expansion: 2.0.1 - minimist-options@4.1.0: - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - minimist@1.2.8: {} minipass@3.3.6: @@ -7863,21 +7717,21 @@ snapshots: negotiator@0.6.3: {} - next-intl@3.20.0(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1): + next-intl@3.20.0(next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7))(react@18.3.1): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next: 14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next: 14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7) react: 18.3.1 use-intl: 3.20.0(react@18.3.1) - next-themes@0.2.1(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-themes@0.2.1(next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next: 14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1): + next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7): dependencies: '@next/env': 14.2.14 '@swc/helpers': 0.5.5 @@ -7887,7 +7741,7 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.26.0)(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 14.2.14 '@next/swc-darwin-x64': 14.2.14 @@ -7898,8 +7752,8 @@ snapshots: '@next/swc-win32-arm64-msvc': 14.2.14 '@next/swc-win32-ia32-msvc': 14.2.14 '@next/swc-win32-x64-msvc': 14.2.14 - '@playwright/test': 1.48.1 - sass: 1.79.1 + '@playwright/test': 1.48.2 + sass: 1.80.7 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -7914,13 +7768,16 @@ snapshots: abort-controller-x: 0.4.3 nice-grpc-common: 2.0.2 + node-addon-api@7.1.1: + optional: true + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 node-releases@2.0.18: {} - nodemon@3.1.5: + nodemon@3.1.7: dependencies: chokidar: 3.6.0 debug: 4.3.7(supports-color@5.5.0) @@ -7937,13 +7794,6 @@ snapshots: dependencies: abbrev: 1.1.1 - normalize-package-data@3.0.3: - dependencies: - hosted-git-info: 4.1.0 - is-core-module: 2.15.1 - semver: 7.6.3 - validate-npm-package-license: 3.0.4 - normalize-path@3.0.0: {} normalize-range@0.1.2: {} @@ -8067,27 +7917,18 @@ snapshots: dependencies: aggregate-error: 3.1.0 - p-map@5.5.0: - dependencies: - aggregate-error: 4.0.1 + p-map@7.0.2: {} p-try@2.2.0: {} - package-json-from-dist@1.0.0: {} + package-json-from-dist@1.0.1: {} - package-manager-detector@0.2.0: {} + package-manager-detector@0.2.2: {} parent-module@1.0.1: dependencies: callsites: 3.1.0 - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.24.7 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - parse5@7.1.2: dependencies: entities: 4.5.0 @@ -8109,6 +7950,8 @@ snapshots: path-type@4.0.0: {} + path-type@5.0.0: {} + pathe@1.1.2: {} pathval@2.0.0: {} @@ -8123,7 +7966,7 @@ snapshots: picocolors@1.0.1: {} - picocolors@1.1.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -8137,46 +7980,46 @@ snapshots: pirates@4.0.6: {} - playwright-core@1.48.1: {} + playwright-core@1.48.2: {} - playwright@1.48.1: + playwright@1.48.2: dependencies: - playwright-core: 1.48.1 + playwright-core: 1.48.2 optionalDependencies: fsevents: 2.3.2 possible-typed-array-names@1.0.0: {} - postcss-import@15.1.0(postcss@8.4.47): + postcss-import@15.1.0(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-js@4.0.1(postcss@8.4.47): + postcss-js@4.0.1(postcss@8.4.49): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.47 + postcss: 8.4.49 - postcss-load-config@4.0.2(postcss@8.4.47): + postcss-load-config@4.0.2(postcss@8.4.49): dependencies: lilconfig: 3.1.2 yaml: 2.5.0 optionalDependencies: - postcss: 8.4.47 + postcss: 8.4.49 - postcss-load-config@6.0.1(jiti@1.21.6)(postcss@8.4.47)(yaml@2.5.0): + postcss-load-config@6.0.1(jiti@1.21.6)(postcss@8.4.49)(yaml@2.5.0): dependencies: lilconfig: 3.1.2 optionalDependencies: jiti: 1.21.6 - postcss: 8.4.47 + postcss: 8.4.49 yaml: 2.5.0 - postcss-nested@6.2.0(postcss@8.4.47): + postcss-nested@6.2.0(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-selector-parser: 6.1.2 postcss-selector-parser@6.1.2: @@ -8189,27 +8032,27 @@ snapshots: postcss@8.4.31: dependencies: nanoid: 3.3.7 - picocolors: 1.1.0 + picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.4.47: + postcss@8.4.49: dependencies: nanoid: 3.3.7 - picocolors: 1.1.0 + picocolors: 1.1.1 source-map-js: 1.2.1 prelude-ls@1.2.1: {} - prettier-plugin-organize-imports@4.0.0(prettier@3.3.3)(typescript@5.6.2): + prettier-plugin-organize-imports@4.1.0(prettier@3.3.3)(typescript@5.6.3): dependencies: prettier: 3.3.3 - typescript: 5.6.2 + typescript: 5.6.3 - prettier-plugin-tailwindcss@0.6.6(prettier-plugin-organize-imports@4.0.0(prettier@3.3.3)(typescript@5.6.2))(prettier@3.3.3): + prettier-plugin-tailwindcss@0.6.8(prettier-plugin-organize-imports@4.1.0(prettier@3.3.3)(typescript@5.6.3))(prettier@3.3.3): dependencies: prettier: 3.3.3 optionalDependencies: - prettier-plugin-organize-imports: 4.0.0(prettier@3.3.3)(typescript@5.6.2) + prettier-plugin-organize-imports: 4.1.0(prettier@3.3.3)(typescript@5.6.3) prettier@2.8.8: {} @@ -8243,7 +8086,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.7.6 + '@types/node': 22.9.0 long: 5.2.3 proxy-from-env@1.0.0: {} @@ -8256,8 +8099,6 @@ snapshots: pseudomap@1.0.2: {} - psl@1.9.0: {} - pstree.remy@1.1.8: {} pump@3.0.0: @@ -8271,16 +8112,12 @@ snapshots: dependencies: react: 18.3.1 - qs@6.10.4: + qs@6.13.0: dependencies: side-channel: 1.0.6 - querystringify@2.2.0: {} - queue-microtask@1.2.3: {} - quick-lru@5.1.1: {} - react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -8305,19 +8142,6 @@ snapshots: dependencies: pify: 2.3.0 - read-pkg-up@8.0.0: - dependencies: - find-up: 5.0.0 - read-pkg: 6.0.0 - type-fest: 1.4.0 - - read-pkg@6.0.0: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 3.0.3 - parse-json: 5.2.0 - type-fest: 1.4.0 - read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -8335,18 +8159,13 @@ snapshots: dependencies: picomatch: 2.3.1 - readdirp@4.0.1: {} + readdirp@4.0.2: {} redent@3.0.0: dependencies: indent-string: 4.0.0 strip-indent: 3.0.0 - redent@4.0.0: - dependencies: - indent-string: 5.0.0 - strip-indent: 4.0.0 - reflect.getprototypeof@1.0.6: dependencies: call-bind: 1.0.7 @@ -8372,8 +8191,6 @@ snapshots: require-directory@2.1.1: {} - requires-port@1.0.0: {} - resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -8410,30 +8227,30 @@ snapshots: dependencies: glob: 7.2.3 - rollup@4.21.3: + rollup@4.25.0: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.21.3 - '@rollup/rollup-android-arm64': 4.21.3 - '@rollup/rollup-darwin-arm64': 4.21.3 - '@rollup/rollup-darwin-x64': 4.21.3 - '@rollup/rollup-linux-arm-gnueabihf': 4.21.3 - '@rollup/rollup-linux-arm-musleabihf': 4.21.3 - '@rollup/rollup-linux-arm64-gnu': 4.21.3 - '@rollup/rollup-linux-arm64-musl': 4.21.3 - '@rollup/rollup-linux-powerpc64le-gnu': 4.21.3 - '@rollup/rollup-linux-riscv64-gnu': 4.21.3 - '@rollup/rollup-linux-s390x-gnu': 4.21.3 - '@rollup/rollup-linux-x64-gnu': 4.21.3 - '@rollup/rollup-linux-x64-musl': 4.21.3 - '@rollup/rollup-win32-arm64-msvc': 4.21.3 - '@rollup/rollup-win32-ia32-msvc': 4.21.3 - '@rollup/rollup-win32-x64-msvc': 4.21.3 + '@rollup/rollup-android-arm-eabi': 4.25.0 + '@rollup/rollup-android-arm64': 4.25.0 + '@rollup/rollup-darwin-arm64': 4.25.0 + '@rollup/rollup-darwin-x64': 4.25.0 + '@rollup/rollup-freebsd-arm64': 4.25.0 + '@rollup/rollup-freebsd-x64': 4.25.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.25.0 + '@rollup/rollup-linux-arm-musleabihf': 4.25.0 + '@rollup/rollup-linux-arm64-gnu': 4.25.0 + '@rollup/rollup-linux-arm64-musl': 4.25.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.25.0 + '@rollup/rollup-linux-riscv64-gnu': 4.25.0 + '@rollup/rollup-linux-s390x-gnu': 4.25.0 + '@rollup/rollup-linux-x64-gnu': 4.25.0 + '@rollup/rollup-linux-x64-musl': 4.25.0 + '@rollup/rollup-win32-arm64-msvc': 4.25.0 + '@rollup/rollup-win32-ia32-msvc': 4.25.0 + '@rollup/rollup-win32-x64-msvc': 4.25.0 fsevents: 2.3.3 - rrweb-cssom@0.6.0: {} - rrweb-cssom@0.7.1: {} run-parallel@1.2.0: @@ -8461,11 +8278,13 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.79.1: + sass@1.80.7: dependencies: - chokidar: 4.0.0 - immutable: 4.3.7 + chokidar: 4.0.1 + immutable: 5.0.2 source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.0 saxes@6.0.0: dependencies: @@ -8532,7 +8351,7 @@ snapshots: slash@3.0.0: {} - slash@4.0.0: {} + slash@5.1.0: {} slice-ansi@3.0.0: dependencies: @@ -8567,20 +8386,6 @@ snapshots: cross-spawn: 5.1.0 signal-exit: 3.0.7 - spdx-correct@3.2.0: - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.20 - - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.20 - - spdx-license-ids@3.0.20: {} - split@0.3.3: dependencies: through: 2.3.8 @@ -8614,7 +8419,7 @@ snapshots: transitivePeerDependencies: - supports-color - std-env@3.7.0: {} + std-env@3.8.0: {} stop-iteration-iterator@1.0.0: dependencies: @@ -8712,18 +8517,14 @@ snapshots: dependencies: min-indent: 1.0.1 - strip-indent@4.0.0: - dependencies: - min-indent: 1.0.1 - strip-json-comments@3.1.1: {} - styled-jsx@5.1.1(@babel/core@7.25.2)(react@18.3.1): + styled-jsx@5.1.1(@babel/core@7.26.0)(react@18.3.1): dependencies: client-only: 0.0.1 react: 18.3.1 optionalDependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.0 sucrase@3.35.0: dependencies: @@ -8759,7 +8560,7 @@ snapshots: tabbable@6.2.0: {} - tailwindcss@3.4.12: + tailwindcss@3.4.14: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -8774,39 +8575,12 @@ snapshots: micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 - picocolors: 1.1.0 - postcss: 8.4.47 - postcss-import: 15.1.0(postcss@8.4.47) - postcss-js: 4.0.1(postcss@8.4.47) - postcss-load-config: 4.0.2(postcss@8.4.47) - postcss-nested: 6.2.0(postcss@8.4.47) - postcss-selector-parser: 6.1.2 - resolve: 1.22.8 - sucrase: 3.35.0 - transitivePeerDependencies: - - ts-node - - tailwindcss@3.4.13: - dependencies: - '@alloc/quick-lru': 5.2.0 - arg: 5.0.2 - chokidar: 3.6.0 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.2 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.21.6 - lilconfig: 2.1.0 - micromatch: 4.0.8 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.1.0 - postcss: 8.4.47 - postcss-import: 15.1.0(postcss@8.4.47) - postcss-js: 4.0.1(postcss@8.4.47) - postcss-load-config: 4.0.2(postcss@8.4.47) - postcss-nested: 6.2.0(postcss@8.4.47) + picocolors: 1.1.1 + postcss: 8.4.49 + postcss-import: 15.1.0(postcss@8.4.49) + postcss-js: 4.0.1(postcss@8.4.49) + postcss-load-config: 4.0.2(postcss@8.4.49) + postcss-nested: 6.2.0(postcss@8.4.49) postcss-selector-parser: 6.1.2 resolve: 1.22.8 sucrase: 3.35.0 @@ -8844,11 +8618,11 @@ snapshots: tinycolor2@1.4.2: {} - tinyexec@0.3.0: {} + tinyexec@0.3.1: {} - tinyglobby@0.2.6: + tinyglobby@0.2.10: dependencies: - fdir: 6.3.0(picomatch@4.0.2) + fdir: 6.4.2(picomatch@4.0.2) picomatch: 4.0.2 tinypool@1.0.1: {} @@ -8857,14 +8631,18 @@ snapshots: tinyspy@3.0.2: {} + tldts-core@6.1.60: {} + + tldts@6.1.60: + dependencies: + tldts-core: 6.1.60 + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 tmp@0.2.3: {} - to-fast-properties@2.0.0: {} - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -8873,12 +8651,9 @@ snapshots: touch@3.1.1: {} - tough-cookie@4.1.4: + tough-cookie@5.0.0: dependencies: - psl: 1.9.0 - punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 + tldts: 6.1.60 tr46@0.0.3: {} @@ -8892,11 +8667,9 @@ snapshots: tree-kill@1.2.2: {} - trim-newlines@4.1.1: {} - - ts-api-utils@1.3.0(typescript@5.6.2): + ts-api-utils@1.3.0(typescript@5.6.3): dependencies: - typescript: 5.6.2 + typescript: 5.6.3 ts-error@1.0.6: {} @@ -8908,18 +8681,18 @@ snapshots: ts-proto-descriptors@2.0.0: dependencies: - '@bufbuild/protobuf': 2.0.0 + '@bufbuild/protobuf': 2.2.0 - ts-proto@2.2.0: + ts-proto@2.2.7: dependencies: - '@bufbuild/protobuf': 2.0.0 + '@bufbuild/protobuf': 2.2.0 case-anything: 2.1.13 ts-poet: 6.9.0 ts-proto-descriptors: 2.0.0 - tsconfck@3.1.1(typescript@5.6.2): + tsconfck@3.1.4(typescript@5.6.3): optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 tsconfig-paths@3.15.0: dependencies: @@ -8930,27 +8703,27 @@ snapshots: tslib@2.7.0: {} - tsup@8.3.0(jiti@1.21.6)(postcss@8.4.47)(typescript@5.6.2)(yaml@2.5.0): + tsup@8.3.5(jiti@1.21.6)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.5.0): dependencies: - bundle-require: 5.0.0(esbuild@0.23.1) + bundle-require: 5.0.0(esbuild@0.24.0) cac: 6.7.14 - chokidar: 3.6.0 + chokidar: 4.0.1 consola: 3.2.3 debug: 4.3.7(supports-color@5.5.0) - esbuild: 0.23.1 - execa: 5.1.1 + esbuild: 0.24.0 joycon: 3.1.1 - picocolors: 1.1.0 - postcss-load-config: 6.0.1(jiti@1.21.6)(postcss@8.4.47)(yaml@2.5.0) + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@1.21.6)(postcss@8.4.49)(yaml@2.5.0) resolve-from: 5.0.0 - rollup: 4.21.3 + rollup: 4.25.0 source-map: 0.8.0-beta.0 sucrase: 3.35.0 - tinyglobby: 0.2.6 + tinyexec: 0.3.1 + tinyglobby: 0.2.10 tree-kill: 1.2.2 optionalDependencies: - postcss: 8.4.47 - typescript: 5.6.2 + postcss: 8.4.49 + typescript: 5.6.3 transitivePeerDependencies: - jiti - supports-color @@ -8961,32 +8734,32 @@ snapshots: dependencies: safe-buffer: 5.2.1 - turbo-darwin-64@2.1.2: + turbo-darwin-64@2.2.3: optional: true - turbo-darwin-arm64@2.1.2: + turbo-darwin-arm64@2.2.3: optional: true - turbo-linux-64@2.1.2: + turbo-linux-64@2.2.3: optional: true - turbo-linux-arm64@2.1.2: + turbo-linux-arm64@2.2.3: optional: true - turbo-windows-64@2.1.2: + turbo-windows-64@2.2.3: optional: true - turbo-windows-arm64@2.1.2: + turbo-windows-arm64@2.2.3: optional: true - turbo@2.1.2: + turbo@2.2.3: optionalDependencies: - turbo-darwin-64: 2.1.2 - turbo-darwin-arm64: 2.1.2 - turbo-linux-64: 2.1.2 - turbo-linux-arm64: 2.1.2 - turbo-windows-64: 2.1.2 - turbo-windows-arm64: 2.1.2 + turbo-darwin-64: 2.2.3 + turbo-darwin-arm64: 2.2.3 + turbo-linux-64: 2.2.3 + turbo-linux-arm64: 2.2.3 + turbo-windows-64: 2.2.3 + turbo-windows-arm64: 2.2.3 tweetnacl@0.14.5: {} @@ -8998,8 +8771,6 @@ snapshots: type-fest@0.21.3: {} - type-fest@1.4.0: {} - typed-array-buffer@1.0.2: dependencies: call-bind: 1.0.7 @@ -9032,7 +8803,7 @@ snapshots: is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 - typescript@5.6.2: {} + typescript@5.6.3: {} unbox-primitive@1.0.2: dependencies: @@ -9049,9 +8820,9 @@ snapshots: dependencies: '@fastify/busboy': 2.1.1 - universalify@0.1.2: {} + unicorn-magic@0.1.0: {} - universalify@0.2.0: {} + universalify@0.1.2: {} universalify@2.0.1: {} @@ -9061,17 +8832,18 @@ snapshots: dependencies: browserslist: 4.23.3 escalade: 3.2.0 - picocolors: 1.1.0 + picocolors: 1.1.1 + + update-browserslist-db@1.1.1(browserslist@4.24.2): + dependencies: + browserslist: 4.24.2 + escalade: 3.2.0 + picocolors: 1.1.1 uri-js@4.4.1: dependencies: punycode: 2.3.1 - url-parse@1.5.10: - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - use-intl@3.20.0(react@18.3.1): dependencies: '@formatjs/fast-memoize': 2.2.0 @@ -9086,23 +8858,18 @@ snapshots: uuid@8.3.2: {} - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 - verror@1.10.0: dependencies: assert-plus: 1.0.0 core-util-is: 1.0.2 extsprintf: 1.3.0 - vite-node@2.1.1(@types/node@22.7.6)(sass@1.79.1): + vite-node@2.1.4(@types/node@22.9.0)(sass@1.80.7): dependencies: cac: 6.7.14 debug: 4.3.7(supports-color@5.5.0) pathe: 1.1.2 - vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) + vite: 5.4.11(@types/node@22.9.0)(sass@1.80.7) transitivePeerDependencies: - '@types/node' - less @@ -9114,51 +8881,52 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1)): + vite-tsconfig-paths@5.1.2(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(sass@1.80.7)): dependencies: - debug: 4.3.6 + debug: 4.3.7(supports-color@5.5.0) globrex: 0.1.2 - tsconfck: 3.1.1(typescript@5.6.2) + tsconfck: 3.1.4(typescript@5.6.3) optionalDependencies: - vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) + vite: 5.4.11(@types/node@22.9.0)(sass@1.80.7) transitivePeerDependencies: - supports-color - typescript - vite@5.4.6(@types/node@22.7.6)(sass@1.79.1): + vite@5.4.11(@types/node@22.9.0)(sass@1.80.7): dependencies: esbuild: 0.21.5 - postcss: 8.4.47 - rollup: 4.21.3 + postcss: 8.4.49 + rollup: 4.25.0 optionalDependencies: - '@types/node': 22.7.6 + '@types/node': 22.9.0 fsevents: 2.3.3 - sass: 1.79.1 + sass: 1.80.7 - vitest@2.1.1(@types/node@22.7.6)(jsdom@25.0.0)(sass@1.79.1): + vitest@2.1.4(@types/node@22.9.0)(jsdom@25.0.1)(sass@1.80.7): dependencies: - '@vitest/expect': 2.1.1 - '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1)) - '@vitest/pretty-format': 2.1.1 - '@vitest/runner': 2.1.1 - '@vitest/snapshot': 2.1.1 - '@vitest/spy': 2.1.1 - '@vitest/utils': 2.1.1 - chai: 5.1.1 + '@vitest/expect': 2.1.4 + '@vitest/mocker': 2.1.4(vite@5.4.11(@types/node@22.9.0)(sass@1.80.7)) + '@vitest/pretty-format': 2.1.4 + '@vitest/runner': 2.1.4 + '@vitest/snapshot': 2.1.4 + '@vitest/spy': 2.1.4 + '@vitest/utils': 2.1.4 + chai: 5.1.2 debug: 4.3.7(supports-color@5.5.0) - magic-string: 0.30.11 + expect-type: 1.1.0 + magic-string: 0.30.12 pathe: 1.1.2 - std-env: 3.7.0 + std-env: 3.8.0 tinybench: 2.9.0 - tinyexec: 0.3.0 + tinyexec: 0.3.1 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) - vite-node: 2.1.1(@types/node@22.7.6)(sass@1.79.1) + vite: 5.4.11(@types/node@22.9.0)(sass@1.80.7) + vite-node: 2.1.4(@types/node@22.9.0)(sass@1.80.7) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.7.6 - jsdom: 25.0.0 + '@types/node': 22.9.0 + jsdom: 25.0.1 transitivePeerDependencies: - less - lightningcss @@ -9311,8 +9079,6 @@ snapshots: yaml@2.5.0: {} - yargs-parser@20.2.9: {} - yargs-parser@21.1.1: {} yargs@17.7.2: From 350c6450fdcaac9d3a5ce2fa7f02b9c8cb9ae202 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Wed, 13 Nov 2024 19:48:25 +0100 Subject: [PATCH 391/640] chore: more acceptance tests --- acceptance/tests/admin.spec.ts | 4 +- acceptance/tests/login.ts | 36 +++-- acceptance/tests/loginname-screen.ts | 12 ++ acceptance/tests/loginname.ts | 6 +- acceptance/tests/passkey.ts | 146 +++++++++++++++--- acceptance/tests/password-screen.ts | 47 ++++++ acceptance/tests/password.ts | 27 ++-- acceptance/tests/register-screen.ts | 27 ++++ acceptance/tests/register.spec.ts | 10 +- acceptance/tests/register.ts | 26 +--- acceptance/tests/user.ts | 81 +++++----- acceptance/tests/username-passkey.spec.ts | 8 +- .../tests/username-password-changed.spec.ts | 21 ++- acceptance/tests/username-password.spec.ts | 22 ++- .../src/components/password-complexity.tsx | 12 +- apps/login/src/components/password-form.tsx | 2 +- apps/login/src/components/username-form.tsx | 2 +- 17 files changed, 342 insertions(+), 147 deletions(-) create mode 100644 acceptance/tests/loginname-screen.ts create mode 100644 acceptance/tests/password-screen.ts create mode 100644 acceptance/tests/register-screen.ts diff --git a/acceptance/tests/admin.spec.ts b/acceptance/tests/admin.spec.ts index 929e2a5be4..3702250b0d 100644 --- a/acceptance/tests/admin.spec.ts +++ b/acceptance/tests/admin.spec.ts @@ -1,7 +1,7 @@ import {test} from "@playwright/test"; -import {checkLogin, loginWithPassword} from "./login"; +import {loginScreenExpect, loginWithPassword} from "./login"; test("admin login", async ({page}) => { await loginWithPassword(page, "zitadel-admin@zitadel.localhost", "Password1.") - await checkLogin(page, "ZITADEL Admin"); + await loginScreenExpect(page, "ZITADEL Admin"); }); diff --git a/acceptance/tests/login.ts b/acceptance/tests/login.ts index 58d8572f50..5732ae495c 100644 --- a/acceptance/tests/login.ts +++ b/acceptance/tests/login.ts @@ -1,23 +1,29 @@ import {expect, Page} from "@playwright/test"; -import {loginnameScreen} from "./loginname"; -import {passwordScreen} from "./password"; -import {passkeyScreen} from "./passkey"; +import {loginname} from "./loginname"; +import {password} from "./password"; -export async function loginWithPassword(page: Page, username: string, password: string) { +export async function startLogin(page: Page) { await page.goto("/loginname"); - await loginnameScreen(page, username) - await page.getByTestId("submit-button").click() - await passwordScreen(page, password) - await page.getByTestId("submit-button").click() } -export async function loginWithPasskey(page: Page, username: string) { - await page.goto("/loginname"); - await loginnameScreen(page, username) - await page.getByTestId("submit-button").click() - await passkeyScreen(page) +export async function loginWithPassword(page: Page, username: string, pw: string) { + await startLogin(page); + await loginname(page, username); + await password(page, pw); } -export async function checkLogin(page: Page, fullName: string) { +export async function loginWithPasskey(page: Page, authenticatorId: string, username: string) { + await startLogin(page); + await loginname(page, username); + // await passkey(page, authenticatorId); +} + +export async function loginScreenExpect(page: Page, fullName: string) { await expect(page.getByRole('heading')).toContainText(fullName); -} \ No newline at end of file +} + +export async function loginWithOTP(page: Page, username: string, password: string) { + await loginWithPassword(page, username, password); + + +} diff --git a/acceptance/tests/loginname-screen.ts b/acceptance/tests/loginname-screen.ts new file mode 100644 index 0000000000..bed33226f7 --- /dev/null +++ b/acceptance/tests/loginname-screen.ts @@ -0,0 +1,12 @@ +import {expect, Page} from "@playwright/test"; + +const usernameUserInput = "username-text-input" + +export async function loginnameScreen(page: Page, username: string) { + await page.getByTestId(usernameUserInput).pressSequentially(username); +} + +export async function loginnameScreenExpect(page: Page, username: string) { + await expect(page.getByTestId(usernameUserInput)).toHaveValue(username); + await expect(page.getByTestId('error').locator('div')).toContainText("Could not find user") +} diff --git a/acceptance/tests/loginname.ts b/acceptance/tests/loginname.ts index eb490fbb61..5e6719622a 100644 --- a/acceptance/tests/loginname.ts +++ b/acceptance/tests/loginname.ts @@ -1,5 +1,7 @@ import {Page} from "@playwright/test"; +import {loginnameScreen} from "./loginname-screen"; -export async function loginnameScreen(page: Page, username: string) { - await page.getByTestId("username-text-input").pressSequentially(username); +export async function loginname(page: Page, username: string) { + await loginnameScreen(page, username) + await page.getByTestId("submit-button").click() } diff --git a/acceptance/tests/passkey.ts b/acceptance/tests/passkey.ts index 92957cdd8c..8ee895b7b1 100644 --- a/acceptance/tests/passkey.ts +++ b/acceptance/tests/passkey.ts @@ -1,4 +1,5 @@ -import {Page} from "@playwright/test"; +import {expect, Page} from "@playwright/test"; +import {CDPSession} from "playwright-core"; const BASE64_ENCODED_PK = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDbBOu5Lhs4vpowbCnmCyLUpIE7JM9sm9QXzye2G+jr+Kr" + @@ -20,10 +21,15 @@ const BASE64_ENCODED_PK = "zJOGpf9x2RSWzQJ+dq8+6fACgfFZOVpN644+sAHfNPAI/gnNKU5OfUv+eav8fBnzlf1A3y3GIkyMyzFN3DE7e0n/lyqxE4H" + "BYGpI8g=="; -export async function passkeyScreen(page: Page) { - const client = await page.context().newCDPSession(page); - await client.send('WebAuthn.enable', {enableUI: true}); - const result = await client.send('WebAuthn.addVirtualAuthenticator', { +interface session { + client: CDPSession + authenticatorId: string +} + +async function client(page: Page): Promise { + const cdpSession = await page.context().newCDPSession(page); + await cdpSession.send('WebAuthn.enable', {enableUI: false}); + const result = await cdpSession.send('WebAuthn.addVirtualAuthenticator', { options: { protocol: 'ctap2', transport: 'internal', @@ -33,30 +39,120 @@ export async function passkeyScreen(page: Page) { automaticPresenceSimulation: true, }, }); - const authenticatorId = result.authenticatorId; + return {client: cdpSession, authenticatorId: result.authenticatorId}; +} +export async function passkeyRegister(page: Page): Promise { + const session = await client(page) + + await passkeyNotExisting(session.client, session.authenticatorId); + await simulateSuccessfulPasskeyRegister( + session.client, + session.authenticatorId, + () => + page.getByTestId("submit-button").click() + ); + await passkeyRegistered(session.client, session.authenticatorId); + + return session.authenticatorId +} + +export async function passkey(page: Page, authenticatorId: string) { + const cdpSession = await page.context().newCDPSession(page); + await cdpSession.send('WebAuthn.enable', {enableUI: false}); + + const signCount = await passkeyExisting(cdpSession, authenticatorId); + + await simulateSuccessfulPasskeyInput( + cdpSession, + authenticatorId, + () => + page.getByTestId("submit-button").click() + ); + + await passkeyUsed(cdpSession, authenticatorId, signCount); +} + +async function passkeyNotExisting(client: CDPSession, authenticatorId: string) { + const result = await client.send('WebAuthn.getCredentials', {authenticatorId}); + expect(result.credentials).toHaveLength(0); +} + +async function passkeyRegistered(client: CDPSession, authenticatorId: string) { + const result = await client.send('WebAuthn.getCredentials', {authenticatorId}); + expect(result.credentials).toHaveLength(1); + await passkeyUsed(client, authenticatorId, 0); +} + +async function passkeyExisting(client: CDPSession, authenticatorId: string): Promise { + const result = await client.send('WebAuthn.getCredentials', {authenticatorId}); + expect(result.credentials).toHaveLength(1); + return result.credentials[0].signCount +} + +async function passkeyUsed(client: CDPSession, authenticatorId: string, signCount: number) { + const result = await client.send('WebAuthn.getCredentials', {authenticatorId}); + expect(result.credentials).toHaveLength(1); + expect(result.credentials[0].signCount).toBeGreaterThan(signCount); +} + +async function simulateSuccessfulPasskeyRegister(client: CDPSession, authenticatorId: string, operationTrigger: () => Promise) { // initialize event listeners to wait for a successful passkey input event const operationCompleted = new Promise(resolve => { - client.on('WebAuthn.credentialAdded', () => resolve()); - client.on('WebAuthn.credentialAsserted', () => resolve()); + client.on('WebAuthn.credentialAdded', () => { + console.log('Credential Added!'); + resolve() + }); }); - const url = new URL(process.env.ZITADEL_API_URL!) - await client.send('WebAuthn.addCredential', { - credential: { - credentialId: "", - rpId: url.hostname, - privateKey: BASE64_ENCODED_PK, - isResidentCredential: false, - signCount: 0, - }, - authenticatorId: authenticatorId - }); + // perform a user action that triggers passkey prompt + await operationTrigger(); - - // triggers passkey check - await page.getByTestId("submit-button").click() - - // wait for successfully verified + // wait to receive the event that the passkey was successfully registered or verified await operationCompleted; -} \ No newline at end of file +} + +async function simulateSuccessfulPasskeyInput(client: CDPSession, authenticatorId: string, operationTrigger: () => Promise) { + // initialize event listeners to wait for a successful passkey input event + const operationCompleted = new Promise(resolve => { + client.on('WebAuthn.credentialAsserted', () => { + console.log('Credential Asserted!'); + resolve() + }); + }); + + // perform a user action that triggers passkey prompt + await operationTrigger(); + + // wait to receive the event that the passkey was successfully registered or verified + await operationCompleted; +} + +async function simulateFailedPasskeyInput(client: CDPSession, authenticatorId: string, operationTrigger: () => Promise, postOperationCheck: () => Promise) { + // set isUserVerified option to false + // (so that subsequent passkey operations will fail) + await client.send('WebAuthn.setUserVerified', { + authenticatorId: authenticatorId, + isUserVerified: false, + }); + + // set automaticPresenceSimulation option to true + // (so that the virtual authenticator will respond to the next passkey prompt) + await client.send('WebAuthn.setAutomaticPresenceSimulation', { + authenticatorId: authenticatorId, + enabled: true, + }); + + // perform a user action that triggers passkey prompt + await operationTrigger(); + + // wait for an expected UI change that indicates the passkey operation has completed + await postOperationCheck(); + + // set automaticPresenceSimulation option back to false + await client.send('WebAuthn.setAutomaticPresenceSimulation', { + authenticatorId, + enabled: false, + }); +} + diff --git a/acceptance/tests/password-screen.ts b/acceptance/tests/password-screen.ts new file mode 100644 index 0000000000..ee79bed176 --- /dev/null +++ b/acceptance/tests/password-screen.ts @@ -0,0 +1,47 @@ +import {expect, Page} from "@playwright/test"; + +const passwordField = 'password-text-input' +const passwordConfirmField = 'password-confirm-text-input' +const lengthCheck = "length-check" +const symbolCheck = "symbol-check" +const numberCheck = "number-check" +const uppercaseCheck = "uppercase-check" +const lowercaseCheck = "lowercase-check" +const equalCheck = "equal-check" + +const matchText = "Matches" +const noMatchText = "Doesn\'t match" + +export async function changePasswordScreen(page: Page, password1: string, password2: string) { + await page.getByTestId(passwordField).pressSequentially(password1); + await page.getByTestId(passwordConfirmField).pressSequentially(password2); +} + +export async function passwordScreen(page: Page, password: string) { + await page.getByTestId(passwordField).pressSequentially(password); +} + +export async function passwordScreenExpect(page: Page, password: string) { + await expect(page.getByTestId(passwordField)).toHaveValue(password); + await expect(page.getByTestId('error').locator('div')).toContainText("Could not verify password"); +} + +export async function changePasswordScreenExpect(page: Page, password1: string, password2: string, length: boolean, symbol: boolean, number: boolean, uppercase: boolean, lowercase: boolean, equals: boolean) { + await expect(page.getByTestId(passwordField)).toHaveValue(password1); + await expect(page.getByTestId(passwordConfirmField)).toHaveValue(password2); + + await checkContent(page, lengthCheck, length); + await checkContent(page, symbolCheck, symbol); + await checkContent(page, numberCheck, number); + await checkContent(page, uppercaseCheck, uppercase); + await checkContent(page, lowercaseCheck, lowercase); + await checkContent(page, equalCheck, equals); +} + +async function checkContent(page: Page, testid: string, match: boolean) { + if (match) { + await expect(page.getByTestId(testid)).toContainText(matchText); + } else { + await expect(page.getByTestId(testid)).toContainText(noMatchText); + } +} \ No newline at end of file diff --git a/acceptance/tests/password.ts b/acceptance/tests/password.ts index 7d2286d858..0deba87955 100644 --- a/acceptance/tests/password.ts +++ b/acceptance/tests/password.ts @@ -1,21 +1,20 @@ import {Page} from "@playwright/test"; +import {changePasswordScreen, passwordScreen} from "./password-screen"; + +const passwordSubmitButton = "submit-button" + + +export async function startChangePassword(page: Page, loginname: string) { + await page.goto('password/change?' + new URLSearchParams({loginName: loginname})); +} export async function changePassword(page: Page, loginname: string, password: string) { - await page.goto('password/change?' + new URLSearchParams({loginName: loginname})); + await startChangePassword(page, loginname); await changePasswordScreen(page, password, password) - await page.getByTestId("submit-button").click(); + await page.getByTestId(passwordSubmitButton).click(); } -async function changePasswordScreen(page: Page, password1: string, password2: string) { - await page.getByTestId('password-text-input').pressSequentially(password1); - await page.getByTestId('password-confirm-text-input').pressSequentially(password2); -} - -export async function passwordScreen(page: Page, password: string) { - await page.getByTestId("password-text-input").pressSequentially(password); -} - -export async function registerPasswordScreen(page: Page, password1: string, password2: string) { - await page.getByTestId('password-text-input').pressSequentially(password1); - await page.getByTestId('password-confirm-text-input').pressSequentially(password2); +export async function password(page: Page, password: string) { + await passwordScreen(page, password) + await page.getByTestId(passwordSubmitButton).click() } diff --git a/acceptance/tests/register-screen.ts b/acceptance/tests/register-screen.ts new file mode 100644 index 0000000000..83a3250588 --- /dev/null +++ b/acceptance/tests/register-screen.ts @@ -0,0 +1,27 @@ +import {Page} from "@playwright/test"; + +const passwordField = 'password-text-input' +const passwordConfirmField = 'password-confirm-text-input' + +export async function registerUserScreenPassword(page: Page, firstname: string, lastname: string, email: string) { + await registerUserScreen(page, firstname, lastname, email) + await page.getByTestId('Password-radio').click(); +} + +export async function registerUserScreenPasskey(page: Page, firstname: string, lastname: string, email: string) { + await registerUserScreen(page, firstname, lastname, email) + await page.getByTestId('Passkeys-radio').click(); +} + +export async function registerPasswordScreen(page: Page, password1: string, password2: string) { + await page.getByTestId(passwordField).pressSequentially(password1); + await page.getByTestId(passwordConfirmField).pressSequentially(password2); +} + +export async function registerUserScreen(page: Page, firstname: string, lastname: string, email: string) { + await page.getByTestId('firstname-text-input').pressSequentially(firstname); + await page.getByTestId('lastname-text-input').pressSequentially(lastname); + await page.getByTestId('email-text-input').pressSequentially(email); + await page.getByTestId('privacy-policy-checkbox').check(); + await page.getByTestId('tos-checkbox').check(); +} \ No newline at end of file diff --git a/acceptance/tests/register.spec.ts b/acceptance/tests/register.spec.ts index c482dd6874..0e08db5614 100644 --- a/acceptance/tests/register.spec.ts +++ b/acceptance/tests/register.spec.ts @@ -1,6 +1,6 @@ import {test} from "@playwright/test"; import {registerWithPasskey, registerWithPassword} from './register'; -import {checkLogin} from "./login"; +import {loginScreenExpect} from "./login"; import {removeUserByUsername} from './zitadel'; import path from 'path'; import dotenv from 'dotenv'; @@ -9,22 +9,22 @@ import dotenv from 'dotenv'; dotenv.config({path: path.resolve(__dirname, '.env.local')}); test("register with password", async ({page}) => { - const username = "register@example.com" + const username = "register-password@example.com" const password = "Password1!" const firstname = "firstname" const lastname = "lastname" await removeUserByUsername(username) await registerWithPassword(page, firstname, lastname, username, password, password) - await checkLogin(page, firstname + " " + lastname); + await loginScreenExpect(page, firstname + " " + lastname); }); test("register with passkey", async ({page}) => { - const username = "register@example.com" + const username = "register-passkey@example.com" const firstname = "firstname" const lastname = "lastname" await removeUserByUsername(username) await registerWithPasskey(page, firstname, lastname, username) - await checkLogin(page, firstname + " " + lastname); + await loginScreenExpect(page, firstname + " " + lastname); }); diff --git a/acceptance/tests/register.ts b/acceptance/tests/register.ts index e045588f1d..abff319437 100644 --- a/acceptance/tests/register.ts +++ b/acceptance/tests/register.ts @@ -1,6 +1,6 @@ import {Page} from "@playwright/test"; -import {passkeyScreen} from './passkey'; -import {registerPasswordScreen} from './password'; +import {passkeyRegister} from './passkey'; +import {registerPasswordScreen, registerUserScreenPasskey, registerUserScreenPassword} from './register-screen'; export async function registerWithPassword(page: Page, firstname: string, lastname: string, email: string, password1: string, password2: string) { await page.goto('/register'); @@ -10,27 +10,9 @@ export async function registerWithPassword(page: Page, firstname: string, lastna await page.getByTestId('submit-button').click(); } -async function registerUserScreenPassword(page: Page, firstname: string, lastname: string, email: string) { - await registerUserScreen(page, firstname, lastname, email) - await page.getByTestId('Password-radio').click(); -} - -export async function registerWithPasskey(page: Page, firstname: string, lastname: string, email: string) { +export async function registerWithPasskey(page: Page, firstname: string, lastname: string, email: string): Promise { await page.goto('/register'); await registerUserScreenPasskey(page, firstname, lastname, email) await page.getByTestId('submit-button').click(); - await passkeyScreen(page) + return await passkeyRegister(page) } - -async function registerUserScreenPasskey(page: Page, firstname: string, lastname: string, email: string) { - await registerUserScreen(page, firstname, lastname, email) - await page.getByTestId('Passkeys-radio').click(); -} - -async function registerUserScreen(page: Page, firstname: string, lastname: string, email: string) { - await page.getByTestId('firstname-text-input').pressSequentially(firstname); - await page.getByTestId('lastname-text-input').pressSequentially(lastname); - await page.getByTestId('email-text-input').pressSequentially(email); - await page.getByTestId('privacy-policy-checkbox').check(); - await page.getByTestId('tos-checkbox').check(); -} \ No newline at end of file diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index a2a18787b1..b29fd9af46 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -1,9 +1,9 @@ import fetch from "node-fetch"; import {Page} from "@playwright/test"; import {registerWithPasskey} from "./register"; -import {loginWithPasskey, loginWithPassword} from "./login"; +import {loginWithPassword} from "./login"; import {changePassword} from "./password"; -import {removeUser, getUserByUsername} from './zitadel'; +import {getUserByUsername, removeUser} from './zitadel'; export interface userProps { email: string; @@ -59,7 +59,7 @@ class User { } async remove() { - await removeUser(this.userId()) + await removeUser(this.getUserId()) return } @@ -67,37 +67,33 @@ class User { this.user = userId } - public userId() { + public getUserId() { return this.user; } - public username() { + public getUsername() { return this.props.email; } - public password() { + public getPassword() { return this.props.password; } - public firstname() { + public getFirstname() { return this.props.firstName } - public lastname() { + public getLastname() { return this.props.lastName } - public fullName() { + public getFullName() { return this.props.firstName + " " + this.props.lastName } - public async login(page: Page) { - await loginWithPassword(page, this.username(), this.password()) - } - - public async changePassword(page: Page, password: string) { - await loginWithPassword(page, this.username(), this.password()) - await changePassword(page, this.username(), password) + public async doPasswordChange(page: Page, password: string) { + await loginWithPassword(page, this.getUsername(), this.getPassword()) + await changePassword(page, this.getUsername(), password) this.props.password = password } } @@ -105,8 +101,7 @@ class User { export class PasswordUser extends User { } -enum OtpType { - time = "time-based", +export enum OtpType { sms = "sms", email = "email", } @@ -116,6 +111,7 @@ export interface otpUserProps { firstName: string; lastName: string; organization: string; + password: string, type: OtpType, } @@ -129,7 +125,7 @@ export class PasswordUserWithOTP extends User { firstName: props.firstName, lastName: props.lastName, organization: props.organization, - password: "" + password: props.password, }) this.type = props.type } @@ -137,27 +133,16 @@ export class PasswordUserWithOTP extends User { async ensure(page: Page) { await super.ensure(page) - const body = { - username: this.props.email, - organization: { - orgId: this.props.organization - }, - profile: { - givenName: this.props.firstName, - familyName: this.props.lastName, - }, - email: { - email: this.props.email, - isVerified: true, - }, - password: { - password: this.props.password!, - } + let url = "otp_" + switch (this.type) { + case OtpType.sms: + url = url + "sms" + case OtpType.email: + url = url + "email" } - const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/human", { + const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + this.getUserId() + "/" + url, { method: 'POST', - body: JSON.stringify(body), headers: { 'Content-Type': 'application/json', 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! @@ -168,8 +153,15 @@ export class PasswordUserWithOTP extends User { console.error(error); throw new Error(error); } + + // TODO: get code from SMS or Email provider + this.code = "" return } + + public getCode() { + return this.code + } } export interface passkeyUserProps { @@ -180,6 +172,8 @@ export interface passkeyUserProps { } export class PasskeyUser extends User { + private authenticatorId: string + constructor(props: passkeyUserProps) { super({ email: props.email, @@ -192,19 +186,20 @@ export class PasskeyUser extends User { public async ensure(page: Page) { await this.remove() - await registerWithPasskey(page, this.firstname(), this.lastname(), this.username()) - } - - public async login(page: Page) { - await loginWithPasskey(page, this.username()) + const authId = await registerWithPasskey(page, this.getFirstname(), this.getLastname(), this.getUsername()) + this.authenticatorId = authId } public async remove() { - const resp = await getUserByUsername(this.username()) + const resp = await getUserByUsername(this.getUsername()) if (!resp || !resp.result || !resp.result[0]) { return } this.setUserId(resp.result[0].userId) await super.remove() } + + public getAuthenticatorId(): string { + return this.authenticatorId + } } diff --git a/acceptance/tests/username-passkey.spec.ts b/acceptance/tests/username-passkey.spec.ts index bf56f61d63..43c3cec714 100644 --- a/acceptance/tests/username-passkey.spec.ts +++ b/acceptance/tests/username-passkey.spec.ts @@ -2,7 +2,7 @@ import {test as base} from "@playwright/test"; import path from 'path'; import dotenv from 'dotenv'; import {PasskeyUser} from "./user"; -import {checkLogin} from "./login"; +import {loginScreenExpect, loginWithPasskey} from "./login"; // Read from ".env" file. dotenv.config({path: path.resolve(__dirname, '.env.local')}); @@ -16,11 +16,11 @@ const test = base.extend<{ user: PasskeyUser }>({ organization: "", }); await user.ensure(page); - await use(user) + await use(user); }, }); test("username and passkey login", async ({user, page}) => { - await user.login(page) - await checkLogin(page, user.fullName()); + await loginWithPasskey(page, user.getAuthenticatorId(), user.getUsername()) + await loginScreenExpect(page, user.getFullName()); }); diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index 0607ad15f7..9a7f09b046 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -2,7 +2,9 @@ import {test as base} from "@playwright/test"; import {PasswordUser} from './user'; import path from 'path'; import dotenv from 'dotenv'; -import {checkLogin} from "./login"; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {changePassword, startChangePassword} from "./password"; +import {changePasswordScreen, changePasswordScreenExpect} from "./password-screen"; // Read from ".env" file. dotenv.config({path: path.resolve(__dirname, '.env.local')}); @@ -22,7 +24,18 @@ const test = base.extend<{ user: PasswordUser }>({ }); test("username and password changed login", async ({user, page}) => { - await user.changePassword(page, "ChangedPw1!") - await user.login(page) - await checkLogin(page, user.fullName()); + const changedPw = "ChangedPw1!" + await loginWithPassword(page, user.getUsername(), user.getPassword()) + await changePassword(page, user.getUsername(), changedPw) + await loginWithPassword(page, user.getUsername(), changedPw) + await loginScreenExpect(page, user.getFullName()); +}); + +test("password not with desired complexity", async ({user, page}) => { + const changedPw1 = "change" + const changedPw2 = "chang" + await loginWithPassword(page, user.getUsername(), user.getPassword()) + await startChangePassword(page, user.getUsername()); + await changePasswordScreen(page, changedPw1, changedPw2) + await changePasswordScreenExpect(page, changedPw1, changedPw2, false, false, false, false, true, false) }); diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index e24e4b50ee..e9ab31d998 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -2,7 +2,11 @@ import {test as base} from "@playwright/test"; import {PasswordUser} from './user'; import path from 'path'; import dotenv from 'dotenv'; -import {checkLogin} from "./login"; +import {loginScreenExpect, loginWithPassword, startLogin} from "./login"; +import {loginnameScreenExpect} from "./loginname-screen"; +import {passwordScreenExpect} from "./password-screen"; +import {loginname} from "./loginname"; +import {password} from "./password"; // Read from ".env" file. dotenv.config({path: path.resolve(__dirname, '.env.local')}); @@ -22,8 +26,20 @@ const test = base.extend<{ user: PasswordUser }>({ }); test("username and password login", async ({user, page}) => { - await user.login(page) - await checkLogin(page, user.fullName()); + await loginWithPassword(page, user.getUsername(), user.getPassword()) + await loginScreenExpect(page, user.getFullName()); }); +test("username and password login, unknown username", async ({page}) => { + const username = "unknown" + await startLogin(page); + await loginname(page, username) + await loginnameScreenExpect(page, username) +}); +test("username and password login, wrong password", async ({user, page}) => { + await startLogin(page); + await loginname(page, user.getUsername()) + await password(page, "wrong") + await passwordScreenExpect(page, "wrong") +}); diff --git a/apps/login/src/components/password-complexity.tsx b/apps/login/src/components/password-complexity.tsx index 617aefda3b..40988984b6 100644 --- a/apps/login/src/components/password-complexity.tsx +++ b/apps/login/src/components/password-complexity.tsx @@ -65,7 +65,7 @@ export function PasswordComplexity({ return (
    {passwordComplexitySettings.minLength != undefined ? ( -
    +
    {hasMinLength ? check : cross} Password length {passwordComplexitySettings.minLength.toString()} @@ -74,23 +74,23 @@ export function PasswordComplexity({ ) : ( )} -
    +
    {hasSymbol ? check : cross} has Symbol
    -
    +
    {hasNumber ? check : cross} has Number
    -
    +
    {hasUppercase ? check : cross} has uppercase
    -
    +
    {hasLowercase ? check : cross} has lowercase
    -
    +
    {equals ? check : cross} equals
    diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index d2ec163c13..559faef5ed 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -150,7 +150,7 @@ export function PasswordForm({ )} {error && ( -
    +
    {error}
    )} diff --git a/apps/login/src/components/username-form.tsx b/apps/login/src/components/username-form.tsx index f0766d77f6..26959e20d0 100644 --- a/apps/login/src/components/username-form.tsx +++ b/apps/login/src/components/username-form.tsx @@ -106,7 +106,7 @@ export function UsernameForm({
    {error && ( -
    +
    {error}
    )} From d5a249d7e5d4a48ec45b9f8a7f766b0c4edf0e0b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 14 Nov 2024 09:24:57 +0100 Subject: [PATCH 392/640] catch def org error --- apps/login/src/app/(login)/loginname/page.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 68928755d3..b71ff46841 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -34,7 +34,10 @@ export default async function Page({ let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg(); + const org: Organization | null = await getDefaultOrg().catch((error) => { + console.warn(error); + return null; + }); if (org) { defaultOrganization = org.id; } From 258c3c497d6c4821a9e373c16da4702658339716 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 14 Nov 2024 09:25:39 +0100 Subject: [PATCH 393/640] catch def org on pwd page --- apps/login/src/app/(login)/password/page.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 659488043b..9fe93dcb61 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -25,7 +25,10 @@ export default async function Page({ let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg(); + const org: Organization | null = await getDefaultOrg().catch((error) => { + console.warn(error); + return null; + }); if (org) { defaultOrganization = org.id; } From 3311042195f83e61bda2a58db898ddd02359ddbb Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 14 Nov 2024 09:35:40 +0100 Subject: [PATCH 394/640] redirect to /loginname --- apps/login/src/app/(login)/loginname/page.tsx | 9 +++++---- apps/login/src/app/(login)/page.tsx | 6 +++--- apps/login/src/app/(login)/password/page.tsx | 9 +++++---- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index b71ff46841..47e3245cc9 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -34,10 +34,11 @@ export default async function Page({ let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg().catch((error) => { - console.warn(error); - return null; - }); + const org: Organization | null = await getDefaultOrg(); + // .catch((error) => { + // console.warn(error); + // return null; + // }); if (org) { defaultOrganization = org.id; } diff --git a/apps/login/src/app/(login)/page.tsx b/apps/login/src/app/(login)/page.tsx index f1fce50f90..9a530bc59d 100644 --- a/apps/login/src/app/(login)/page.tsx +++ b/apps/login/src/app/(login)/page.tsx @@ -2,7 +2,7 @@ import { redirect } from "next/navigation"; export default function Page() { // automatically redirect to loginname - if (process.env.DEBUG !== "true") { - redirect("/loginname"); - } + // if (process.env.DEBUG !== "true") { + redirect("/loginname"); + // } } diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 9fe93dcb61..4fc1e54457 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -25,10 +25,11 @@ export default async function Page({ let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg().catch((error) => { - console.warn(error); - return null; - }); + const org: Organization | null = await getDefaultOrg(); + // .catch((error) => { + // console.warn(error); + // return null; + // }); if (org) { defaultOrganization = org.id; } From 5fa43f02397716a1a2ff1a9c7b17d9d41f1a11b5 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 14 Nov 2024 09:44:19 +0100 Subject: [PATCH 395/640] loginname as starting point --- acceptance/tests/username-password.spec.ts | 2 +- .../src/components/mobile-nav-toggle.tsx | 71 ------------------- 2 files changed, 1 insertion(+), 72 deletions(-) delete mode 100644 apps/login/src/components/mobile-nav-toggle.tsx diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index 31d165358e..071bbd18dc 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -1,7 +1,7 @@ import { test } from "@playwright/test"; test("username and password", async ({ page }) => { - await page.goto("/"); + await page.goto("/loginname"); const loginname = page.getByLabel("Loginname"); await loginname.pressSequentially("zitadel-admin@zitadel.localhost"); await loginname.press("Enter"); diff --git a/apps/login/src/components/mobile-nav-toggle.tsx b/apps/login/src/components/mobile-nav-toggle.tsx deleted file mode 100644 index e288237ee1..0000000000 --- a/apps/login/src/components/mobile-nav-toggle.tsx +++ /dev/null @@ -1,71 +0,0 @@ -"use client"; - -import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/solid"; -import { clsx } from "clsx"; -import { - createContext, - Dispatch, - ReactNode, - SetStateAction, - useContext, - useState, -} from "react"; - -const MobileNavContext = createContext< - [boolean, Dispatch>] | undefined ->(undefined); - -export function MobileNavContextProvider({ - children, -}: { - children: ReactNode; -}) { - const [isOpen, setIsOpen] = useState(false); - return ( - - {children} - - ); -} - -export function useMobileNavToggle() { - const context = useContext(MobileNavContext); - if (context === undefined) { - throw new Error( - "useMobileNavToggle must be used within a MobileNavContextProvider", - ); - } - return context; -} - -export function MobileNavToggle({ children }: { children: ReactNode }) { - const [isOpen, setIsOpen] = useMobileNavToggle(); - - return ( - <> - - -
    - {children} -
    - - ); -} From 14eaa51b2839b49a840694c555badb78def31673 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 14 Nov 2024 09:52:17 +0100 Subject: [PATCH 396/640] debug=true --- acceptance/setup.sh | 4 +++- apps/login/src/app/(login)/page.tsx | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/acceptance/setup.sh b/acceptance/setup.sh index 5359659efa..aed1f1d8de 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -29,6 +29,8 @@ echo "Writing environment file to ${WRITE_ENVIRONMENT_FILE} when done." echo "ZITADEL_API_URL=${ZITADEL_API_URL} ZITADEL_SERVICE_USER_ID=${ZITADEL_SERVICE_USER_ID} -ZITADEL_SERVICE_USER_TOKEN=${PAT}" > ${WRITE_ENVIRONMENT_FILE} +ZITADEL_SERVICE_USER_TOKEN=${PAT} +DEBUG=true" > ${WRITE_ENVIRONMENT_FILE} + echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}" cat ${WRITE_ENVIRONMENT_FILE} diff --git a/apps/login/src/app/(login)/page.tsx b/apps/login/src/app/(login)/page.tsx index 9a530bc59d..f1fce50f90 100644 --- a/apps/login/src/app/(login)/page.tsx +++ b/apps/login/src/app/(login)/page.tsx @@ -2,7 +2,7 @@ import { redirect } from "next/navigation"; export default function Page() { // automatically redirect to loginname - // if (process.env.DEBUG !== "true") { - redirect("/loginname"); - // } + if (process.env.DEBUG !== "true") { + redirect("/loginname"); + } } From 3ca7d9929fab73e709d94ef164e6e4231b866d88 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 14 Nov 2024 10:04:01 +0100 Subject: [PATCH 397/640] catch error on default org --- apps/login/src/app/(login)/loginname/page.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 47e3245cc9..b71ff46841 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -34,11 +34,10 @@ export default async function Page({ let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg(); - // .catch((error) => { - // console.warn(error); - // return null; - // }); + const org: Organization | null = await getDefaultOrg().catch((error) => { + console.warn(error); + return null; + }); if (org) { defaultOrganization = org.id; } From 9b2ed8d200df01b94e2fdffe1753056096f3faff Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 14 Nov 2024 10:25:36 +0100 Subject: [PATCH 398/640] script --- acceptance/docker-compose.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index 59839e0017..f15cd7524e 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -13,7 +13,7 @@ services: condition: "service_healthy" db: - restart: 'always' + restart: "always" image: "${POSTGRES_IMAGE:-postgres:latest}" environment: - POSTGRES_USER=zitadel @@ -23,10 +23,10 @@ services: command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c shared_buffers=1GB -c work_mem=16MB -c effective_io_concurrency=100 -c wal_level=minimal -c archive_mode=off -c max_wal_senders=0 healthcheck: test: ["CMD-SHELL", "pg_isready"] - interval: '10s' - timeout: '30s' + interval: "10s" + timeout: "30s" retries: 5 - start_period: '20s' + start_period: "20s" ports: - 5432:5432 @@ -34,10 +34,10 @@ services: image: curlimages/curl:8.00.1 command: [ - "/bin/sh", - "-c", - "i=0; while ! curl http://zitadel:8080/debug/ready && [ $$i -lt 30 ]; do sleep 1; i=$$((i+1)); done; [ $$i -eq 120 ] && exit 1 || exit 0", + "CMD-SHELL", + "until curl -s -o /dev/null -w '%{http_code}' http://zitadel:8080/debug/ready | grep -q 200; do sleep 5; done", ] + depends_on: - zitadel From 17b04dce54e8afb152bbc82b54555c261349899e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 14 Nov 2024 10:30:38 +0100 Subject: [PATCH 399/640] script --- acceptance/docker-compose.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index f15cd7524e..b106e5a89d 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -32,11 +32,7 @@ services: wait_for_zitadel: image: curlimages/curl:8.00.1 - command: - [ - "CMD-SHELL", - "until curl -s -o /dev/null -w '%{http_code}' http://zitadel:8080/debug/ready | grep -q 200; do sleep 5; done", - ] + command: /bin/sh -c "until curl -s -o /dev/null -w '%{http_code}' http://zitadel:8080/debug/ready | grep -q 200; do sleep 5; done" depends_on: - zitadel From 8303c492eecee314f862948654a77ecdb495e848 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 14 Nov 2024 10:35:36 +0100 Subject: [PATCH 400/640] escape error catching --- apps/login/src/app/(login)/loginname/page.tsx | 5 +---- apps/login/src/app/(login)/password/page.tsx | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index b71ff46841..68928755d3 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -34,10 +34,7 @@ export default async function Page({ let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg().catch((error) => { - console.warn(error); - return null; - }); + const org: Organization | null = await getDefaultOrg(); if (org) { defaultOrganization = org.id; } diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 4fc1e54457..9731e4030e 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -26,10 +26,7 @@ export default async function Page({ let defaultOrganization; if (!organization) { const org: Organization | null = await getDefaultOrg(); - // .catch((error) => { - // console.warn(error); - // return null; - // }); + if (org) { defaultOrganization = org.id; } From 7a40a3c4f4e833a3c99e8777e8933f59dde71ed7 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:46:27 +0100 Subject: [PATCH 401/640] chore: correct ready check for acceptance tests --- acceptance/docker-compose.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index b106e5a89d..e8448ddb00 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -32,8 +32,7 @@ services: wait_for_zitadel: image: curlimages/curl:8.00.1 - command: /bin/sh -c "until curl -s -o /dev/null -w '%{http_code}' http://zitadel:8080/debug/ready | grep -q 200; do sleep 5; done" - + command: /bin/sh -c "until curl -s -o /dev/null -i -f http://zitadel:8080/debug/ready; do echo 'waiting' && sleep 1; done; echo 'ready' && sleep 5;" || false depends_on: - zitadel From 726d97ffa01afbd0217798a78be2f4496d588d73 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:49:45 +0100 Subject: [PATCH 402/640] chore: clean up unused functions --- acceptance/tests/passkey.ts | 50 +------------------------------------ 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/acceptance/tests/passkey.ts b/acceptance/tests/passkey.ts index 8ee895b7b1..4e336e7732 100644 --- a/acceptance/tests/passkey.ts +++ b/acceptance/tests/passkey.ts @@ -1,26 +1,6 @@ import {expect, Page} from "@playwright/test"; import {CDPSession} from "playwright-core"; -const BASE64_ENCODED_PK = - "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDbBOu5Lhs4vpowbCnmCyLUpIE7JM9sm9QXzye2G+jr+Kr" + - "MsinWohEce47BFPJlTaDzHSvOW2eeunBO89ZcvvVc8RLz4qyQ8rO98xS1jtgqi1NcBPETDrtzthODu/gd0sjB2Tk3TLuBGV" + - "oPXt54a+Oo4JbBJ6h3s0+5eAfGplCbSNq6hN3Jh9YOTw5ZA6GCEy5l8zBaOgjXytd2v2OdSVoEDNiNQRkjJd2rmS2oi9AyQ" + - "FR3B7BrPSiDlCcITZFOWgLF5C31Wp/PSHwQhlnh7/6YhnE2y9tzsUvzx0wJXrBADW13+oMxrneDK3WGbxTNYgIi1PvSqXlq" + - "GjHtCK+R2QkXAgMBAAECggEAVc6bu7VAnP6v0gDOeX4razv4FX/adCao9ZsHZ+WPX8PQxtmWYqykH5CY4TSfsuizAgyPuQ0" + - "+j4Vjssr9VODLqFoanspT6YXsvaKanncUYbasNgUJnfnLnw3an2XpU2XdmXTNYckCPRX9nsAAURWT3/n9ljc/XYY22ecYxM" + - "8sDWnHu2uKZ1B7M3X60bQYL5T/lVXkKdD6xgSNLeP4AkRx0H4egaop68hoW8FIwmDPVWYVAvo8etzWCtibRXz5FcNld9MgD" + - "/Ai7ycKy4Q1KhX5GBFI79MVVaHkSQfxPHpr7/XcmpQOEAr+BMPon4s4vnKqAGdGB3j/E3d/+4F2swykoQKBgQD8hCsp6FIQ" + - "5umJlk9/j/nGsMl85LgLaNVYpWlPRKPc54YNumtvj5vx1BG+zMbT7qIE3nmUPTCHP7qb5ERZG4CdMCS6S64/qzZEqijLCqe" + - "pwj6j4fV5SyPWEcpxf6ehNdmcfgzVB3Wolfwh1ydhx/96L1jHJcTKchdJJzlfTvq8wwKBgQDeCnKws1t5GapfE1rmC/h4ol" + - "L2qZTth9oQmbrXYohVnoqNFslDa43ePZwL9Jmd9kYb0axOTNMmyrP0NTj41uCfgDS0cJnNTc63ojKjegxHIyYDKRZNVUR/d" + - "xAYB/vPfBYZUS7M89pO6LLsHhzS3qpu3/hppo/Uc/AM/r8PSflNHQKBgDnWgBh6OQncChPUlOLv9FMZPR1ZOfqLCYrjYEqi" + - "uzGm6iKM13zXFO4AGAxu1P/IAd5BovFcTpg79Z8tWqZaUUwvscnl+cRlj+mMXAmdqCeO8VASOmqM1ml667axeZDIR867ZG8" + - "K5V029Wg+4qtX5uFypNAAi6GfHkxIKrD04yOHAoGACdh4wXESi0oiDdkz3KOHPwIjn6BhZC7z8mx+pnJODU3cYukxv3WTct" + - "lUhAsyjJiQ/0bK1yX87ulqFVgO0Knmh+wNajrb9wiONAJTMICG7tiWJOm7fW5cfTJwWkBwYADmkfTRmHDvqzQSSvoC2S7aa" + - "9QulbC3C/qgGFNrcWgcT9kCgYAZTa1P9bFCDU7hJc2mHwJwAW7/FQKEJg8SL33KINpLwcR8fqaYOdAHWWz636osVEqosRrH" + - "zJOGpf9x2RSWzQJ+dq8+6fACgfFZOVpN644+sAHfNPAI/gnNKU5OfUv+eav8fBnzlf1A3y3GIkyMyzFN3DE7e0n/lyqxE4H" + - "BYGpI8g=="; - interface session { client: CDPSession authenticatorId: string @@ -127,32 +107,4 @@ async function simulateSuccessfulPasskeyInput(client: CDPSession, authenticatorI // wait to receive the event that the passkey was successfully registered or verified await operationCompleted; } - -async function simulateFailedPasskeyInput(client: CDPSession, authenticatorId: string, operationTrigger: () => Promise, postOperationCheck: () => Promise) { - // set isUserVerified option to false - // (so that subsequent passkey operations will fail) - await client.send('WebAuthn.setUserVerified', { - authenticatorId: authenticatorId, - isUserVerified: false, - }); - - // set automaticPresenceSimulation option to true - // (so that the virtual authenticator will respond to the next passkey prompt) - await client.send('WebAuthn.setAutomaticPresenceSimulation', { - authenticatorId: authenticatorId, - enabled: true, - }); - - // perform a user action that triggers passkey prompt - await operationTrigger(); - - // wait for an expected UI change that indicates the passkey operation has completed - await postOperationCheck(); - - // set automaticPresenceSimulation option back to false - await client.send('WebAuthn.setAutomaticPresenceSimulation', { - authenticatorId, - enabled: false, - }); -} - +s \ No newline at end of file From 0cbde4c74b860e39bd5f81f2deeb51389ee1512c Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:32:18 +0100 Subject: [PATCH 403/640] chore: add wait for default organization to setup --- acceptance/setup.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/acceptance/setup.sh b/acceptance/setup.sh index aed1f1d8de..7c8dd2c011 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -34,3 +34,14 @@ DEBUG=true" > ${WRITE_ENVIRONMENT_FILE} echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}" cat ${WRITE_ENVIRONMENT_FILE} + +# waiting for default organization +until $(echo "$DEFAULTORG_RESPONSE" | jq -r '.result | length') == 1 +do + DEFAULTORG_RESPONSE=$(curl -s --request POST \ + --url "${ZITADEL_API_INTERNAL_URL}/v2/organizations/_search" \ + --header "Authorization: Bearer ${PAT}" \ + --header "Host: ${ZITADEL_API_DOMAIN}" \ + -d '{"queries": [{"defaultQuery": {}}]}' ) + echo "Received default organization response: ${DEFAULTORG_RESPONSE}" +done From b43db1ff2bd2ec1c736e3a7c160ad77516e98943 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 14 Nov 2024 15:43:29 +0100 Subject: [PATCH 404/640] change setup script --- acceptance/setup.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/acceptance/setup.sh b/acceptance/setup.sh index 7c8dd2c011..1756cf428e 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -36,12 +36,18 @@ echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}" cat ${WRITE_ENVIRONMENT_FILE} # waiting for default organization -until $(echo "$DEFAULTORG_RESPONSE" | jq -r '.result | length') == 1 -do +until [ "$(echo "$DEFAULTORG_RESPONSE" | jq -r '.result | length')" -eq 1 ]; do DEFAULTORG_RESPONSE=$(curl -s --request POST \ --url "${ZITADEL_API_INTERNAL_URL}/v2/organizations/_search" \ --header "Authorization: Bearer ${PAT}" \ --header "Host: ${ZITADEL_API_DOMAIN}" \ - -d '{"queries": [{"defaultQuery": {}}]}' ) + -d '{"queries": [{"defaultQuery": {}}]}' ) + + if [ $? -ne 0 ]; then + echo "Error: Failed to fetch default organization response" + exit 1 + fi + echo "Received default organization response: ${DEFAULTORG_RESPONSE}" -done + sleep 5 +done \ No newline at end of file From 414be0343cab4e5595ff0766cd0653f520eb36f6 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 14 Nov 2024 15:54:39 +0100 Subject: [PATCH 405/640] revert :D and set value --- acceptance/setup.sh | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/acceptance/setup.sh b/acceptance/setup.sh index 1756cf428e..90f40191de 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -36,18 +36,12 @@ echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}" cat ${WRITE_ENVIRONMENT_FILE} # waiting for default organization -until [ "$(echo "$DEFAULTORG_RESPONSE" | jq -r '.result | length')" -eq 1 ]; do +until $(echo "$DEFAULTORG_RESPONSE" | jq -r '.result | length') == 1 +do DEFAULTORG_RESPONSE=$(curl -s --request POST \ --url "${ZITADEL_API_INTERNAL_URL}/v2/organizations/_search" \ --header "Authorization: Bearer ${PAT}" \ --header "Host: ${ZITADEL_API_DOMAIN}" \ - -d '{"queries": [{"defaultQuery": {}}]}' ) - - if [ $? -ne 0 ]; then - echo "Error: Failed to fetch default organization response" - exit 1 - fi - + -d '{"queries": [{"defaultQuery": {"value": true}}]}' ) echo "Received default organization response: ${DEFAULTORG_RESPONSE}" - sleep 5 -done \ No newline at end of file +done From 882b3c59a278e233ef7e1f81b2325be7f87ad906 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:33:58 +0100 Subject: [PATCH 406/640] chore: add wait for default organization to setup --- acceptance/setup.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/acceptance/setup.sh b/acceptance/setup.sh index 7c8dd2c011..525819f417 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -35,13 +35,14 @@ DEBUG=true" > ${WRITE_ENVIRONMENT_FILE} echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}" cat ${WRITE_ENVIRONMENT_FILE} +DEFAULTORG_RESPONSE_RESULTS=0 # waiting for default organization -until $(echo "$DEFAULTORG_RESPONSE" | jq -r '.result | length') == 1 +until [ ${DEFAULTORG_RESPONSE_RESULTS} -eq 1 ] do - DEFAULTORG_RESPONSE=$(curl -s --request POST \ + DEFAULTORG_RESPONSE_RESULTS=$(curl -s --request POST \ --url "${ZITADEL_API_INTERNAL_URL}/v2/organizations/_search" \ --header "Authorization: Bearer ${PAT}" \ --header "Host: ${ZITADEL_API_DOMAIN}" \ - -d '{"queries": [{"defaultQuery": {}}]}' ) - echo "Received default organization response: ${DEFAULTORG_RESPONSE}" + -d '{"queries": [{"defaultQuery": {}}]}' | jq -r '.result | length') + echo "Received default organization response results: ${DEFAULTORG_RESPONSE_RESULTS}" done From e44ef268aa8108e61e08d3f2bfab0afb3f6c5e3d Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:42:41 +0100 Subject: [PATCH 407/640] chore: add wait for default organization to setup --- acceptance/setup.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/acceptance/setup.sh b/acceptance/setup.sh index a9b3cf6d31..2f770db88d 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -35,10 +35,13 @@ DEBUG=true" > ${WRITE_ENVIRONMENT_FILE} echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}" cat ${WRITE_ENVIRONMENT_FILE} +sleep 10s + DEFAULTORG_RESPONSE_RESULTS=0 # waiting for default organization until [ ${DEFAULTORG_RESPONSE_RESULTS} -eq 1 ] do + sleep 1s DEFAULTORG_RESPONSE_RESULTS=$(curl -s --request POST \ --url "${ZITADEL_API_INTERNAL_URL}/v2/organizations/_search" \ --header "Authorization: Bearer ${PAT}" \ From 83db788995f55be95f03fe5e041f5a8c5473812f Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:02:36 +0100 Subject: [PATCH 408/640] chore: add wait for default organization to setup --- acceptance/setup.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/acceptance/setup.sh b/acceptance/setup.sh index 2f770db88d..e010785e6e 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -42,10 +42,12 @@ DEFAULTORG_RESPONSE_RESULTS=0 until [ ${DEFAULTORG_RESPONSE_RESULTS} -eq 1 ] do sleep 1s - DEFAULTORG_RESPONSE_RESULTS=$(curl -s --request POST \ + DEFAULTORG_RESPONSE=$(curl -s --request POST \ --url "${ZITADEL_API_INTERNAL_URL}/v2/organizations/_search" \ --header "Authorization: Bearer ${PAT}" \ --header "Host: ${ZITADEL_API_DOMAIN}" \ - -d '{"queries": [{"defaultQuery": {"value": true}}]}' | jq -r '.result | length') - echo "Received default organization response results: ${DEFAULTORG_RESPONSE_RESULTS}" + -d '{"queries": [{"defaultQuery": {"value": true}}]}') + echo "Received default organization response: ${DEFAULTORG_RESPONSE}" + DEFAULTORG_RESPONSE_RESULTS=$(echo $DEFAULTORG_RESPONSE | jq -r '.result | length') + echo "Received default organization response result: ${DEFAULTORG_RESPONSE_RESULTS}" done From c01209ed2e9904cd92f0138cff3800f029ca29a1 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:25:43 +0100 Subject: [PATCH 409/640] chore: add wait for default organization to setup --- acceptance/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/setup.sh b/acceptance/setup.sh index e010785e6e..9ac85627c2 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -46,7 +46,7 @@ do --url "${ZITADEL_API_INTERNAL_URL}/v2/organizations/_search" \ --header "Authorization: Bearer ${PAT}" \ --header "Host: ${ZITADEL_API_DOMAIN}" \ - -d '{"queries": [{"defaultQuery": {"value": true}}]}') + -d '{"queries": [{"default_query": {}}]}' ) echo "Received default organization response: ${DEFAULTORG_RESPONSE}" DEFAULTORG_RESPONSE_RESULTS=$(echo $DEFAULTORG_RESPONSE | jq -r '.result | length') echo "Received default organization response result: ${DEFAULTORG_RESPONSE_RESULTS}" From 97a02a671d7e67cd1474be420c1ea253ac64b156 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:31:54 +0100 Subject: [PATCH 410/640] chore: add wait for default organization to setup --- acceptance/setup.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/acceptance/setup.sh b/acceptance/setup.sh index 9ac85627c2..689ff09823 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -35,18 +35,15 @@ DEBUG=true" > ${WRITE_ENVIRONMENT_FILE} echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}" cat ${WRITE_ENVIRONMENT_FILE} -sleep 10s - DEFAULTORG_RESPONSE_RESULTS=0 # waiting for default organization until [ ${DEFAULTORG_RESPONSE_RESULTS} -eq 1 ] do - sleep 1s DEFAULTORG_RESPONSE=$(curl -s --request POST \ --url "${ZITADEL_API_INTERNAL_URL}/v2/organizations/_search" \ --header "Authorization: Bearer ${PAT}" \ --header "Host: ${ZITADEL_API_DOMAIN}" \ - -d '{"queries": [{"default_query": {}}]}' ) + -d "{\"queries\": [{\"defaultQuery\":{}}]}" ) echo "Received default organization response: ${DEFAULTORG_RESPONSE}" DEFAULTORG_RESPONSE_RESULTS=$(echo $DEFAULTORG_RESPONSE | jq -r '.result | length') echo "Received default organization response result: ${DEFAULTORG_RESPONSE_RESULTS}" From ff355f64ab2f631b2b015487f32502f461848a95 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:43:08 +0100 Subject: [PATCH 411/640] chore: add wait for default organization to setup --- acceptance/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index e8448ddb00..25fe770dba 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -1,7 +1,7 @@ services: zitadel: user: "${ZITADEL_DEV_UID}" - image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:latest}" + image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:v2.65.0}" command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled --config /zitadel.yaml --steps /zitadel.yaml' ports: - "8080:8080" From 2bf1e09f688416f1c6e5d12962927f47d13ace02 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:01:04 +0100 Subject: [PATCH 412/640] chore: add wait for default organization to setup --- acceptance/docker-compose.yaml | 2 +- acceptance/setup.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index 25fe770dba..e8448ddb00 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -1,7 +1,7 @@ services: zitadel: user: "${ZITADEL_DEV_UID}" - image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:v2.65.0}" + image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:latest}" command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled --config /zitadel.yaml --steps /zitadel.yaml' ports: - "8080:8080" diff --git a/acceptance/setup.sh b/acceptance/setup.sh index 689ff09823..01b6bd826e 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -43,6 +43,7 @@ do --url "${ZITADEL_API_INTERNAL_URL}/v2/organizations/_search" \ --header "Authorization: Bearer ${PAT}" \ --header "Host: ${ZITADEL_API_DOMAIN}" \ + --header "Content-Type: application/json" \ -d "{\"queries\": [{\"defaultQuery\":{}}]}" ) echo "Received default organization response: ${DEFAULTORG_RESPONSE}" DEFAULTORG_RESPONSE_RESULTS=$(echo $DEFAULTORG_RESPONSE | jq -r '.result | length') From 6a6284caa7c6f5a8076aec65a642e7272d19a055 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:28:43 +0100 Subject: [PATCH 413/640] chore: add wait for default organization to setup --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3dd3cca341..6bd2abcf7c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,7 @@ on: pull_request jobs: quality: env: - ZITADEL_IMAGE: ghcr.io/zitadel/zitadel:v2.63.4 + ZITADEL_IMAGE: ghcr.io/zitadel/zitadel:v2.65.0 POSTGRES_IMAGE: postgres:17.0-alpine3.19 name: Ensure Quality From d79ee0f19e0756d8fff7c79fe37bfe8b411f1176 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:29:48 +0100 Subject: [PATCH 414/640] chore: otp starting --- acceptance/tests/otp.ts | 31 ++++++++++++++++ .../tests/username-password-otp_sms.spec.ts | 36 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 acceptance/tests/otp.ts create mode 100644 acceptance/tests/username-password-otp_sms.spec.ts diff --git a/acceptance/tests/otp.ts b/acceptance/tests/otp.ts new file mode 100644 index 0000000000..b08e430f21 --- /dev/null +++ b/acceptance/tests/otp.ts @@ -0,0 +1,31 @@ +import * as http from "node:http"; + +let messages = new Map(); + +export function startSink() { + const hostname = "127.0.0.1" + const port = 3030 + + const server = http.createServer((req, res) => { + console.log("Sink received message: ") + let body = ''; + req.on('data', (chunk) => { + body += chunk; + }); + + req.on('end', () => { + console.log(body); + const data = JSON.parse(body) + messages.set(data.contextInfo.recipientEmailAddress, data.args.code) + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.write('OK'); + res.end(); + }); + }); + + server.listen(port, hostname, () => { + console.log(`Sink running at http://${hostname}:${port}/`); + }); + return server +} \ No newline at end of file diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts new file mode 100644 index 0000000000..9caf8bd5ce --- /dev/null +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -0,0 +1,36 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + +test("username, password and otp login", async ({user, page}) => { + const server = startSink() + await loginWithPassword(page, user.getUsername(), user.getPassword()) + + + await loginScreenExpect(page, user.getFullName()); + server.close() +}); + + From 96f1db9a84986b0435679b0477e0f36640451ee3 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Fri, 15 Nov 2024 13:34:06 +0100 Subject: [PATCH 415/640] chore: fixes to tests --- acceptance/tests/login.ts | 1 + acceptance/tests/passkey.ts | 1 - acceptance/tests/user.ts | 19 +++++-------------- .../tests/username-password-otp_sms.spec.ts | 4 ++-- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/acceptance/tests/login.ts b/acceptance/tests/login.ts index 5732ae495c..ff3c13e6f2 100644 --- a/acceptance/tests/login.ts +++ b/acceptance/tests/login.ts @@ -19,6 +19,7 @@ export async function loginWithPasskey(page: Page, authenticatorId: string, user } export async function loginScreenExpect(page: Page, fullName: string) { + await expect(page).toHaveURL(/signedin.*/) await expect(page.getByRole('heading')).toContainText(fullName); } diff --git a/acceptance/tests/passkey.ts b/acceptance/tests/passkey.ts index 4e336e7732..7284a102e8 100644 --- a/acceptance/tests/passkey.ts +++ b/acceptance/tests/passkey.ts @@ -107,4 +107,3 @@ async function simulateSuccessfulPasskeyInput(client: CDPSession, authenticatorI // wait to receive the event that the passkey was successfully registered or verified await operationCompleted; } -s \ No newline at end of file diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index b29fd9af46..bf4f7b2662 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -1,8 +1,6 @@ import fetch from "node-fetch"; import {Page} from "@playwright/test"; import {registerWithPasskey} from "./register"; -import {loginWithPassword} from "./login"; -import {changePassword} from "./password"; import {getUserByUsername, removeUser} from './zitadel'; export interface userProps { @@ -59,7 +57,11 @@ class User { } async remove() { - await removeUser(this.getUserId()) + const resp = await getUserByUsername(this.getUsername()) + if (!resp || !resp.result || !resp.result[0]) { + return + } + await removeUser(resp.result[0].userId) return } @@ -90,12 +92,6 @@ class User { public getFullName() { return this.props.firstName + " " + this.props.lastName } - - public async doPasswordChange(page: Page, password: string) { - await loginWithPassword(page, this.getUsername(), this.getPassword()) - await changePassword(page, this.getUsername(), password) - this.props.password = password - } } export class PasswordUser extends User { @@ -191,11 +187,6 @@ export class PasskeyUser extends User { } public async remove() { - const resp = await getUserByUsername(this.getUsername()) - if (!resp || !resp.result || !resp.result[0]) { - return - } - this.setUserId(resp.result[0].userId) await super.remove() } diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index 9caf8bd5ce..0c733a6faf 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -25,12 +25,12 @@ const test = base.extend<{ user: PasswordUserWithOTP }>({ }); test("username, password and otp login", async ({user, page}) => { - const server = startSink() + //const server = startSink() await loginWithPassword(page, user.getUsername(), user.getPassword()) await loginScreenExpect(page, user.getFullName()); - server.close() + //server.close() }); From f5c7a176173c5a9a7fe1548f637dab566010ac18 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 15 Nov 2024 13:39:54 +0100 Subject: [PATCH 416/640] missing deps --- package.json | 3 ++- pnpm-lock.yaml | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 2266c62bbc..642c0df2ce 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "build": "turbo run build", "test": "turbo run test", "start": "turbo run start", - "start:built": "turbo run start:built", + "start:built": "turbo run start:built", "test:unit": "turbo run test:unit -- --passWithNoTests", "test:integration": "turbo run test:integration", "test:acceptance": "pnpm exec playwright test", @@ -34,6 +34,7 @@ "@types/node": "^22.9.0", "@vitejs/plugin-react": "^4.3.3", "@zitadel/prettier-config": "workspace:*", + "dotenv": "^16.4.5", "eslint": "8.57.1", "eslint-config-zitadel": "workspace:*", "prettier": "^3.2.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b73dee807c..185010931e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@zitadel/prettier-config': specifier: workspace:* version: link:packages/zitadel-prettier-config + dotenv: + specifier: ^16.4.5 + version: 16.4.5 eslint: specifier: 8.57.1 version: 8.57.1 @@ -2071,6 +2074,10 @@ packages: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + dprint-node@1.0.8: resolution: {integrity: sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==} @@ -6368,6 +6375,8 @@ snapshots: dotenv@16.0.3: {} + dotenv@16.4.5: {} + dprint-node@1.0.8: dependencies: detect-libc: 1.0.3 @@ -6619,7 +6628,7 @@ snapshots: debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 @@ -6632,7 +6641,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: @@ -6653,7 +6662,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 From bdd03572112ed9f268d8f189509b408013e76706 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 15 Nov 2024 13:40:47 +0100 Subject: [PATCH 417/640] lint --- apps/login/src/components/authentication-method-radio.tsx | 2 +- apps/login/src/lib/server/session.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/login/src/components/authentication-method-radio.tsx b/apps/login/src/components/authentication-method-radio.tsx index b01f8efdec..9e0af95921 100644 --- a/apps/login/src/components/authentication-method-radio.tsx +++ b/apps/login/src/components/authentication-method-radio.tsx @@ -30,7 +30,7 @@ export function AuthenticationMethodRadio({ `${ active diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 160e6d9de4..4c0836879d 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -7,7 +7,6 @@ import { import { deleteSession, listAuthenticationMethodTypes } from "@/lib/zitadel"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; -import { headers } from "next/headers"; import { getMostRecentSessionCookie, getSessionCookieById, @@ -68,7 +67,7 @@ export async function updateSession(options: UpdateSessionCommand) { }); // TODO remove ports from host header for URL with port - const host = "localhost" + const host = "localhost"; if ( host && From c9eb18a7bf6dafa102fa46c558d33b95ebda6acf Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 15 Nov 2024 13:48:42 +0100 Subject: [PATCH 418/640] lint --- acceptance/tests/admin.spec.ts | 10 +- acceptance/tests/login.ts | 28 +- acceptance/tests/loginname-screen.ts | 10 +- acceptance/tests/loginname.ts | 8 +- acceptance/tests/otp.ts | 44 +-- acceptance/tests/passkey.ts | 140 ++++---- acceptance/tests/password-screen.ts | 72 +++-- acceptance/tests/password.ts | 19 +- acceptance/tests/register-screen.ts | 30 +- acceptance/tests/register.spec.ts | 44 +-- acceptance/tests/register.ts | 33 +- acceptance/tests/user.ts | 305 +++++++++--------- acceptance/tests/username-passkey.spec.ts | 38 +-- .../tests/username-password-changed.spec.ts | 64 ++-- .../tests/username-password-otp_sms.spec.ts | 50 ++- acceptance/tests/zitadel.ts | 84 ++--- playwright.config.ts | 104 +++--- 17 files changed, 547 insertions(+), 536 deletions(-) diff --git a/acceptance/tests/admin.spec.ts b/acceptance/tests/admin.spec.ts index 3702250b0d..52db893528 100644 --- a/acceptance/tests/admin.spec.ts +++ b/acceptance/tests/admin.spec.ts @@ -1,7 +1,7 @@ -import {test} from "@playwright/test"; -import {loginScreenExpect, loginWithPassword} from "./login"; +import { test } from "@playwright/test"; +import { loginScreenExpect, loginWithPassword } from "./login"; -test("admin login", async ({page}) => { - await loginWithPassword(page, "zitadel-admin@zitadel.localhost", "Password1.") - await loginScreenExpect(page, "ZITADEL Admin"); +test("admin login", async ({ page }) => { + await loginWithPassword(page, "zitadel-admin@zitadel.localhost", "Password1."); + await loginScreenExpect(page, "ZITADEL Admin"); }); diff --git a/acceptance/tests/login.ts b/acceptance/tests/login.ts index ff3c13e6f2..a9642573fe 100644 --- a/acceptance/tests/login.ts +++ b/acceptance/tests/login.ts @@ -1,30 +1,28 @@ -import {expect, Page} from "@playwright/test"; -import {loginname} from "./loginname"; -import {password} from "./password"; +import { expect, Page } from "@playwright/test"; +import { loginname } from "./loginname"; +import { password } from "./password"; export async function startLogin(page: Page) { - await page.goto("/loginname"); + await page.goto("/loginname"); } export async function loginWithPassword(page: Page, username: string, pw: string) { - await startLogin(page); - await loginname(page, username); - await password(page, pw); + await startLogin(page); + await loginname(page, username); + await password(page, pw); } export async function loginWithPasskey(page: Page, authenticatorId: string, username: string) { - await startLogin(page); - await loginname(page, username); - // await passkey(page, authenticatorId); + await startLogin(page); + await loginname(page, username); + // await passkey(page, authenticatorId); } export async function loginScreenExpect(page: Page, fullName: string) { - await expect(page).toHaveURL(/signedin.*/) - await expect(page.getByRole('heading')).toContainText(fullName); + await expect(page).toHaveURL(/signedin.*/); + await expect(page.getByRole("heading")).toContainText(fullName); } export async function loginWithOTP(page: Page, username: string, password: string) { - await loginWithPassword(page, username, password); - - + await loginWithPassword(page, username, password); } diff --git a/acceptance/tests/loginname-screen.ts b/acceptance/tests/loginname-screen.ts index bed33226f7..50f716d03b 100644 --- a/acceptance/tests/loginname-screen.ts +++ b/acceptance/tests/loginname-screen.ts @@ -1,12 +1,12 @@ -import {expect, Page} from "@playwright/test"; +import { expect, Page } from "@playwright/test"; -const usernameUserInput = "username-text-input" +const usernameUserInput = "username-text-input"; export async function loginnameScreen(page: Page, username: string) { - await page.getByTestId(usernameUserInput).pressSequentially(username); + await page.getByTestId(usernameUserInput).pressSequentially(username); } export async function loginnameScreenExpect(page: Page, username: string) { - await expect(page.getByTestId(usernameUserInput)).toHaveValue(username); - await expect(page.getByTestId('error').locator('div')).toContainText("Could not find user") + await expect(page.getByTestId(usernameUserInput)).toHaveValue(username); + await expect(page.getByTestId("error").locator("div")).toContainText("Could not find user"); } diff --git a/acceptance/tests/loginname.ts b/acceptance/tests/loginname.ts index 5e6719622a..2050ec1d3c 100644 --- a/acceptance/tests/loginname.ts +++ b/acceptance/tests/loginname.ts @@ -1,7 +1,7 @@ -import {Page} from "@playwright/test"; -import {loginnameScreen} from "./loginname-screen"; +import { Page } from "@playwright/test"; +import { loginnameScreen } from "./loginname-screen"; export async function loginname(page: Page, username: string) { - await loginnameScreen(page, username) - await page.getByTestId("submit-button").click() + await loginnameScreen(page, username); + await page.getByTestId("submit-button").click(); } diff --git a/acceptance/tests/otp.ts b/acceptance/tests/otp.ts index b08e430f21..85d3258442 100644 --- a/acceptance/tests/otp.ts +++ b/acceptance/tests/otp.ts @@ -3,29 +3,29 @@ import * as http from "node:http"; let messages = new Map(); export function startSink() { - const hostname = "127.0.0.1" - const port = 3030 + const hostname = "127.0.0.1"; + const port = 3030; - const server = http.createServer((req, res) => { - console.log("Sink received message: ") - let body = ''; - req.on('data', (chunk) => { - body += chunk; - }); - - req.on('end', () => { - console.log(body); - const data = JSON.parse(body) - messages.set(data.contextInfo.recipientEmailAddress, data.args.code) - res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.write('OK'); - res.end(); - }); + const server = http.createServer((req, res) => { + console.log("Sink received message: "); + let body = ""; + req.on("data", (chunk) => { + body += chunk; }); - server.listen(port, hostname, () => { - console.log(`Sink running at http://${hostname}:${port}/`); + req.on("end", () => { + console.log(body); + const data = JSON.parse(body); + messages.set(data.contextInfo.recipientEmailAddress, data.args.code); + res.statusCode = 200; + res.setHeader("Content-Type", "text/plain"); + res.write("OK"); + res.end(); }); - return server -} \ No newline at end of file + }); + + server.listen(port, hostname, () => { + console.log(`Sink running at http://${hostname}:${port}/`); + }); + return server; +} diff --git a/acceptance/tests/passkey.ts b/acceptance/tests/passkey.ts index 7284a102e8..d8cda10ddb 100644 --- a/acceptance/tests/passkey.ts +++ b/acceptance/tests/passkey.ts @@ -1,109 +1,109 @@ -import {expect, Page} from "@playwright/test"; -import {CDPSession} from "playwright-core"; +import { expect, Page } from "@playwright/test"; +import { CDPSession } from "playwright-core"; interface session { - client: CDPSession - authenticatorId: string + client: CDPSession; + authenticatorId: string; } async function client(page: Page): Promise { - const cdpSession = await page.context().newCDPSession(page); - await cdpSession.send('WebAuthn.enable', {enableUI: false}); - const result = await cdpSession.send('WebAuthn.addVirtualAuthenticator', { - options: { - protocol: 'ctap2', - transport: 'internal', - hasResidentKey: true, - hasUserVerification: true, - isUserVerified: true, - automaticPresenceSimulation: true, - }, - }); - return {client: cdpSession, authenticatorId: result.authenticatorId}; + const cdpSession = await page.context().newCDPSession(page); + await cdpSession.send("WebAuthn.enable", { enableUI: false }); + const result = await cdpSession.send("WebAuthn.addVirtualAuthenticator", { + options: { + protocol: "ctap2", + transport: "internal", + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + automaticPresenceSimulation: true, + }, + }); + return { client: cdpSession, authenticatorId: result.authenticatorId }; } export async function passkeyRegister(page: Page): Promise { - const session = await client(page) + const session = await client(page); - await passkeyNotExisting(session.client, session.authenticatorId); - await simulateSuccessfulPasskeyRegister( - session.client, - session.authenticatorId, - () => - page.getByTestId("submit-button").click() - ); - await passkeyRegistered(session.client, session.authenticatorId); + await passkeyNotExisting(session.client, session.authenticatorId); + await simulateSuccessfulPasskeyRegister(session.client, session.authenticatorId, () => + page.getByTestId("submit-button").click(), + ); + await passkeyRegistered(session.client, session.authenticatorId); - return session.authenticatorId + return session.authenticatorId; } export async function passkey(page: Page, authenticatorId: string) { - const cdpSession = await page.context().newCDPSession(page); - await cdpSession.send('WebAuthn.enable', {enableUI: false}); + const cdpSession = await page.context().newCDPSession(page); + await cdpSession.send("WebAuthn.enable", { enableUI: false }); - const signCount = await passkeyExisting(cdpSession, authenticatorId); + const signCount = await passkeyExisting(cdpSession, authenticatorId); - await simulateSuccessfulPasskeyInput( - cdpSession, - authenticatorId, - () => - page.getByTestId("submit-button").click() - ); + await simulateSuccessfulPasskeyInput(cdpSession, authenticatorId, () => page.getByTestId("submit-button").click()); - await passkeyUsed(cdpSession, authenticatorId, signCount); + await passkeyUsed(cdpSession, authenticatorId, signCount); } async function passkeyNotExisting(client: CDPSession, authenticatorId: string) { - const result = await client.send('WebAuthn.getCredentials', {authenticatorId}); - expect(result.credentials).toHaveLength(0); + const result = await client.send("WebAuthn.getCredentials", { authenticatorId }); + expect(result.credentials).toHaveLength(0); } async function passkeyRegistered(client: CDPSession, authenticatorId: string) { - const result = await client.send('WebAuthn.getCredentials', {authenticatorId}); - expect(result.credentials).toHaveLength(1); - await passkeyUsed(client, authenticatorId, 0); + const result = await client.send("WebAuthn.getCredentials", { authenticatorId }); + expect(result.credentials).toHaveLength(1); + await passkeyUsed(client, authenticatorId, 0); } async function passkeyExisting(client: CDPSession, authenticatorId: string): Promise { - const result = await client.send('WebAuthn.getCredentials', {authenticatorId}); - expect(result.credentials).toHaveLength(1); - return result.credentials[0].signCount + const result = await client.send("WebAuthn.getCredentials", { authenticatorId }); + expect(result.credentials).toHaveLength(1); + return result.credentials[0].signCount; } async function passkeyUsed(client: CDPSession, authenticatorId: string, signCount: number) { - const result = await client.send('WebAuthn.getCredentials', {authenticatorId}); - expect(result.credentials).toHaveLength(1); - expect(result.credentials[0].signCount).toBeGreaterThan(signCount); + const result = await client.send("WebAuthn.getCredentials", { authenticatorId }); + expect(result.credentials).toHaveLength(1); + expect(result.credentials[0].signCount).toBeGreaterThan(signCount); } -async function simulateSuccessfulPasskeyRegister(client: CDPSession, authenticatorId: string, operationTrigger: () => Promise) { - // initialize event listeners to wait for a successful passkey input event - const operationCompleted = new Promise(resolve => { - client.on('WebAuthn.credentialAdded', () => { - console.log('Credential Added!'); - resolve() - }); +async function simulateSuccessfulPasskeyRegister( + client: CDPSession, + authenticatorId: string, + operationTrigger: () => Promise, +) { + // initialize event listeners to wait for a successful passkey input event + const operationCompleted = new Promise((resolve) => { + client.on("WebAuthn.credentialAdded", () => { + console.log("Credential Added!"); + resolve(); }); + }); - // perform a user action that triggers passkey prompt - await operationTrigger(); + // perform a user action that triggers passkey prompt + await operationTrigger(); - // wait to receive the event that the passkey was successfully registered or verified - await operationCompleted; + // wait to receive the event that the passkey was successfully registered or verified + await operationCompleted; } -async function simulateSuccessfulPasskeyInput(client: CDPSession, authenticatorId: string, operationTrigger: () => Promise) { - // initialize event listeners to wait for a successful passkey input event - const operationCompleted = new Promise(resolve => { - client.on('WebAuthn.credentialAsserted', () => { - console.log('Credential Asserted!'); - resolve() - }); +async function simulateSuccessfulPasskeyInput( + client: CDPSession, + authenticatorId: string, + operationTrigger: () => Promise, +) { + // initialize event listeners to wait for a successful passkey input event + const operationCompleted = new Promise((resolve) => { + client.on("WebAuthn.credentialAsserted", () => { + console.log("Credential Asserted!"); + resolve(); }); + }); - // perform a user action that triggers passkey prompt - await operationTrigger(); + // perform a user action that triggers passkey prompt + await operationTrigger(); - // wait to receive the event that the passkey was successfully registered or verified - await operationCompleted; + // wait to receive the event that the passkey was successfully registered or verified + await operationCompleted; } diff --git a/acceptance/tests/password-screen.ts b/acceptance/tests/password-screen.ts index ee79bed176..49e8843822 100644 --- a/acceptance/tests/password-screen.ts +++ b/acceptance/tests/password-screen.ts @@ -1,47 +1,57 @@ -import {expect, Page} from "@playwright/test"; +import { expect, Page } from "@playwright/test"; -const passwordField = 'password-text-input' -const passwordConfirmField = 'password-confirm-text-input' -const lengthCheck = "length-check" -const symbolCheck = "symbol-check" -const numberCheck = "number-check" -const uppercaseCheck = "uppercase-check" -const lowercaseCheck = "lowercase-check" -const equalCheck = "equal-check" +const passwordField = "password-text-input"; +const passwordConfirmField = "password-confirm-text-input"; +const lengthCheck = "length-check"; +const symbolCheck = "symbol-check"; +const numberCheck = "number-check"; +const uppercaseCheck = "uppercase-check"; +const lowercaseCheck = "lowercase-check"; +const equalCheck = "equal-check"; -const matchText = "Matches" -const noMatchText = "Doesn\'t match" +const matchText = "Matches"; +const noMatchText = "Doesn't match"; export async function changePasswordScreen(page: Page, password1: string, password2: string) { - await page.getByTestId(passwordField).pressSequentially(password1); - await page.getByTestId(passwordConfirmField).pressSequentially(password2); + await page.getByTestId(passwordField).pressSequentially(password1); + await page.getByTestId(passwordConfirmField).pressSequentially(password2); } export async function passwordScreen(page: Page, password: string) { - await page.getByTestId(passwordField).pressSequentially(password); + await page.getByTestId(passwordField).pressSequentially(password); } export async function passwordScreenExpect(page: Page, password: string) { - await expect(page.getByTestId(passwordField)).toHaveValue(password); - await expect(page.getByTestId('error').locator('div')).toContainText("Could not verify password"); + await expect(page.getByTestId(passwordField)).toHaveValue(password); + await expect(page.getByTestId("error").locator("div")).toContainText("Could not verify password"); } -export async function changePasswordScreenExpect(page: Page, password1: string, password2: string, length: boolean, symbol: boolean, number: boolean, uppercase: boolean, lowercase: boolean, equals: boolean) { - await expect(page.getByTestId(passwordField)).toHaveValue(password1); - await expect(page.getByTestId(passwordConfirmField)).toHaveValue(password2); +export async function changePasswordScreenExpect( + page: Page, + password1: string, + password2: string, + length: boolean, + symbol: boolean, + number: boolean, + uppercase: boolean, + lowercase: boolean, + equals: boolean, +) { + await expect(page.getByTestId(passwordField)).toHaveValue(password1); + await expect(page.getByTestId(passwordConfirmField)).toHaveValue(password2); - await checkContent(page, lengthCheck, length); - await checkContent(page, symbolCheck, symbol); - await checkContent(page, numberCheck, number); - await checkContent(page, uppercaseCheck, uppercase); - await checkContent(page, lowercaseCheck, lowercase); - await checkContent(page, equalCheck, equals); + await checkContent(page, lengthCheck, length); + await checkContent(page, symbolCheck, symbol); + await checkContent(page, numberCheck, number); + await checkContent(page, uppercaseCheck, uppercase); + await checkContent(page, lowercaseCheck, lowercase); + await checkContent(page, equalCheck, equals); } async function checkContent(page: Page, testid: string, match: boolean) { - if (match) { - await expect(page.getByTestId(testid)).toContainText(matchText); - } else { - await expect(page.getByTestId(testid)).toContainText(noMatchText); - } -} \ No newline at end of file + if (match) { + await expect(page.getByTestId(testid)).toContainText(matchText); + } else { + await expect(page.getByTestId(testid)).toContainText(noMatchText); + } +} diff --git a/acceptance/tests/password.ts b/acceptance/tests/password.ts index 0deba87955..3943844252 100644 --- a/acceptance/tests/password.ts +++ b/acceptance/tests/password.ts @@ -1,20 +1,19 @@ -import {Page} from "@playwright/test"; -import {changePasswordScreen, passwordScreen} from "./password-screen"; - -const passwordSubmitButton = "submit-button" +import { Page } from "@playwright/test"; +import { changePasswordScreen, passwordScreen } from "./password-screen"; +const passwordSubmitButton = "submit-button"; export async function startChangePassword(page: Page, loginname: string) { - await page.goto('password/change?' + new URLSearchParams({loginName: loginname})); + await page.goto("password/change?" + new URLSearchParams({ loginName: loginname })); } export async function changePassword(page: Page, loginname: string, password: string) { - await startChangePassword(page, loginname); - await changePasswordScreen(page, password, password) - await page.getByTestId(passwordSubmitButton).click(); + await startChangePassword(page, loginname); + await changePasswordScreen(page, password, password); + await page.getByTestId(passwordSubmitButton).click(); } export async function password(page: Page, password: string) { - await passwordScreen(page, password) - await page.getByTestId(passwordSubmitButton).click() + await passwordScreen(page, password); + await page.getByTestId(passwordSubmitButton).click(); } diff --git a/acceptance/tests/register-screen.ts b/acceptance/tests/register-screen.ts index 83a3250588..414e38793b 100644 --- a/acceptance/tests/register-screen.ts +++ b/acceptance/tests/register-screen.ts @@ -1,27 +1,27 @@ -import {Page} from "@playwright/test"; +import { Page } from "@playwright/test"; -const passwordField = 'password-text-input' -const passwordConfirmField = 'password-confirm-text-input' +const passwordField = "password-text-input"; +const passwordConfirmField = "password-confirm-text-input"; export async function registerUserScreenPassword(page: Page, firstname: string, lastname: string, email: string) { - await registerUserScreen(page, firstname, lastname, email) - await page.getByTestId('Password-radio').click(); + await registerUserScreen(page, firstname, lastname, email); + await page.getByTestId("Password-radio").click(); } export async function registerUserScreenPasskey(page: Page, firstname: string, lastname: string, email: string) { - await registerUserScreen(page, firstname, lastname, email) - await page.getByTestId('Passkeys-radio').click(); + await registerUserScreen(page, firstname, lastname, email); + await page.getByTestId("Passkeys-radio").click(); } export async function registerPasswordScreen(page: Page, password1: string, password2: string) { - await page.getByTestId(passwordField).pressSequentially(password1); - await page.getByTestId(passwordConfirmField).pressSequentially(password2); + await page.getByTestId(passwordField).pressSequentially(password1); + await page.getByTestId(passwordConfirmField).pressSequentially(password2); } export async function registerUserScreen(page: Page, firstname: string, lastname: string, email: string) { - await page.getByTestId('firstname-text-input').pressSequentially(firstname); - await page.getByTestId('lastname-text-input').pressSequentially(lastname); - await page.getByTestId('email-text-input').pressSequentially(email); - await page.getByTestId('privacy-policy-checkbox').check(); - await page.getByTestId('tos-checkbox').check(); -} \ No newline at end of file + await page.getByTestId("firstname-text-input").pressSequentially(firstname); + await page.getByTestId("lastname-text-input").pressSequentially(lastname); + await page.getByTestId("email-text-input").pressSequentially(email); + await page.getByTestId("privacy-policy-checkbox").check(); + await page.getByTestId("tos-checkbox").check(); +} diff --git a/acceptance/tests/register.spec.ts b/acceptance/tests/register.spec.ts index 0e08db5614..ce95b61a04 100644 --- a/acceptance/tests/register.spec.ts +++ b/acceptance/tests/register.spec.ts @@ -1,30 +1,30 @@ -import {test} from "@playwright/test"; -import {registerWithPasskey, registerWithPassword} from './register'; -import {loginScreenExpect} from "./login"; -import {removeUserByUsername} from './zitadel'; -import path from 'path'; -import dotenv from 'dotenv'; +import { test } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { loginScreenExpect } from "./login"; +import { registerWithPasskey, registerWithPassword } from "./register"; +import { removeUserByUsername } from "./zitadel"; // Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); -test("register with password", async ({page}) => { - const username = "register-password@example.com" - const password = "Password1!" - const firstname = "firstname" - const lastname = "lastname" +test("register with password", async ({ page }) => { + const username = "register-password@example.com"; + const password = "Password1!"; + const firstname = "firstname"; + const lastname = "lastname"; - await removeUserByUsername(username) - await registerWithPassword(page, firstname, lastname, username, password, password) - await loginScreenExpect(page, firstname + " " + lastname); + await removeUserByUsername(username); + await registerWithPassword(page, firstname, lastname, username, password, password); + await loginScreenExpect(page, firstname + " " + lastname); }); -test("register with passkey", async ({page}) => { - const username = "register-passkey@example.com" - const firstname = "firstname" - const lastname = "lastname" +test("register with passkey", async ({ page }) => { + const username = "register-passkey@example.com"; + const firstname = "firstname"; + const lastname = "lastname"; - await removeUserByUsername(username) - await registerWithPasskey(page, firstname, lastname, username) - await loginScreenExpect(page, firstname + " " + lastname); + await removeUserByUsername(username); + await registerWithPasskey(page, firstname, lastname, username); + await loginScreenExpect(page, firstname + " " + lastname); }); diff --git a/acceptance/tests/register.ts b/acceptance/tests/register.ts index abff319437..f943e5bacc 100644 --- a/acceptance/tests/register.ts +++ b/acceptance/tests/register.ts @@ -1,18 +1,25 @@ -import {Page} from "@playwright/test"; -import {passkeyRegister} from './passkey'; -import {registerPasswordScreen, registerUserScreenPasskey, registerUserScreenPassword} from './register-screen'; +import { Page } from "@playwright/test"; +import { passkeyRegister } from "./passkey"; +import { registerPasswordScreen, registerUserScreenPasskey, registerUserScreenPassword } from "./register-screen"; -export async function registerWithPassword(page: Page, firstname: string, lastname: string, email: string, password1: string, password2: string) { - await page.goto('/register'); - await registerUserScreenPassword(page, firstname, lastname, email) - await page.getByTestId('submit-button').click(); - await registerPasswordScreen(page, password1, password2) - await page.getByTestId('submit-button').click(); +export async function registerWithPassword( + page: Page, + firstname: string, + lastname: string, + email: string, + password1: string, + password2: string, +) { + await page.goto("/register"); + await registerUserScreenPassword(page, firstname, lastname, email); + await page.getByTestId("submit-button").click(); + await registerPasswordScreen(page, password1, password2); + await page.getByTestId("submit-button").click(); } export async function registerWithPasskey(page: Page, firstname: string, lastname: string, email: string): Promise { - await page.goto('/register'); - await registerUserScreenPasskey(page, firstname, lastname, email) - await page.getByTestId('submit-button').click(); - return await passkeyRegister(page) + await page.goto("/register"); + await registerUserScreenPasskey(page, firstname, lastname, email); + await page.getByTestId("submit-button").click(); + return await passkeyRegister(page); } diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index bf4f7b2662..efa53ed3e4 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -1,196 +1,195 @@ +import { Page } from "@playwright/test"; import fetch from "node-fetch"; -import {Page} from "@playwright/test"; -import {registerWithPasskey} from "./register"; -import {getUserByUsername, removeUser} from './zitadel'; +import { registerWithPasskey } from "./register"; +import { getUserByUsername, removeUser } from "./zitadel"; export interface userProps { - email: string; - firstName: string; - lastName: string; - organization: string; - password: string; + email: string; + firstName: string; + lastName: string; + organization: string; + password: string; } class User { - private readonly props: userProps; - private user: string; + private readonly props: userProps; + private user: string; - constructor(userProps: userProps) { - this.props = userProps; + constructor(userProps: userProps) { + this.props = userProps; + } + + async ensure(page: Page) { + await this.remove(); + + const body = { + username: this.props.email, + organization: { + orgId: this.props.organization, + }, + profile: { + givenName: this.props.firstName, + familyName: this.props.lastName, + }, + email: { + email: this.props.email, + isVerified: true, + }, + password: { + password: this.props.password!, + }, + }; + + const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/human", { + method: "POST", + body: JSON.stringify(body), + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN!, + }, + }); + if (response.statusCode >= 400 && response.statusCode != 409) { + const error = "HTTP Error: " + response.statusCode + " - " + response.statusMessage; + console.error(error); + throw new Error(error); } + return; + } - async ensure(page: Page) { - await this.remove() - - const body = { - username: this.props.email, - organization: { - orgId: this.props.organization - }, - profile: { - givenName: this.props.firstName, - familyName: this.props.lastName, - }, - email: { - email: this.props.email, - isVerified: true, - }, - password: { - password: this.props.password!, - } - } - - const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/human", { - method: 'POST', - body: JSON.stringify(body), - headers: { - 'Content-Type': 'application/json', - 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! - } - }); - if (response.statusCode >= 400 && response.statusCode != 409) { - const error = 'HTTP Error: ' + response.statusCode + ' - ' + response.statusMessage; - console.error(error); - throw new Error(error); - } - return + async remove() { + const resp = await getUserByUsername(this.getUsername()); + if (!resp || !resp.result || !resp.result[0]) { + return; } + await removeUser(resp.result[0].userId); + return; + } - async remove() { - const resp = await getUserByUsername(this.getUsername()) - if (!resp || !resp.result || !resp.result[0]) { - return - } - await removeUser(resp.result[0].userId) - return - } + public setUserId(userId: string) { + this.user = userId; + } - public setUserId(userId: string) { - this.user = userId - } + public getUserId() { + return this.user; + } - public getUserId() { - return this.user; - } + public getUsername() { + return this.props.email; + } - public getUsername() { - return this.props.email; - } + public getPassword() { + return this.props.password; + } - public getPassword() { - return this.props.password; - } + public getFirstname() { + return this.props.firstName; + } - public getFirstname() { - return this.props.firstName - } + public getLastname() { + return this.props.lastName; + } - public getLastname() { - return this.props.lastName - } - - public getFullName() { - return this.props.firstName + " " + this.props.lastName - } + public getFullName() { + return this.props.firstName + " " + this.props.lastName; + } } -export class PasswordUser extends User { -} +export class PasswordUser extends User {} export enum OtpType { - sms = "sms", - email = "email", + sms = "sms", + email = "email", } export interface otpUserProps { - email: string; - firstName: string; - lastName: string; - organization: string; - password: string, - type: OtpType, + email: string; + firstName: string; + lastName: string; + organization: string; + password: string; + type: OtpType; } export class PasswordUserWithOTP extends User { - private type: OtpType - private code: string + private type: OtpType; + private code: string; - constructor(props: otpUserProps) { - super({ - email: props.email, - firstName: props.firstName, - lastName: props.lastName, - organization: props.organization, - password: props.password, - }) - this.type = props.type + constructor(props: otpUserProps) { + super({ + email: props.email, + firstName: props.firstName, + lastName: props.lastName, + organization: props.organization, + password: props.password, + }); + this.type = props.type; + } + + async ensure(page: Page) { + await super.ensure(page); + + let url = "otp_"; + switch (this.type) { + case OtpType.sms: + url = url + "sms"; + case OtpType.email: + url = url + "email"; } - async ensure(page: Page) { - await super.ensure(page) - - let url = "otp_" - switch (this.type) { - case OtpType.sms: - url = url + "sms" - case OtpType.email: - url = url + "email" - } - - const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + this.getUserId() + "/" + url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! - } - }); - if (response.statusCode >= 400 && response.statusCode != 409) { - const error = 'HTTP Error: ' + response.statusCode + ' - ' + response.statusMessage; - console.error(error); - throw new Error(error); - } - - // TODO: get code from SMS or Email provider - this.code = "" - return + const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + this.getUserId() + "/" + url, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN!, + }, + }); + if (response.statusCode >= 400 && response.statusCode != 409) { + const error = "HTTP Error: " + response.statusCode + " - " + response.statusMessage; + console.error(error); + throw new Error(error); } - public getCode() { - return this.code - } + // TODO: get code from SMS or Email provider + this.code = ""; + return; + } + + public getCode() { + return this.code; + } } export interface passkeyUserProps { - email: string; - firstName: string; - lastName: string; - organization: string; + email: string; + firstName: string; + lastName: string; + organization: string; } export class PasskeyUser extends User { - private authenticatorId: string + private authenticatorId: string; - constructor(props: passkeyUserProps) { - super({ - email: props.email, - firstName: props.firstName, - lastName: props.lastName, - organization: props.organization, - password: "" - }) - } + constructor(props: passkeyUserProps) { + super({ + email: props.email, + firstName: props.firstName, + lastName: props.lastName, + organization: props.organization, + password: "", + }); + } - public async ensure(page: Page) { - await this.remove() - const authId = await registerWithPasskey(page, this.getFirstname(), this.getLastname(), this.getUsername()) - this.authenticatorId = authId - } + public async ensure(page: Page) { + await this.remove(); + const authId = await registerWithPasskey(page, this.getFirstname(), this.getLastname(), this.getUsername()); + this.authenticatorId = authId; + } - public async remove() { - await super.remove() - } + public async remove() { + await super.remove(); + } - public getAuthenticatorId(): string { - return this.authenticatorId - } + public getAuthenticatorId(): string { + return this.authenticatorId; + } } diff --git a/acceptance/tests/username-passkey.spec.ts b/acceptance/tests/username-passkey.spec.ts index 43c3cec714..552208f324 100644 --- a/acceptance/tests/username-passkey.spec.ts +++ b/acceptance/tests/username-passkey.spec.ts @@ -1,26 +1,26 @@ -import {test as base} from "@playwright/test"; -import path from 'path'; -import dotenv from 'dotenv'; -import {PasskeyUser} from "./user"; -import {loginScreenExpect, loginWithPasskey} from "./login"; +import { test as base } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { loginScreenExpect, loginWithPasskey } from "./login"; +import { PasskeyUser } from "./user"; // Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); const test = base.extend<{ user: PasskeyUser }>({ - user: async ({page}, use) => { - const user = new PasskeyUser({ - email: "passkey@example.com", - firstName: "first", - lastName: "last", - organization: "", - }); - await user.ensure(page); - await use(user); - }, + user: async ({ page }, use) => { + const user = new PasskeyUser({ + email: "passkey@example.com", + firstName: "first", + lastName: "last", + organization: "", + }); + await user.ensure(page); + await use(user); + }, }); -test("username and passkey login", async ({user, page}) => { - await loginWithPasskey(page, user.getAuthenticatorId(), user.getUsername()) - await loginScreenExpect(page, user.getFullName()); +test("username and passkey login", async ({ user, page }) => { + await loginWithPasskey(page, user.getAuthenticatorId(), user.getUsername()); + await loginScreenExpect(page, user.getFullName()); }); diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index 9a7f09b046..73a2da63c7 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -1,41 +1,41 @@ -import {test as base} from "@playwright/test"; -import {PasswordUser} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {changePassword, startChangePassword} from "./password"; -import {changePasswordScreen, changePasswordScreenExpect} from "./password-screen"; +import { test as base } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { loginScreenExpect, loginWithPassword } from "./login"; +import { changePassword, startChangePassword } from "./password"; +import { changePasswordScreen, changePasswordScreenExpect } from "./password-screen"; +import { PasswordUser } from "./user"; // Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); const test = base.extend<{ user: PasswordUser }>({ - user: async ({page}, use) => { - const user = new PasswordUser({ - email: "password-changed@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - }); - await user.ensure(page); - await use(user); - }, + user: async ({ page }, use) => { + const user = new PasswordUser({ + email: "password-changed@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + }); + await user.ensure(page); + await use(user); + }, }); -test("username and password changed login", async ({user, page}) => { - const changedPw = "ChangedPw1!" - await loginWithPassword(page, user.getUsername(), user.getPassword()) - await changePassword(page, user.getUsername(), changedPw) - await loginWithPassword(page, user.getUsername(), changedPw) - await loginScreenExpect(page, user.getFullName()); +test("username and password changed login", async ({ user, page }) => { + const changedPw = "ChangedPw1!"; + await loginWithPassword(page, user.getUsername(), user.getPassword()); + await changePassword(page, user.getUsername(), changedPw); + await loginWithPassword(page, user.getUsername(), changedPw); + await loginScreenExpect(page, user.getFullName()); }); -test("password not with desired complexity", async ({user, page}) => { - const changedPw1 = "change" - const changedPw2 = "chang" - await loginWithPassword(page, user.getUsername(), user.getPassword()) - await startChangePassword(page, user.getUsername()); - await changePasswordScreen(page, changedPw1, changedPw2) - await changePasswordScreenExpect(page, changedPw1, changedPw2, false, false, false, false, true, false) +test("password not with desired complexity", async ({ user, page }) => { + const changedPw1 = "change"; + const changedPw2 = "chang"; + await loginWithPassword(page, user.getUsername(), user.getPassword()); + await startChangePassword(page, user.getUsername()); + await changePasswordScreen(page, changedPw1, changedPw2); + await changePasswordScreenExpect(page, changedPw1, changedPw2, false, false, false, false, true, false); }); diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index 0c733a6faf..19dca62a57 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -1,36 +1,32 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; +import { test as base } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { loginScreenExpect, loginWithPassword } from "./login"; +import { OtpType, PasswordUserWithOTP } from "./user"; // Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); + user: async ({ page }, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); - await user.ensure(page); - await use(user); - }, + await user.ensure(page); + await use(user); + }, }); -test("username, password and otp login", async ({user, page}) => { - //const server = startSink() - await loginWithPassword(page, user.getUsername(), user.getPassword()) +test("username, password and otp login", async ({ user, page }) => { + //const server = startSink() + await loginWithPassword(page, user.getUsername(), user.getPassword()); - - await loginScreenExpect(page, user.getFullName()); - //server.close() + await loginScreenExpect(page, user.getFullName()); + //server.close() }); - - diff --git a/acceptance/tests/zitadel.ts b/acceptance/tests/zitadel.ts index 8a1935ad57..11773c9686 100644 --- a/acceptance/tests/zitadel.ts +++ b/acceptance/tests/zitadel.ts @@ -1,50 +1,52 @@ import fetch from "node-fetch"; export async function removeUserByUsername(username: string) { - const resp = await getUserByUsername(username) - if (!resp || !resp.result || !resp.result[0]) { - return - } - await removeUser(resp.result[0].userId) + const resp = await getUserByUsername(username); + if (!resp || !resp.result || !resp.result[0]) { + return; + } + await removeUser(resp.result[0].userId); } export async function removeUser(id: string) { - const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + id, { - method: 'DELETE', - headers: { - 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! - } - }); - if (response.statusCode >= 400 && response.statusCode != 404) { - const error = 'HTTP Error: ' + response.statusCode + ' - ' + response.statusMessage; - console.error(error); - throw new Error(error); - } - return + const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + id, { + method: "DELETE", + headers: { + Authorization: "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN!, + }, + }); + if (response.statusCode >= 400 && response.statusCode != 404) { + const error = "HTTP Error: " + response.statusCode + " - " + response.statusMessage; + console.error(error); + throw new Error(error); + } + return; } export async function getUserByUsername(username: string) { - const listUsersBody = { - queries: [{ - userNameQuery: { - userName: username, - } - }] - } - const jsonBody = JSON.stringify(listUsersBody) - const registerResponse = await fetch(process.env.ZITADEL_API_URL! + "/v2/users", { - method: 'POST', - body: jsonBody, - headers: { - 'Content-Type': 'application/json', - 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! - } - }); - if (registerResponse.statusCode >= 400) { - const error = 'HTTP Error: ' + registerResponse.statusCode + ' - ' + registerResponse.statusMessage; - console.error(error); - throw new Error(error); - } - const respJson = await registerResponse.json() - return respJson -} \ No newline at end of file + const listUsersBody = { + queries: [ + { + userNameQuery: { + userName: username, + }, + }, + ], + }; + const jsonBody = JSON.stringify(listUsersBody); + const registerResponse = await fetch(process.env.ZITADEL_API_URL! + "/v2/users", { + method: "POST", + body: jsonBody, + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN!, + }, + }); + if (registerResponse.statusCode >= 400) { + const error = "HTTP Error: " + registerResponse.statusCode + " - " + registerResponse.statusMessage; + console.error(error); + throw new Error(error); + } + const respJson = await registerResponse.json(); + return respJson; +} diff --git a/playwright.config.ts b/playwright.config.ts index a87431a849..0ca27fe1ed 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,4 +1,4 @@ -import {defineConfig, devices} from "@playwright/test"; +import { defineConfig, devices } from "@playwright/test"; /** * Read environment variables from file. @@ -12,33 +12,33 @@ import {defineConfig, devices} from "@playwright/test"; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: "./acceptance/tests", - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: "html", - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: "http://localhost:3000", + testDir: "./acceptance/tests", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: "http://localhost:3000", - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: "on-first-retry", + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, }, - - /* Configure projects for major browsers */ - projects: [ - { - name: "chromium", - use: {...devices["Desktop Chrome"]}, - }, - /* + /* { name: "firefox", use: { ...devices["Desktop Firefox"] }, @@ -50,32 +50,32 @@ export default defineConfig({ }, */ - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, - ], + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], - /* Run local dev server before starting the tests */ - webServer: { - command: "pnpm start:built", - url: "http://127.0.0.1:3000", - reuseExistingServer: !process.env.CI, - timeout: 5 * 60_000, - }, + /* Run local dev server before starting the tests */ + webServer: { + command: "pnpm start:built", + url: "http://127.0.0.1:3000", + reuseExistingServer: !process.env.CI, + timeout: 5 * 60_000, + }, }); From 1bc174febbf70ba6191cfa0eb6c0082e7809cb17 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 15 Nov 2024 13:56:18 +0100 Subject: [PATCH 419/640] node fetch, rel path --- acceptance/tests/password.ts | 2 +- package.json | 1 + pnpm-lock.yaml | 48 ++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/acceptance/tests/password.ts b/acceptance/tests/password.ts index 3943844252..e8cd787b04 100644 --- a/acceptance/tests/password.ts +++ b/acceptance/tests/password.ts @@ -4,7 +4,7 @@ import { changePasswordScreen, passwordScreen } from "./password-screen"; const passwordSubmitButton = "submit-button"; export async function startChangePassword(page: Page, loginname: string) { - await page.goto("password/change?" + new URLSearchParams({ loginName: loginname })); + await page.goto("/password/change?" + new URLSearchParams({ loginName: loginname })); } export async function changePassword(page: Page, loginname: string, password: string) { diff --git a/package.json b/package.json index 642c0df2ce..2bd8b16103 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "dotenv": "^16.4.5", "eslint": "8.57.1", "eslint-config-zitadel": "workspace:*", + "node-fetch": "^3.3.2", "prettier": "^3.2.5", "prettier-plugin-organize-imports": "^4.1.0", "tsup": "^8.3.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 185010931e..86cff0c2a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: eslint-config-zitadel: specifier: workspace:* version: link:packages/eslint-config-zitadel + node-fetch: + specifier: ^3.3.2 + version: 3.3.2 prettier: specifier: ^3.2.5 version: 3.3.3 @@ -1942,6 +1945,10 @@ packages: resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} engines: {node: '>=0.10'} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -2392,6 +2399,10 @@ packages: picomatch: optional: true + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} @@ -2445,6 +2456,10 @@ packages: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -3250,6 +3265,10 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -3259,6 +3278,10 @@ packages: encoding: optional: true + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} @@ -4463,6 +4486,10 @@ packages: engines: {node: '>=12.0.0'} hasBin: true + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -6239,6 +6266,8 @@ snapshots: dependencies: assert-plus: 1.0.0 + data-uri-to-buffer@4.0.1: {} + data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -6908,6 +6937,11 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + fflate@0.8.2: {} figures@3.2.0: @@ -6961,6 +6995,10 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + fraction.js@4.3.7: {} from@0.1.7: {} @@ -7780,10 +7818,18 @@ snapshots: node-addon-api@7.1.1: optional: true + node-domexception@1.0.0: {} + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + node-releases@2.0.18: {} nodemon@3.1.7: @@ -8961,6 +9007,8 @@ snapshots: transitivePeerDependencies: - debug + web-streams-polyfill@3.3.3: {} + webidl-conversions@3.0.1: {} webidl-conversions@4.0.2: {} From fd45c077588957cf29b7062b93774a292c0df0b9 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 15 Nov 2024 14:27:46 +0100 Subject: [PATCH 420/640] fix: axios, user, zitadel --- acceptance/tests/user.ts | 76 ++++++++++++++++++------------- acceptance/tests/zitadel.ts | 62 +++++++++++++++----------- package.json | 2 +- pnpm-lock.yaml | 89 ++++++++++++------------------------- 4 files changed, 109 insertions(+), 120 deletions(-) diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index efa53ed3e4..ceda6d7478 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -1,5 +1,5 @@ import { Page } from "@playwright/test"; -import fetch from "node-fetch"; +import axios from "axios"; import { registerWithPasskey } from "./register"; import { getUserByUsername, removeUser } from "./zitadel"; @@ -40,29 +40,31 @@ class User { }, }; - const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/human", { - method: "POST", - body: JSON.stringify(body), - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN!, - }, - }); - if (response.statusCode >= 400 && response.statusCode != 409) { - const error = "HTTP Error: " + response.statusCode + " - " + response.statusMessage; - console.error(error); - throw new Error(error); + try { + const response = await axios.post(`${process.env.ZITADEL_API_URL}/v2/users/human`, body, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, + }, + }); + + if (response.status >= 400 && response.status !== 409) { + const error = `HTTP Error: ${response.status} - ${response.statusText}`; + console.error(error); + throw new Error(error); + } + } catch (error) { + console.error("Error making request:", error); + throw error; } - return; } async remove() { - const resp = await getUserByUsername(this.getUsername()); + const resp: any = await getUserByUsername(this.getUsername()); if (!resp || !resp.result || !resp.result[0]) { return; } await removeUser(resp.result[0].userId); - return; } public setUserId(userId: string) { @@ -90,7 +92,7 @@ class User { } public getFullName() { - return this.props.firstName + " " + this.props.lastName; + return `${this.props.firstName} ${this.props.lastName}`; } } @@ -132,26 +134,36 @@ export class PasswordUserWithOTP extends User { switch (this.type) { case OtpType.sms: url = url + "sms"; + break; case OtpType.email: url = url + "email"; + break; } - const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + this.getUserId() + "/" + url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN!, - }, - }); - if (response.statusCode >= 400 && response.statusCode != 409) { - const error = "HTTP Error: " + response.statusCode + " - " + response.statusMessage; - console.error(error); - throw new Error(error); - } + try { + const response = await axios.post( + `${process.env.ZITADEL_API_URL}/v2/users/${this.getUserId()}/${url}`, + {}, + { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, + }, + }, + ); - // TODO: get code from SMS or Email provider - this.code = ""; - return; + if (response.status >= 400 && response.status !== 409) { + const error = `HTTP Error: ${response.status} - ${response.statusText}`; + console.error(error); + throw new Error(error); + } + + // TODO: get code from SMS or Email provider + this.code = ""; + } catch (error) { + console.error("Error making request:", error); + throw error; + } } public getCode() { diff --git a/acceptance/tests/zitadel.ts b/acceptance/tests/zitadel.ts index 11773c9686..aa0c03ffdf 100644 --- a/acceptance/tests/zitadel.ts +++ b/acceptance/tests/zitadel.ts @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import axios from "axios"; export async function removeUserByUsername(username: string) { const resp = await getUserByUsername(username); @@ -9,18 +9,22 @@ export async function removeUserByUsername(username: string) { } export async function removeUser(id: string) { - const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + id, { - method: "DELETE", - headers: { - Authorization: "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN!, - }, - }); - if (response.statusCode >= 400 && response.statusCode != 404) { - const error = "HTTP Error: " + response.statusCode + " - " + response.statusMessage; - console.error(error); - throw new Error(error); + try { + const response = await axios.delete(`${process.env.ZITADEL_API_URL}/v2/users/${id}`, { + headers: { + Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, + }, + }); + + if (response.status >= 400 && response.status !== 404) { + const error = `HTTP Error: ${response.status} - ${response.statusText}`; + console.error(error); + throw new Error(error); + } + } catch (error) { + console.error("Error making request:", error); + throw error; } - return; } export async function getUserByUsername(username: string) { @@ -33,20 +37,24 @@ export async function getUserByUsername(username: string) { }, ], }; - const jsonBody = JSON.stringify(listUsersBody); - const registerResponse = await fetch(process.env.ZITADEL_API_URL! + "/v2/users", { - method: "POST", - body: jsonBody, - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN!, - }, - }); - if (registerResponse.statusCode >= 400) { - const error = "HTTP Error: " + registerResponse.statusCode + " - " + registerResponse.statusMessage; - console.error(error); - throw new Error(error); + + try { + const response = await axios.post(`${process.env.ZITADEL_API_URL}/v2/users`, listUsersBody, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, + }, + }); + + if (response.status >= 400) { + const error = `HTTP Error: ${response.status} - ${response.statusText}`; + console.error(error); + throw new Error(error); + } + + return response.data; + } catch (error) { + console.error("Error making request:", error); + throw error; } - const respJson = await registerResponse.json(); - return respJson; } diff --git a/package.json b/package.json index 2bd8b16103..3636acdfb6 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,10 @@ "@types/node": "^22.9.0", "@vitejs/plugin-react": "^4.3.3", "@zitadel/prettier-config": "workspace:*", + "axios": "^1.7.7", "dotenv": "^16.4.5", "eslint": "8.57.1", "eslint-config-zitadel": "workspace:*", - "node-fetch": "^3.3.2", "prettier": "^3.2.5", "prettier-plugin-organize-imports": "^4.1.0", "tsup": "^8.3.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86cff0c2a8..0381141c34 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@zitadel/prettier-config': specifier: workspace:* version: link:packages/zitadel-prettier-config + axios: + specifier: ^1.7.7 + version: 1.7.7 dotenv: specifier: ^16.4.5 version: 16.4.5 @@ -35,9 +38,6 @@ importers: eslint-config-zitadel: specifier: workspace:* version: link:packages/eslint-config-zitadel - node-fetch: - specifier: ^3.3.2 - version: 3.3.2 prettier: specifier: ^3.2.5 version: 3.3.3 @@ -1945,10 +1945,6 @@ packages: resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} engines: {node: '>=0.10'} - data-uri-to-buffer@4.0.1: - resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} - engines: {node: '>= 12'} - data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -2399,10 +2395,6 @@ packages: picomatch: optional: true - fetch-blob@3.2.0: - resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} - engines: {node: ^12.20 || >= 14.13} - fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} @@ -2456,10 +2448,6 @@ packages: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} - formdata-polyfill@4.0.10: - resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} - engines: {node: '>=12.20.0'} - fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -3265,10 +3253,6 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-domexception@1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} - engines: {node: '>=10.5.0'} - node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -3278,10 +3262,6 @@ packages: encoding: optional: true - node-fetch@3.3.2: - resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} @@ -4486,10 +4466,6 @@ packages: engines: {node: '>=12.0.0'} hasBin: true - web-streams-polyfill@3.3.3: - resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} - engines: {node: '>= 8'} - webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -4657,7 +4633,7 @@ snapshots: '@babel/traverse': 7.25.9 '@babel/types': 7.26.0 convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -4744,7 +4720,7 @@ snapshots: '@babel/parser': 7.26.2 '@babel/template': 7.25.9 '@babel/types': 7.26.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5134,7 +5110,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -5233,7 +5209,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -5780,7 +5756,7 @@ snapshots: agent-base@7.1.1: dependencies: - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -5955,6 +5931,14 @@ snapshots: axe-core@4.10.0: {} + axios@1.7.7: + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axios@1.7.7(debug@4.3.7): dependencies: follow-redirects: 1.15.6(debug@4.3.7) @@ -6266,8 +6250,6 @@ snapshots: dependencies: assert-plus: 1.0.0 - data-uri-to-buffer@4.0.1: {} - data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -6303,6 +6285,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.7: + dependencies: + ms: 2.1.3 + debug@4.3.7(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -6779,7 +6765,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -6937,11 +6923,6 @@ snapshots: optionalDependencies: picomatch: 4.0.2 - fetch-blob@3.2.0: - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 3.3.3 - fflate@0.8.2: {} figures@3.2.0: @@ -6974,6 +6955,8 @@ snapshots: flatted@3.3.1: {} + follow-redirects@1.15.6: {} + follow-redirects@1.15.6(debug@4.3.7): optionalDependencies: debug: 4.3.7(supports-color@5.5.0) @@ -6995,10 +6978,6 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 - formdata-polyfill@4.0.10: - dependencies: - fetch-blob: 3.2.0 - fraction.js@4.3.7: {} from@0.1.7: {} @@ -7212,7 +7191,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -7232,7 +7211,7 @@ snapshots: https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.1 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -7818,18 +7797,10 @@ snapshots: node-addon-api@7.1.1: optional: true - node-domexception@1.0.0: {} - node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 - node-fetch@3.3.2: - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - node-releases@2.0.18: {} nodemon@3.1.7: @@ -8764,7 +8735,7 @@ snapshots: cac: 6.7.14 chokidar: 4.0.1 consola: 3.2.3 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7 esbuild: 0.24.0 joycon: 3.1.1 picocolors: 1.1.1 @@ -8922,7 +8893,7 @@ snapshots: vite-node@2.1.4(@types/node@22.9.0)(sass@1.80.7): dependencies: cac: 6.7.14 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7 pathe: 1.1.2 vite: 5.4.11(@types/node@22.9.0)(sass@1.80.7) transitivePeerDependencies: @@ -8938,7 +8909,7 @@ snapshots: vite-tsconfig-paths@5.1.2(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(sass@1.80.7)): dependencies: - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7 globrex: 0.1.2 tsconfck: 3.1.4(typescript@5.6.3) optionalDependencies: @@ -8967,7 +8938,7 @@ snapshots: '@vitest/spy': 2.1.4 '@vitest/utils': 2.1.4 chai: 5.1.2 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7 expect-type: 1.1.0 magic-string: 0.30.12 pathe: 1.1.2 @@ -9007,8 +8978,6 @@ snapshots: transitivePeerDependencies: - debug - web-streams-polyfill@3.3.3: {} - webidl-conversions@3.0.1: {} webidl-conversions@4.0.2: {} From 26e423a7c17f9a9c1020d08753b9a9f9c554e2d4 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:54:14 +0100 Subject: [PATCH 421/640] chore: fixes to tests --- acceptance/setup.sh | 7 ++++++- acceptance/tests/user.ts | 9 +++++++++ .../tests/username-password-changed.spec.ts | 16 +++++++++++----- .../tests/username-password-otp_sms.spec.ts | 2 ++ 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/acceptance/setup.sh b/acceptance/setup.sh index 01b6bd826e..32f5c0f9d8 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -26,15 +26,20 @@ fi WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.local} echo "Writing environment file to ${WRITE_ENVIRONMENT_FILE} when done." +WRITE_TEST_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../acceptance/tests/.env.local} +echo "Writing environment file to ${WRITE_TEST_ENVIRONMENT_FILE} when done." echo "ZITADEL_API_URL=${ZITADEL_API_URL} ZITADEL_SERVICE_USER_ID=${ZITADEL_SERVICE_USER_ID} ZITADEL_SERVICE_USER_TOKEN=${PAT} DEBUG=true" > ${WRITE_ENVIRONMENT_FILE} - echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}" cat ${WRITE_ENVIRONMENT_FILE} +cp ${WRITE_ENVIRONMENT_FILE} ${WRITE_TEST_ENVIRONMENT_FILE} +echo "Wrote environment file ${WRITE_TEST_ENVIRONMENT_FILE}" +cat ${WRITE_TEST_ENVIRONMENT_FILE} + DEFAULTORG_RESPONSE_RESULTS=0 # waiting for default organization until [ ${DEFAULTORG_RESPONSE_RESULTS} -eq 1 ] diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index ceda6d7478..34cd720edb 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -57,6 +57,9 @@ class User { console.error("Error making request:", error); throw error; } + + // wait for projection of user + await page.waitForTimeout(3000) } async remove() { @@ -164,6 +167,9 @@ export class PasswordUserWithOTP extends User { console.error("Error making request:", error); throw error; } + + // wait for projection of user + await page.waitForTimeout(2000) } public getCode() { @@ -195,6 +201,9 @@ export class PasskeyUser extends User { await this.remove(); const authId = await registerWithPasskey(page, this.getFirstname(), this.getLastname(), this.getUsername()); this.authenticatorId = authId; + + // wait for projection of user + await page.waitForTimeout(2000) } public async remove() { diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index 73a2da63c7..caaa419e94 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -24,11 +24,17 @@ const test = base.extend<{ user: PasswordUser }>({ }); test("username and password changed login", async ({ user, page }) => { - const changedPw = "ChangedPw1!"; - await loginWithPassword(page, user.getUsername(), user.getPassword()); - await changePassword(page, user.getUsername(), changedPw); - await loginWithPassword(page, user.getUsername(), changedPw); - await loginScreenExpect(page, user.getFullName()); + const changedPw = "ChangedPw1!" + await loginWithPassword(page, user.getUsername(), user.getPassword()) + + // wait for projection of token + await page.waitForTimeout(2000) + + await changePassword(page, user.getUsername(), changedPw) + await loginScreenExpect(page, user.getFullName()); + + await loginWithPassword(page, user.getUsername(), changedPw) + await loginScreenExpect(page, user.getFullName()); }); test("password not with desired complexity", async ({ user, page }) => { diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index 19dca62a57..920de7230a 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -23,6 +23,7 @@ const test = base.extend<{ user: PasswordUserWithOTP }>({ }, }); +/* test("username, password and otp login", async ({ user, page }) => { //const server = startSink() await loginWithPassword(page, user.getUsername(), user.getPassword()); @@ -30,3 +31,4 @@ test("username, password and otp login", async ({ user, page }) => { await loginScreenExpect(page, user.getFullName()); //server.close() }); +*/ \ No newline at end of file From 88620ad697958880bdd979e147504694c39a5eff Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 15 Nov 2024 15:04:33 +0100 Subject: [PATCH 422/640] double print env --- acceptance/setup.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/acceptance/setup.sh b/acceptance/setup.sh index 32f5c0f9d8..0d4d16f167 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -32,11 +32,10 @@ echo "Writing environment file to ${WRITE_TEST_ENVIRONMENT_FILE} when done." echo "ZITADEL_API_URL=${ZITADEL_API_URL} ZITADEL_SERVICE_USER_ID=${ZITADEL_SERVICE_USER_ID} ZITADEL_SERVICE_USER_TOKEN=${PAT} -DEBUG=true" > ${WRITE_ENVIRONMENT_FILE} +DEBUG=true"| tee "${WRITE_ENVIRONMENT_FILE}" "${WRITE_TEST_ENVIRONMENT_FILE}" > /dev/null echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}" cat ${WRITE_ENVIRONMENT_FILE} -cp ${WRITE_ENVIRONMENT_FILE} ${WRITE_TEST_ENVIRONMENT_FILE} echo "Wrote environment file ${WRITE_TEST_ENVIRONMENT_FILE}" cat ${WRITE_TEST_ENVIRONMENT_FILE} From 39335c3ed4d41ea3cbe4985b7428226bfb9087a9 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 15 Nov 2024 15:05:38 +0100 Subject: [PATCH 423/640] format --- acceptance/tests/user.ts | 6 +++--- .../tests/username-password-changed.spec.ts | 16 ++++++++-------- .../tests/username-password-otp_sms.spec.ts | 3 +-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index 34cd720edb..522e76dc67 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -59,7 +59,7 @@ class User { } // wait for projection of user - await page.waitForTimeout(3000) + await page.waitForTimeout(3000); } async remove() { @@ -169,7 +169,7 @@ export class PasswordUserWithOTP extends User { } // wait for projection of user - await page.waitForTimeout(2000) + await page.waitForTimeout(2000); } public getCode() { @@ -203,7 +203,7 @@ export class PasskeyUser extends User { this.authenticatorId = authId; // wait for projection of user - await page.waitForTimeout(2000) + await page.waitForTimeout(2000); } public async remove() { diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index caaa419e94..e1949ff9fe 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -24,17 +24,17 @@ const test = base.extend<{ user: PasswordUser }>({ }); test("username and password changed login", async ({ user, page }) => { - const changedPw = "ChangedPw1!" - await loginWithPassword(page, user.getUsername(), user.getPassword()) + const changedPw = "ChangedPw1!"; + await loginWithPassword(page, user.getUsername(), user.getPassword()); - // wait for projection of token - await page.waitForTimeout(2000) + // wait for projection of token + await page.waitForTimeout(2000); - await changePassword(page, user.getUsername(), changedPw) - await loginScreenExpect(page, user.getFullName()); + await changePassword(page, user.getUsername(), changedPw); + await loginScreenExpect(page, user.getFullName()); - await loginWithPassword(page, user.getUsername(), changedPw) - await loginScreenExpect(page, user.getFullName()); + await loginWithPassword(page, user.getUsername(), changedPw); + await loginScreenExpect(page, user.getFullName()); }); test("password not with desired complexity", async ({ user, page }) => { diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index 920de7230a..6d91463a2d 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -1,7 +1,6 @@ import { test as base } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; -import { loginScreenExpect, loginWithPassword } from "./login"; import { OtpType, PasswordUserWithOTP } from "./user"; // Read from ".env" file. @@ -31,4 +30,4 @@ test("username, password and otp login", async ({ user, page }) => { await loginScreenExpect(page, user.getFullName()); //server.close() }); -*/ \ No newline at end of file +*/ From 3654d5e0ce738ce3093b2628ef30dd916323f19e Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:58:48 +0100 Subject: [PATCH 424/640] chore: fixes to tests --- acceptance/docker-compose.yaml | 1 + acceptance/setup.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index e8448ddb00..9a02597d84 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -44,6 +44,7 @@ services: PAT_FILE: /pat/zitadel-admin-sa.pat ZITADEL_API_INTERNAL_URL: http://zitadel:8080 WRITE_ENVIRONMENT_FILE: /apps/login/.env.local + WRITE_TEST_ENVIRONMENT_FILE: /acceptance/tests/.env.local volumes: - "./pat:/pat" - "../apps/login:/apps/login" diff --git a/acceptance/setup.sh b/acceptance/setup.sh index 0d4d16f167..d490ce3f61 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -26,7 +26,7 @@ fi WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.local} echo "Writing environment file to ${WRITE_ENVIRONMENT_FILE} when done." -WRITE_TEST_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../acceptance/tests/.env.local} +WRITE_TEST_ENVIRONMENT_FILE=${WRITE_TEST_ENVIRONMENT_FILE:-$(dirname "$0")/../acceptance/tests/.env.local} echo "Writing environment file to ${WRITE_TEST_ENVIRONMENT_FILE} when done." echo "ZITADEL_API_URL=${ZITADEL_API_URL} From e3d7ff0b972a7037ab5e7ba57f48a726ebbe06e4 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:00:35 +0100 Subject: [PATCH 425/640] chore: fixes to tests --- .github/workflows/test.yml | 4 ---- acceptance/docker-compose.yaml | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6bd2abcf7c..ce61199e7f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,10 +4,6 @@ on: pull_request jobs: quality: - env: - ZITADEL_IMAGE: ghcr.io/zitadel/zitadel:v2.65.0 - POSTGRES_IMAGE: postgres:17.0-alpine3.19 - name: Ensure Quality runs-on: ubuntu-latest diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index 9a02597d84..c1ca3bef38 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -1,7 +1,7 @@ services: zitadel: user: "${ZITADEL_DEV_UID}" - image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:latest}" + image: ghcr.io/zitadel/zitadel:v2.65.0 command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled --config /zitadel.yaml --steps /zitadel.yaml' ports: - "8080:8080" @@ -14,7 +14,7 @@ services: db: restart: "always" - image: "${POSTGRES_IMAGE:-postgres:latest}" + image: postgres:17.0-alpine3.19 environment: - POSTGRES_USER=zitadel - PGUSER=zitadel From 42e65a9f664018c87a04f890c5a0157b67d4e01c Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:05:28 +0100 Subject: [PATCH 426/640] chore: fixes to tests --- acceptance/docker-compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index c1ca3bef38..4ba64880f3 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -48,6 +48,7 @@ services: volumes: - "./pat:/pat" - "../apps/login:/apps/login" + - "../acceptance/tests:/acceptance/tests" depends_on: wait_for_zitadel: condition: "service_completed_successfully" From 1379ff49a71844bd8b90668aafe550025e9f7aa1 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:19:04 +0100 Subject: [PATCH 427/640] chore: fixes to tests --- acceptance/tests/admin.spec.ts | 2 +- acceptance/zitadel.yaml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/acceptance/tests/admin.spec.ts b/acceptance/tests/admin.spec.ts index 52db893528..7ca28e4419 100644 --- a/acceptance/tests/admin.spec.ts +++ b/acceptance/tests/admin.spec.ts @@ -2,6 +2,6 @@ import { test } from "@playwright/test"; import { loginScreenExpect, loginWithPassword } from "./login"; test("admin login", async ({ page }) => { - await loginWithPassword(page, "zitadel-admin@zitadel.localhost", "Password1."); + await loginWithPassword(page, "zitadel-admin@zitadel.localhost", "Password1!"); await loginScreenExpect(page, "ZITADEL Admin"); }); diff --git a/acceptance/zitadel.yaml b/acceptance/zitadel.yaml index 407f424fb4..00fcd28c92 100644 --- a/acceptance/zitadel.yaml +++ b/acceptance/zitadel.yaml @@ -1,5 +1,11 @@ FirstInstance: PatPath: /pat/zitadel-admin-sa.pat + PrivacyPolicy: + TOSLink: "https://zitadel.com/docs/legal/terms-of-service" + PrivacyLink: "https://zitadel.com/docs/legal/policies/privacy-policy" + HelpLink: "https://zitadel.com/docs" + SupportEmail: "support@zitadel.com" + DocsLink: "https://zitadel.com/docs" Org: Human: UserName: zitadel-admin From 10c9d64977b8382771689f8db25f25eb782f743a Mon Sep 17 00:00:00 2001 From: Fabienne Date: Mon, 18 Nov 2024 09:30:23 +0100 Subject: [PATCH 428/640] add test flows for login ui --- acceptance/tests/username-passkey.spec.ts | 22 +++++ .../tests/username-password-otp_email.spec.ts | 87 +++++++++++++++++++ .../tests/username-password-otp_sms.spec.ts | 35 ++++++++ acceptance/tests/username-password.spec.ts | 50 +++++++++++ 4 files changed, 194 insertions(+) create mode 100644 acceptance/tests/username-password-otp_email.spec.ts diff --git a/acceptance/tests/username-passkey.spec.ts b/acceptance/tests/username-passkey.spec.ts index 43c3cec714..b3f6b131dd 100644 --- a/acceptance/tests/username-passkey.spec.ts +++ b/acceptance/tests/username-passkey.spec.ts @@ -24,3 +24,25 @@ test("username and passkey login", async ({user, page}) => { await loginWithPasskey(page, user.getAuthenticatorId(), user.getUsername()) await loginScreenExpect(page, user.getFullName()); }); + +test("username and passkey login, if passkey enabled", async ({user, page}) => { + // Given passkey is enabled on the organization of the user + // Given the user has only passkey enabled as authentication + + // enter username + // passkey popup is directly shown + // user verifies passkey + // user is redirected to app +}); + +test("username and passkey login, multiple auth methods", async ({user, page}) => { + // Given passkey and password is enabled on the organization of the user + // Given the user has password and passkey registered + + // enter username + // passkey popup is directly shown + // user aborts passkey authentication + // user switches to password authentication + // user enters password + // user is redirected to app +}); diff --git a/acceptance/tests/username-password-otp_email.spec.ts b/acceptance/tests/username-password-otp_email.spec.ts new file mode 100644 index 0000000000..d15793f040 --- /dev/null +++ b/acceptance/tests/username-password-otp_email.spec.ts @@ -0,0 +1,87 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + + +test("username, password and email otp login, enter code manually", async ({user, page}) => { + // Given email otp is enabled on the organizaiton of the user + // Given the user has only email otp configured as second factor + + // User enters username + // User enters password + // User receives an email with a verification code + // User enters the code into the ui + // User is redirected to the app +}); + + +test("username, password and email otp login, click link in email", async ({user, page}) => { + // Given email otp is enabled on the organizaiton of the user + // Given the user has only email otp configured as second factor + + // User enters username + // User enters password + // User receives an email with a verification code + // User clicks link in the email + // User is redirected to the app +}); + +test("username, password and email otp login, resend code", async ({user, page}) => { + // Given email otp is enabled on the organizaiton of the user + // Given the user has only email otp configured as second factor + + // User enters username + // User enters password + // User receives an email with a verification code + // User clicks resend code + // User receives a new email with a verification code + // User enters the new code in the ui + // User is redirected to the app +}); + + +test("username, password and email otp login, wrong code", async ({user, page}) => { + // Given email otp is enabled on the organizaiton of the user + // Given the user has only email otp configured as second factor + + // User enters username + // User enters password + // User receives an email with a verification code + // User enters a wrond code + // Error message - "Invalid code" is shown +}); + +test("username, password and email otp login, multiple mfa options", async ({user, page}) => { + // Given email otp and sms otp is enabled on the organizaiton of the user + // Given the user has email and sms otp configured as second factor + + // User enters username + // User enters password + // User receives an email with a verification code + // User clicks button to use sms otp as second factor + // User receives an sms with a verification code + // User enters code in ui + // User is redirected to the app +}); diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index 9caf8bd5ce..d80e03fac8 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -34,3 +34,38 @@ test("username, password and otp login", async ({user, page}) => { }); +test("username, password and sms otp login", async ({user, page}) => { + // Given sms otp is enabled on the organizaiton of the user + // Given the user has only sms otp configured as second factor + + // User enters username + // User enters password + // User receives an sms with a verification code + // User enters the code into the ui + // User is redirected to the app +}); + + +test("username, password and sms otp login, resend code", async ({user, page}) => { + // Given sms otp is enabled on the organizaiton of the user + // Given the user has only sms otp configured as second factor + + // User enters username + // User enters password + // User receives an sms with a verification code + // User clicks resend code + // User receives a new sms with a verification code + // User is redirected to the app +}); + + +test("username, password and sms otp login, wrong code", async ({user, page}) => { + // Given sms otp is enabled on the organizaiton of the user + // Given the user has only sms otp configured as second factor + + // User enters username + // User enters password + // User receives an sms with a verification code + // User enters a wrond code + // Error message - "Invalid code" is shown +}); diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index e9ab31d998..ee97f21bc6 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -43,3 +43,53 @@ test("username and password login, wrong password", async ({user, page}) => { await password(page, "wrong") await passwordScreenExpect(page, "wrong") }); + +test("username and password login, wrong username, ignore unknown usernames", async ({user, page}) => { + // Given user doesn't exist but ignore unknown usernames setting is set to true + // Given username password login is enabled on the users organization + + // enter login name + // enter password + // redirect to loginname page --> error message username or password wrong +}); + +test("username and password login, initial password change", async ({user, page}) => { + // Given user is created and has changePassword set to true + // Given username password login is enabled on the users organization + + // enter login name + // enter password + // create new password +}); + +test("username and password login, reset password - enter code manually", async ({user, page}) => { + // Given user has forgotten password and clicks the forgot password button + // Given username password login is enabled on the users organization + + // enter login name + // click password forgotten + // enter code from email + // user is redirected to app +}); + +test("username and password login, reset password - click link", async ({user, page}) => { + // Given user has forgotten password and clicks the forgot password button, and then the link in the email + // Given username password login is enabled on the users organization + + // enter login name + // click password forgotten + // click link in email + // set new password + // redirect to app +}); + +test("username and password login, reset password, resend code", async ({user, page}) => { + // Given user has forgotten password and clicks the forgot password button and then resend code + // Given username password login is enabled on the users organization + + // enter login name + // click password forgotten + // click resend code + // enter code from second email + // user is authenticated +}); \ No newline at end of file From 98391c1dd0389f19ba729a8019983ec0e9b385e1 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 18 Nov 2024 10:27:39 +0100 Subject: [PATCH 429/640] cleanup --- apps/login/src/components/login-otp.tsx | 47 +++++----------- apps/login/src/components/login-passkey.tsx | 33 +++++------ apps/login/src/components/register-u2f.tsx | 62 ++++++++++----------- apps/login/src/components/totp-register.tsx | 38 +++++-------- apps/login/src/lib/login.ts | 2 +- 5 files changed, 71 insertions(+), 111 deletions(-) diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 7dfed9bfd1..13e71ecd5e 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -1,5 +1,6 @@ "use client"; +import { finishFlow } from "@/lib/login"; import { updateSession } from "@/lib/server/session"; import { create } from "@zitadel/client"; import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; @@ -154,40 +155,18 @@ export function LoginOTP({ function setCodeAndContinue(values: Inputs, organization?: string) { return submitCode(values, organization).then((response) => { if (response) { - if (authRequestId && response && response.sessionId) { - const params = new URLSearchParams({ - sessionId: response.sessionId, - authRequest: authRequestId, - }); - - if (organization) { - params.append("organization", organization); - } - - if (authRequestId) { - params.append("authRequest", authRequestId); - } - - if (sessionId) { - params.append("sessionId", sessionId); - } - - return router.push(`/login?` + params); - } else { - const params = new URLSearchParams(); - if (response?.factors?.user?.loginName) { - params.append("loginName", response.factors.user.loginName); - } - if (authRequestId) { - params.append("authRequestId", authRequestId); - } - - if (organization) { - params.append("organization", organization); - } - - return router.push(`/signedin?` + params); - } + return authRequestId && response.sessionId + ? finishFlow({ + sessionId: response.sessionId, + authRequestId: authRequestId, + organization: response.factors?.user?.organizationId, + }) + : response.factors?.user + ? finishFlow({ + loginName: response.factors.user.loginName, + organization: response.factors?.user?.organizationId, + }) + : null; } }); } diff --git a/apps/login/src/components/login-passkey.tsx b/apps/login/src/components/login-passkey.tsx index db4246eda0..ab801028fe 100644 --- a/apps/login/src/components/login-passkey.tsx +++ b/apps/login/src/components/login-passkey.tsx @@ -1,6 +1,7 @@ "use client"; import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64"; +import { finishFlow } from "@/lib/login"; import { updateSession } from "@/lib/server/session"; import { create } from "@zitadel/client"; import { @@ -176,26 +177,18 @@ export function LoginPasskey({ }; return submitLogin(data).then((resp) => { - if (authRequestId && resp && resp.sessionId) { - return router.push( - `/login?` + - new URLSearchParams({ - sessionId: resp.sessionId, - authRequest: authRequestId, - }), - ); - } else { - const params = new URLSearchParams({}); - - if (authRequestId) { - params.set("authRequestId", authRequestId); - } - if (resp?.factors?.user?.loginName) { - params.set("loginName", resp.factors.user.loginName); - } - - return router.push(`/signedin?` + params); - } + return authRequestId && resp?.sessionId + ? finishFlow({ + sessionId: resp.sessionId, + authRequestId: authRequestId, + organization: organization, + }) + : resp?.factors?.user?.loginName + ? finishFlow({ + loginName: resp.factors.user.loginName, + organization: organization, + }) + : null; }); }); } diff --git a/apps/login/src/components/register-u2f.tsx b/apps/login/src/components/register-u2f.tsx index ea0cdc1f58..edb3c54944 100644 --- a/apps/login/src/components/register-u2f.tsx +++ b/apps/login/src/components/register-u2f.tsx @@ -1,6 +1,7 @@ "use client"; import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64"; +import { finishFlow } from "@/lib/login"; import { addU2F, verifyU2F } from "@/lib/server/u2f"; import { RegisterU2FResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { useTranslations } from "next-intl"; @@ -62,7 +63,7 @@ export function RegisterU2f({ return response; } - async function submitRegisterAndContinue(): Promise { + async function submitRegisterAndContinue(): Promise { setError(""); setLoading(true); const response = await addU2F({ @@ -146,38 +147,37 @@ export function RegisterU2f({ return; } - const paramsToContinue = new URLSearchParams({}); - let urlToContinue = "/accounts"; - - if (sessionId) { - paramsToContinue.append("sessionId", sessionId); - } - - if (loginName) { - paramsToContinue.append("loginName", loginName); - } - if (organization) { - paramsToContinue.append("organization", organization); - } - if (checkAfter) { - if (authRequestId) { - paramsToContinue.append("authRequestId", authRequestId); - } - urlToContinue = `/u2f?` + paramsToContinue; - } else if (authRequestId && sessionId) { - if (authRequestId) { - paramsToContinue.append("authRequest", authRequestId); - } - urlToContinue = `/login?` + paramsToContinue; - } else if (loginName) { - if (authRequestId) { - paramsToContinue.append("authRequestId", authRequestId); - } - urlToContinue = `/signedin?` + paramsToContinue; - } + const paramsToContinue = new URLSearchParams({}); - router.push(urlToContinue); + if (sessionId) { + paramsToContinue.append("sessionId", sessionId); + } + if (loginName) { + paramsToContinue.append("loginName", loginName); + } + if (organization) { + paramsToContinue.append("organization", organization); + } + if (authRequestId) { + paramsToContinue.append("authRequestId", authRequestId); + } + + return router.push(`/u2f?` + paramsToContinue); + } else { + return authRequestId && sessionId + ? finishFlow({ + sessionId: sessionId, + authRequestId: authRequestId, + organization: organization, + }) + : loginName + ? finishFlow({ + loginName: loginName, + organization: organization, + }) + : null; + } } } diff --git a/apps/login/src/components/totp-register.tsx b/apps/login/src/components/totp-register.tsx index c3f9b03e2a..37a744fe97 100644 --- a/apps/login/src/components/totp-register.tsx +++ b/apps/login/src/components/totp-register.tsx @@ -1,4 +1,5 @@ "use client"; +import { finishFlow } from "@/lib/login"; import { verifyTOTP } from "@/lib/server-actions"; import { useTranslations } from "next-intl"; import Link from "next/link"; @@ -67,31 +68,18 @@ export function TotpRegister({ return router.push(`/otp/time-based?` + params); } else { - if (authRequestId && sessionId) { - const params = new URLSearchParams({ - sessionId: sessionId, - authRequest: authRequestId, - }); - - if (organization) { - params.append("organization", organization); - } - - return router.push(`/login?` + params); - } else if (loginName) { - const params = new URLSearchParams({ - loginName, - }); - - if (authRequestId) { - params.append("authRequestId", authRequestId); - } - if (organization) { - params.append("organization", organization); - } - - return router.push(`/signedin?` + params); - } + return authRequestId && sessionId + ? finishFlow({ + sessionId: sessionId, + authRequestId: authRequestId, + organization: organization, + }) + : loginName + ? finishFlow({ + loginName: loginName, + organization: organization, + }) + : null; } }) .catch((e) => { diff --git a/apps/login/src/lib/login.ts b/apps/login/src/lib/login.ts index 39f3396c9f..765255a945 100644 --- a/apps/login/src/lib/login.ts +++ b/apps/login/src/lib/login.ts @@ -8,7 +8,7 @@ type FinishFlowCommand = | { loginName: string }; /** - * redirects user back to OIDC application or to a success page + * on client: redirects user back to OIDC application or to a success page * @param command * @returns */ From 81c6e44927df1a0d37060f6696b9f222fc4ce40b Mon Sep 17 00:00:00 2001 From: Fabienne Date: Mon, 18 Nov 2024 10:36:13 +0100 Subject: [PATCH 430/640] add more user test flows --- .../tests/username-password-otp_email.spec.ts | 3 +- .../tests/username-password-totp.spec.ts | 63 +++++++++++++++++++ .../tests/username-password-u2f_email.spec.ts | 52 +++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 acceptance/tests/username-password-totp.spec.ts create mode 100644 acceptance/tests/username-password-u2f_email.spec.ts diff --git a/acceptance/tests/username-password-otp_email.spec.ts b/acceptance/tests/username-password-otp_email.spec.ts index d15793f040..55a05876a5 100644 --- a/acceptance/tests/username-password-otp_email.spec.ts +++ b/acceptance/tests/username-password-otp_email.spec.ts @@ -16,7 +16,7 @@ const test = base.extend<{ user: PasswordUserWithOTP }>({ lastName: "last", password: "Password1!", organization: "", - type: OtpType.sms, + type: OtpType.email, }); await user.ensure(page); @@ -61,7 +61,6 @@ test("username, password and email otp login, resend code", async ({user, page}) // User is redirected to the app }); - test("username, password and email otp login, wrong code", async ({user, page}) => { // Given email otp is enabled on the organizaiton of the user // Given the user has only email otp configured as second factor diff --git a/acceptance/tests/username-password-totp.spec.ts b/acceptance/tests/username-password-totp.spec.ts new file mode 100644 index 0000000000..d81ba9f09e --- /dev/null +++ b/acceptance/tests/username-password-totp.spec.ts @@ -0,0 +1,63 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + +test("username, password and totp login", async ({user, page}) => { + // Given totp is enabled on the organizaiton of the user + // Given the user has only totp configured as second factor + + // User enters username + // User enters password + // Screen for entering the code is shown directly + // User enters the code into the ui + // User is redirected to the app +}); + + +test("username, password and totp otp login, wrong code", async ({user, page}) => { + // Given totp is enabled on the organizaiton of the user + // Given the user has only totp configured as second factor + + // User enters username + // User enters password + // Screen for entering the code is shown directly + // User enters a wrond code + // Error message - "Invalid code" is shown +}); + + +test("username, password and totp login, multiple mfa options", async ({user, page}) => { + // Given totp and email otp is enabled on the organizaiton of the user + // Given the user has totp and email otp configured as second factor + + // User enters username + // User enters password + // Screen for entering the code is shown directly + // Button to switch to email otp is shown + // User clicks button to use email otp instead + // User receives an email with a verification code + // User enters code in ui + // User is redirected to the app +}); diff --git a/acceptance/tests/username-password-u2f_email.spec.ts b/acceptance/tests/username-password-u2f_email.spec.ts new file mode 100644 index 0000000000..fad16a2fc5 --- /dev/null +++ b/acceptance/tests/username-password-u2f_email.spec.ts @@ -0,0 +1,52 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + + +test("username, password and u2f login", async ({user, page}) => { + // Given u2f is enabled on the organizaiton of the user + // Given the user has only u2f configured as second factor + + // User enters username + // User enters password + // Popup for u2f is directly opened + // User verifies u2f + // User is redirected to the app +}); + + +test("username, password and u2f login, multiple mfa options", async ({user, page}) => { + // Given u2f and semailms otp is enabled on the organizaiton of the user + // Given the user has u2f and email otp configured as second factor + + // User enters username + // User enters password + // Popup for u2f is directly opened + // User aborts u2f verification + // User clicks button to use email otp as second factor + // User receives an email with a verification code + // User enters code in ui + // User is redirected to the app +}); From 5db61c8623148599cc3f7b31b4f4e1f0baaf1b1d Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 18 Nov 2024 10:41:13 +0100 Subject: [PATCH 431/640] idp cleanup --- .../login/src/components/sign-in-with-idp.tsx | 46 ++++++++++--------- apps/login/src/lib/server/idp.ts | 9 ++++ 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/apps/login/src/components/sign-in-with-idp.tsx b/apps/login/src/components/sign-in-with-idp.tsx index 90520d449b..e19564b81a 100644 --- a/apps/login/src/components/sign-in-with-idp.tsx +++ b/apps/login/src/components/sign-in-with-idp.tsx @@ -64,17 +64,6 @@ export function SignInWithIdp({ return response; } - async function navigateToAuthUrl(id: string, type: IdentityProviderType) { - const startFlowResponse = await startFlow(id, idpTypeToSlug(type)); - if ( - startFlowResponse && - startFlowResponse.nextStep.case === "authUrl" && - startFlowResponse?.nextStep.value - ) { - router.push(startFlowResponse.nextStep.value); - } - } - return (
    {identityProviders && @@ -86,7 +75,7 @@ export function SignInWithIdp({ key={`idp-${i}`} name={idp.name} onClick={() => - navigateToAuthUrl(idp.id, IdentityProviderType.APPLE) + startFlow(idp.id, idpTypeToSlug(IdentityProviderType.APPLE)) } > ); @@ -96,7 +85,7 @@ export function SignInWithIdp({ key={`idp-${i}`} name={idp.name} onClick={() => - navigateToAuthUrl(idp.id, IdentityProviderType.OAUTH) + startFlow(idp.id, idpTypeToSlug(IdentityProviderType.OAUTH)) } > ); @@ -106,7 +95,7 @@ export function SignInWithIdp({ key={`idp-${i}`} name={idp.name} onClick={() => - navigateToAuthUrl(idp.id, IdentityProviderType.OIDC) + startFlow(idp.id, idpTypeToSlug(IdentityProviderType.OIDC)) } > ); @@ -116,7 +105,10 @@ export function SignInWithIdp({ key={`idp-${i}`} name={idp.name} onClick={() => - navigateToAuthUrl(idp.id, IdentityProviderType.GITHUB) + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.GITHUB), + ) } > ); @@ -126,7 +118,10 @@ export function SignInWithIdp({ key={`idp-${i}`} name={idp.name} onClick={() => - navigateToAuthUrl(idp.id, IdentityProviderType.GITHUB_ES) + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.GITHUB_ES), + ) } > ); @@ -136,7 +131,10 @@ export function SignInWithIdp({ key={`idp-${i}`} name={idp.name} onClick={() => - navigateToAuthUrl(idp.id, IdentityProviderType.AZURE_AD) + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.AZURE_AD), + ) } > ); @@ -147,7 +145,10 @@ export function SignInWithIdp({ e2e="google" name={idp.name} onClick={() => - navigateToAuthUrl(idp.id, IdentityProviderType.GOOGLE) + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.GOOGLE), + ) } > ); @@ -157,7 +158,10 @@ export function SignInWithIdp({ key={`idp-${i}`} name={idp.name} onClick={() => - navigateToAuthUrl(idp.id, IdentityProviderType.GITLAB) + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.GITLAB), + ) } > ); @@ -167,9 +171,9 @@ export function SignInWithIdp({ key={`idp-${i}`} name={idp.name} onClick={() => - navigateToAuthUrl( + startFlow( idp.id, - IdentityProviderType.GITLAB_SELF_HOSTED, + idpTypeToSlug(IdentityProviderType.GITLAB_SELF_HOSTED), ) } > diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index be00840626..019b05ecbc 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -1,6 +1,7 @@ "use server"; import { startIdentityProviderFlow } from "@/lib/zitadel"; +import { redirect } from "next/navigation"; export type StartIDPFlowCommand = { idpId: string; @@ -15,5 +16,13 @@ export async function startIDPFlow(command: StartIDPFlowCommand) { successUrl: command.successUrl, failureUrl: command.failureUrl, }, + }).then((response) => { + if ( + response && + response.nextStep.case === "authUrl" && + response?.nextStep.value + ) { + return redirect(response.nextStep.value); + } }); } From fd90fcb752af66437ff97b9e924294036b48f291 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 18 Nov 2024 10:42:21 +0100 Subject: [PATCH 432/640] format --- apps/login/src/components/idp-signin.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/login/src/components/idp-signin.tsx b/apps/login/src/components/idp-signin.tsx index 657d72e39a..aee4bc7788 100644 --- a/apps/login/src/components/idp-signin.tsx +++ b/apps/login/src/components/idp-signin.tsx @@ -34,7 +34,8 @@ export function IdpSignin({ idpIntentToken, }, authRequestId, - }).catch((error) => { + }) + .catch((error) => { setError(error.message); return; }) From c354ba2432ded535a619a449728bfd7ff6fad26e Mon Sep 17 00:00:00 2001 From: Fabienne Date: Mon, 18 Nov 2024 11:42:34 +0100 Subject: [PATCH 433/640] google idp test criteria --- acceptance/tests/idp-goolge.spec.ts | 152 ++++++++++++++++++ ....spec.ts => username-password-u2f.spec.ts} | 0 2 files changed, 152 insertions(+) create mode 100644 acceptance/tests/idp-goolge.spec.ts rename acceptance/tests/{username-password-u2f_email.spec.ts => username-password-u2f.spec.ts} (100%) diff --git a/acceptance/tests/idp-goolge.spec.ts b/acceptance/tests/idp-goolge.spec.ts new file mode 100644 index 0000000000..345ac72494 --- /dev/null +++ b/acceptance/tests/idp-goolge.spec.ts @@ -0,0 +1,152 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + + +test("login with Google IDP - auto redirect", async ({user, page}) => { + // Given idp Google is configure on the organization as only authencation method + // Given the user has only idp Google added as auth method + + // User is automatically redirected to Google + // User authenticates in Google + // User is redirect to ZITADEL login + // User is redirected to the app +}); + + +test("login with Google IDP - auto redirect, error", async ({user, page}) => { + // Given idp Google is configure on the organization as only authencation method + // Given the user has only idp Google added as auth method + + // User is automatically redirected to Google + // User authenticates in Google and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Google Login" +}); + + +test("login with Google IDP", async ({user, page}) => { + // Given username password and idp Google is configure on the organization as authencation method + // Given the user has username password and Google configured + + // Login form shows username field and a Google Login button + // User clicks on the Google button + // User is redirected to Google + // User authenticates in Google and gets an error + // User is redirect to ZITADEL login automatically + // User is redirected to app automatically +}); + + +test("login with Google IDP, error", async ({user, page}) => { + // Given username password and idp Google is configure on the organization as authencation method + // Given the user has username password and Google configured + + // Login form shows username field and a Google Login button + // User clicks on the Google button + // User is redirected to Google + // User authenticates in Google and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Google Login" + // User can choose password for authentication +}); + +test("login with Google IDP, no user existing - auto register", async ({user, page}) => { + // Given idp Google is configure on the organization as only authencation method + // Given idp Google is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Google + // User authenticates in Google + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Google IDP, no user existing - auto register not possible", async ({user, page}) => { + // Given idp Google is configure on the organization as only authencation method + // Given idp Google is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Google + // User authenticates in Google + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Google IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { + // Given idp Google is configure on the organization as only authencation method + // Given idp Google is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Google + // User authenticates in Google + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information +}); + +test("login with Google IDP, no user linked - auto link", async ({user, page}) => { + // Given idp Google is configure on the organization as only authencation method + // Given idp Google is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + + // User is automatically redirected to Google + // User authenticates in Google with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app +}); + +test("login with Google IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Google is configure on the organization as only authencation method + // Given idp Google is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Google + // User authenticates in Google with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible +}); + + +test("login with Google IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Google is configure on the organization as only authencation method + // Given idp Google is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Google + // User authenticates in Google with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app +}); diff --git a/acceptance/tests/username-password-u2f_email.spec.ts b/acceptance/tests/username-password-u2f.spec.ts similarity index 100% rename from acceptance/tests/username-password-u2f_email.spec.ts rename to acceptance/tests/username-password-u2f.spec.ts From 3a22045b3691df5e6f783bf789f8997f74db90b0 Mon Sep 17 00:00:00 2001 From: Fabienne Date: Mon, 18 Nov 2024 14:04:04 +0100 Subject: [PATCH 434/640] add test spec for all idps --- acceptance/tests/idp-apple.spec.ts | 154 +++++++++++++++++ acceptance/tests/idp-generic-jwt.spec.ts | 151 +++++++++++++++++ acceptance/tests/idp-generic-oauth.spec.ts | 152 +++++++++++++++++ acceptance/tests/idp-generic-oidc.spec.ts | 153 +++++++++++++++++ .../tests/idp-github-enterprise.spec.ts | 156 +++++++++++++++++ acceptance/tests/idp-github.spec.ts | 156 +++++++++++++++++ .../tests/idp-gitlab-self-hosted.spec.ts | 156 +++++++++++++++++ acceptance/tests/idp-gitlab.spec.ts | 156 +++++++++++++++++ ...{idp-goolge.spec.ts => idp-google.spec.ts} | 1 - acceptance/tests/idp-ldap.spec.ts | 151 +++++++++++++++++ acceptance/tests/idp-microsoft.spec.ts | 154 +++++++++++++++++ acceptance/tests/idp-saml.spec.ts | 157 ++++++++++++++++++ 12 files changed, 1696 insertions(+), 1 deletion(-) create mode 100644 acceptance/tests/idp-apple.spec.ts create mode 100644 acceptance/tests/idp-generic-jwt.spec.ts create mode 100644 acceptance/tests/idp-generic-oauth.spec.ts create mode 100644 acceptance/tests/idp-generic-oidc.spec.ts create mode 100644 acceptance/tests/idp-github-enterprise.spec.ts create mode 100644 acceptance/tests/idp-github.spec.ts create mode 100644 acceptance/tests/idp-gitlab-self-hosted.spec.ts create mode 100644 acceptance/tests/idp-gitlab.spec.ts rename acceptance/tests/{idp-goolge.spec.ts => idp-google.spec.ts} (99%) create mode 100644 acceptance/tests/idp-ldap.spec.ts create mode 100644 acceptance/tests/idp-microsoft.spec.ts create mode 100644 acceptance/tests/idp-saml.spec.ts diff --git a/acceptance/tests/idp-apple.spec.ts b/acceptance/tests/idp-apple.spec.ts new file mode 100644 index 0000000000..c948505378 --- /dev/null +++ b/acceptance/tests/idp-apple.spec.ts @@ -0,0 +1,154 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + +// Note for all tests, in case Apple doesn't deliver all relevant information per default +// We should add an action in the needed cases + +test("login with Apple IDP - auto redirect", async ({user, page}) => { + // Given idp Apple is configure on the organization as only authencation method + // Given the user has only idp Apple added as auth method + + // User is automatically redirected to Apple + // User authenticates in Apple + // User is redirect to ZITADEL login + // User is redirected to the app +}); + + +test("login with Apple IDP - auto redirect, error", async ({user, page}) => { + // Given idp Apple is configure on the organization as only authencation method + // Given the user has only idp Apple added as auth method + + // User is automatically redirected to Apple + // User authenticates in Apple and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Apple Login" +}); + + +test("login with Apple IDP", async ({user, page}) => { + // Given username password and idp Apple is configure on the organization as authencation method + // Given the user has username password and Apple configured + + // Login form shows username field and a Apple Login button + // User clicks on the Apple button + // User is redirected to Apple + // User authenticates in Apple and gets an error + // User is redirect to ZITADEL login automatically + // User is redirected to app automatically +}); + + +test("login with Apple IDP, error", async ({user, page}) => { + // Given username password and idp Apple is configure on the organization as authencation method + // Given the user has username password and Apple configured + + // Login form shows username field and a Apple Login button + // User clicks on the Apple button + // User is redirected to Apple + // User authenticates in Apple and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Apple Login" + // User can choose password for authentication +}); + +test("login with Apple IDP, no user existing - auto register", async ({user, page}) => { + // Given idp Apple is configure on the organization as only authencation method + // Given idp Apple is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Apple + // User authenticates in Apple + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Apple IDP, no user existing - auto register not possible", async ({user, page}) => { + // Given idp Apple is configure on the organization as only authencation method + // Given idp Apple is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Apple + // User authenticates in Apple + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Apple IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { + // Given idp Apple is configure on the organization as only authencation method + // Given idp Apple is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Apple + // User authenticates in Apple + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information +}); + +test("login with Apple IDP, no user linked - auto link", async ({user, page}) => { + // Given idp Apple is configure on the organization as only authencation method + // Given idp Apple is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + + // User is automatically redirected to Apple + // User authenticates in Apple with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app +}); + +test("login with Apple IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Apple is configure on the organization as only authencation method + // Given idp Apple is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Apple + // User authenticates in Apple with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible +}); + + +test("login with Apple IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Apple is configure on the organization as only authencation method + // Given idp Apple is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Apple + // User authenticates in Apple with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app +}); diff --git a/acceptance/tests/idp-generic-jwt.spec.ts b/acceptance/tests/idp-generic-jwt.spec.ts new file mode 100644 index 0000000000..2127b10a34 --- /dev/null +++ b/acceptance/tests/idp-generic-jwt.spec.ts @@ -0,0 +1,151 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + +test("login with Generic JWT IDP - auto redirect", async ({user, page}) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given the user has only idp Generic JWT added as auth method + + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT + // User is redirect to ZITADEL login + // User is redirected to the app +}); + + +test("login with Generic JWT IDP - auto redirect, error", async ({user, page}) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given the user has only idp Generic JWT added as auth method + + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Generic JWT Login" +}); + + +test("login with Generic JWT IDP", async ({user, page}) => { + // Given username password and idp Generic JWT is configure on the organization as authencation method + // Given the user has username password and Generic JWT configured + + // Login form shows username field and a Generic JWT Login button + // User clicks on the Generic JWT button + // User is redirected to Generic JWT + // User authenticates in Generic JWT and gets an error + // User is redirect to ZITADEL login automatically + // User is redirected to app automatically +}); + + +test("login with Generic JWT IDP, error", async ({user, page}) => { + // Given username password and idp Generic JWT is configure on the organization as authencation method + // Given the user has username password and Generic JWT configured + + // Login form shows username field and a Generic JWT Login button + // User clicks on the Generic JWT button + // User is redirected to Generic JWT + // User authenticates in Generic JWT and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Generic JWT Login" + // User can choose password for authentication +}); + +test("login with Generic JWT IDP, no user existing - auto register", async ({user, page}) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given idp Generic JWT is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Generic JWT IDP, no user existing - auto register not possible", async ({user, page}) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given idp Generic JWT is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Generic JWT IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given idp Generic JWT is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information +}); + +test("login with Generic JWT IDP, no user linked - auto link", async ({user, page}) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given idp Generic JWT is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app +}); + +test("login with Generic JWT IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given idp Generic JWT is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible +}); + + +test("login with Generic JWT IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given idp Generic JWT is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app +}); diff --git a/acceptance/tests/idp-generic-oauth.spec.ts b/acceptance/tests/idp-generic-oauth.spec.ts new file mode 100644 index 0000000000..7f27e3167f --- /dev/null +++ b/acceptance/tests/idp-generic-oauth.spec.ts @@ -0,0 +1,152 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + + +test("login with Generic OAuth IDP - auto redirect", async ({user, page}) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given the user has only idp Generic OAuth added as auth method + + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth + // User is redirect to ZITADEL login + // User is redirected to the app +}); + + +test("login with Generic OAuth IDP - auto redirect, error", async ({user, page}) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given the user has only idp Generic OAuth added as auth method + + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Generic OAuth Login" +}); + + +test("login with Generic OAuth IDP", async ({user, page}) => { + // Given username password and idp Generic OAuth is configure on the organization as authencation method + // Given the user has username password and Generic OAuth configured + + // Login form shows username field and a Generic OAuth Login button + // User clicks on the Generic OAuth button + // User is redirected to Generic OAuth + // User authenticates in Generic OAuth and gets an error + // User is redirect to ZITADEL login automatically + // User is redirected to app automatically +}); + + +test("login with Generic OAuth IDP, error", async ({user, page}) => { + // Given username password and idp Generic OAuth is configure on the organization as authencation method + // Given the user has username password and Generic OAuth configured + + // Login form shows username field and a Generic OAuth Login button + // User clicks on the Generic OAuth button + // User is redirected to Generic OAuth + // User authenticates in Generic OAuth and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Generic OAuth Login" + // User can choose password for authentication +}); + +test("login with Generic OAuth IDP, no user existing - auto register", async ({user, page}) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given idp Generic OAuth is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Generic OAuth IDP, no user existing - auto register not possible", async ({user, page}) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given idp Generic OAuth is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Generic OAuth IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given idp Generic OAuth is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information +}); + +test("login with Generic OAuth IDP, no user linked - auto link", async ({user, page}) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given idp Generic OAuth is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app +}); + +test("login with Generic OAuth IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given idp Generic OAuth is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible +}); + + +test("login with Generic OAuth IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given idp Generic OAuth is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app +}); diff --git a/acceptance/tests/idp-generic-oidc.spec.ts b/acceptance/tests/idp-generic-oidc.spec.ts new file mode 100644 index 0000000000..f3c9e59bd6 --- /dev/null +++ b/acceptance/tests/idp-generic-oidc.spec.ts @@ -0,0 +1,153 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + +// Note, we should use a provider such as Google to test this, where we know OIDC standard is properly implemented + +test("login with Generic OIDC IDP - auto redirect", async ({user, page}) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given the user has only idp Generic OIDC added as auth method + + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC + // User is redirect to ZITADEL login + // User is redirected to the app +}); + + +test("login with Generic OIDC IDP - auto redirect, error", async ({user, page}) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given the user has only idp Generic OIDC added as auth method + + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Generic OIDC Login" +}); + + +test("login with Generic OIDC IDP", async ({user, page}) => { + // Given username password and idp Generic OIDC is configure on the organization as authencation method + // Given the user has username password and Generic OIDC configured + + // Login form shows username field and a Generic OIDC Login button + // User clicks on the Generic OIDC button + // User is redirected to Generic OIDC + // User authenticates in Generic OIDC and gets an error + // User is redirect to ZITADEL login automatically + // User is redirected to app automatically +}); + + +test("login with Generic OIDC IDP, error", async ({user, page}) => { + // Given username password and idp Generic OIDC is configure on the organization as authencation method + // Given the user has username password and Generic OIDC configured + + // Login form shows username field and a Generic OIDC Login button + // User clicks on the Generic OIDC button + // User is redirected to Generic OIDC + // User authenticates in Generic OIDC and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Generic OIDC Login" + // User can choose password for authentication +}); + +test("login with Generic OIDC IDP, no user existing - auto register", async ({user, page}) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given idp Generic OIDC is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Generic OIDC IDP, no user existing - auto register not possible", async ({user, page}) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given idp Generic OIDC is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Generic OIDC IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given idp Generic OIDC is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information +}); + +test("login with Generic OIDC IDP, no user linked - auto link", async ({user, page}) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given idp Generic OIDC is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app +}); + +test("login with Generic OIDC IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given idp Generic OIDC is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible +}); + + +test("login with Generic OIDC IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given idp Generic OIDC is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app +}); diff --git a/acceptance/tests/idp-github-enterprise.spec.ts b/acceptance/tests/idp-github-enterprise.spec.ts new file mode 100644 index 0000000000..6bbcbc2026 --- /dev/null +++ b/acceptance/tests/idp-github-enterprise.spec.ts @@ -0,0 +1,156 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + + +test("login with GitHub Enterprise IDP - auto redirect", async ({user, page}) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given the user has only idp GitHub Enterprise added as auth method + + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise + // User is redirect to ZITADEL login + // User is redirected to the app +}); + + +test("login with GitHub Enterprise IDP - auto redirect, error", async ({user, page}) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given the user has only idp GitHub Enterprise added as auth method + + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in GitHub Enterprise Login" +}); + + +test("login with GitHub Enterprise IDP", async ({user, page}) => { + // Given username password and idp GitHub Enterprise is configure on the organization as authencation method + // Given the user has username password and GitHub Enterprise configured + + // Login form shows username field and a GitHub Enterprise Login button + // User clicks on the GitHub Enterprise button + // User is redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise and gets an error + // User is redirect to ZITADEL login automatically + // User is redirected to app automatically +}); + + +test("login with GitHub Enterprise IDP, error", async ({user, page}) => { + // Given username password and idp GitHub Enterprise is configure on the organization as authencation method + // Given the user has username password and GitHub Enterprise configured + + // Login form shows username field and a GitHub Enterprise Login button + // User clicks on the GitHub Enterprise button + // User is redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in GitHub Enterprise Login" + // User can choose password for authentication +}); + +test("login with GitHub Enterprise IDP, no user existing - auto register", async ({user, page}) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given idp GitHub Enterprise is configure with account creation alloweed, and automatic creation enabled + // Given ZITADEL Action is added to autofill missing user information + // Given no user exists yet + + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with GitHub Enterprise IDP, no user existing - auto register not possible", async ({user, page}) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given idp GitHub Enterprise is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with GitHub Enterprise IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given idp GitHub Enterprise is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information +}); + +test("login with GitHub Enterprise IDP, no user linked - auto link", async ({user, page}) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given idp GitHub Enterprise is configure with account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com exists + + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app +}); + +test("login with GitHub Enterprise IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given idp GitHub Enterprise is configure with manually account linking not allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible +}); + + +test("login with GitHub Enterprise IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given idp GitHub Enterprise is configure with manually account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app +}); diff --git a/acceptance/tests/idp-github.spec.ts b/acceptance/tests/idp-github.spec.ts new file mode 100644 index 0000000000..d6935f9291 --- /dev/null +++ b/acceptance/tests/idp-github.spec.ts @@ -0,0 +1,156 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + + +test("login with GitHub IDP - auto redirect", async ({user, page}) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given the user has only idp GitHub added as auth method + + // User is automatically redirected to GitHub + // User authenticates in GitHub + // User is redirect to ZITADEL login + // User is redirected to the app +}); + + +test("login with GitHub IDP - auto redirect, error", async ({user, page}) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given the user has only idp GitHub added as auth method + + // User is automatically redirected to GitHub + // User authenticates in GitHub and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in GitHub Login" +}); + + +test("login with GitHub IDP", async ({user, page}) => { + // Given username password and idp GitHub is configure on the organization as authencation method + // Given the user has username password and GitHub configured + + // Login form shows username field and a GitHub Login button + // User clicks on the GitHub button + // User is redirected to GitHub + // User authenticates in GitHub and gets an error + // User is redirect to ZITADEL login automatically + // User is redirected to app automatically +}); + + +test("login with GitHub IDP, error", async ({user, page}) => { + // Given username password and idp GitHub is configure on the organization as authencation method + // Given the user has username password and GitHub configured + + // Login form shows username field and a GitHub Login button + // User clicks on the GitHub button + // User is redirected to GitHub + // User authenticates in GitHub and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in GitHub Login" + // User can choose password for authentication +}); + +test("login with GitHub IDP, no user existing - auto register", async ({user, page}) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given idp GitHub is configure with account creation alloweed, and automatic creation enabled + // Given ZITADEL Action is added to autofill missing user information + // Given no user exists yet + + // User is automatically redirected to GitHub + // User authenticates in GitHub + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with GitHub IDP, no user existing - auto register not possible", async ({user, page}) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given idp GitHub is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to GitHub + // User authenticates in GitHub + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with GitHub IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given idp GitHub is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to GitHub + // User authenticates in GitHub + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information +}); + +test("login with GitHub IDP, no user linked - auto link", async ({user, page}) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given idp GitHub is configure with account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com exists + + // User is automatically redirected to GitHub + // User authenticates in GitHub with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app +}); + +test("login with GitHub IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given idp GitHub is configure with manually account linking not allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to GitHub + // User authenticates in GitHub with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible +}); + + +test("login with GitHub IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given idp GitHub is configure with manually account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to GitHub + // User authenticates in GitHub with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app +}); diff --git a/acceptance/tests/idp-gitlab-self-hosted.spec.ts b/acceptance/tests/idp-gitlab-self-hosted.spec.ts new file mode 100644 index 0000000000..fe3b29439e --- /dev/null +++ b/acceptance/tests/idp-gitlab-self-hosted.spec.ts @@ -0,0 +1,156 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + + +test("login with Gitlab Self-Hosted IDP - auto redirect", async ({user, page}) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given the user has only idp Gitlab Self-Hosted added as auth method + + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted + // User is redirect to ZITADEL login + // User is redirected to the app +}); + + +test("login with Gitlab Self-Hosted IDP - auto redirect, error", async ({user, page}) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given the user has only idp Gitlab Self-Hosted added as auth method + + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Gitlab Self-Hosted Login" +}); + + +test("login with Gitlab Self-Hosted IDP", async ({user, page}) => { + // Given username password and idp Gitlab Self-Hosted is configure on the organization as authencation method + // Given the user has username password and Gitlab Self-Hosted configured + + // Login form shows username field and a Gitlab Self-Hosted Login button + // User clicks on the Gitlab Self-Hosted button + // User is redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted and gets an error + // User is redirect to ZITADEL login automatically + // User is redirected to app automatically +}); + + +test("login with Gitlab Self-Hosted IDP, error", async ({user, page}) => { + // Given username password and idp Gitlab Self-Hosted is configure on the organization as authencation method + // Given the user has username password and Gitlab Self-Hosted configured + + // Login form shows username field and a Gitlab Self-Hosted Login button + // User clicks on the Gitlab Self-Hosted button + // User is redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Gitlab Self-Hosted Login" + // User can choose password for authentication +}); + +test("login with Gitlab Self-Hosted IDP, no user existing - auto register", async ({user, page}) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given idp Gitlab Self-Hosted is configure with account creation alloweed, and automatic creation enabled + // Given ZITADEL Action is added to autofill missing user information + // Given no user exists yet + + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Gitlab Self-Hosted IDP, no user existing - auto register not possible", async ({user, page}) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given idp Gitlab Self-Hosted is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Gitlab Self-Hosted IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given idp Gitlab Self-Hosted is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information +}); + +test("login with Gitlab Self-Hosted IDP, no user linked - auto link", async ({user, page}) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given idp Gitlab Self-Hosted is configure with account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com exists + + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app +}); + +test("login with Gitlab Self-Hosted IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given idp Gitlab Self-Hosted is configure with manually account linking not allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible +}); + + +test("login with Gitlab Self-Hosted IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given idp Gitlab Self-Hosted is configure with manually account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app +}); diff --git a/acceptance/tests/idp-gitlab.spec.ts b/acceptance/tests/idp-gitlab.spec.ts new file mode 100644 index 0000000000..4504f670f9 --- /dev/null +++ b/acceptance/tests/idp-gitlab.spec.ts @@ -0,0 +1,156 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + + +test("login with Gitlab IDP - auto redirect", async ({user, page}) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given the user has only idp Gitlab added as auth method + + // User is automatically redirected to Gitlab + // User authenticates in Gitlab + // User is redirect to ZITADEL login + // User is redirected to the app +}); + + +test("login with Gitlab IDP - auto redirect, error", async ({user, page}) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given the user has only idp Gitlab added as auth method + + // User is automatically redirected to Gitlab + // User authenticates in Gitlab and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Gitlab Login" +}); + + +test("login with Gitlab IDP", async ({user, page}) => { + // Given username password and idp Gitlab is configure on the organization as authencation method + // Given the user has username password and Gitlab configured + + // Login form shows username field and a Gitlab Login button + // User clicks on the Gitlab button + // User is redirected to Gitlab + // User authenticates in Gitlab and gets an error + // User is redirect to ZITADEL login automatically + // User is redirected to app automatically +}); + + +test("login with Gitlab IDP, error", async ({user, page}) => { + // Given username password and idp Gitlab is configure on the organization as authencation method + // Given the user has username password and Gitlab configured + + // Login form shows username field and a Gitlab Login button + // User clicks on the Gitlab button + // User is redirected to Gitlab + // User authenticates in Gitlab and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Gitlab Login" + // User can choose password for authentication +}); + +test("login with Gitlab IDP, no user existing - auto register", async ({user, page}) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given idp Gitlab is configure with account creation alloweed, and automatic creation enabled + // Given ZITADEL Action is added to autofill missing user information + // Given no user exists yet + + // User is automatically redirected to Gitlab + // User authenticates in Gitlab + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Gitlab IDP, no user existing - auto register not possible", async ({user, page}) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given idp Gitlab is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Gitlab + // User authenticates in Gitlab + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Gitlab IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given idp Gitlab is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Gitlab + // User authenticates in Gitlab + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information +}); + +test("login with Gitlab IDP, no user linked - auto link", async ({user, page}) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given idp Gitlab is configure with account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com exists + + // User is automatically redirected to Gitlab + // User authenticates in Gitlab with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app +}); + +test("login with Gitlab IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given idp Gitlab is configure with manually account linking not allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Gitlab + // User authenticates in Gitlab with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible +}); + + +test("login with Gitlab IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given idp Gitlab is configure with manually account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Gitlab + // User authenticates in Gitlab with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app +}); diff --git a/acceptance/tests/idp-goolge.spec.ts b/acceptance/tests/idp-google.spec.ts similarity index 99% rename from acceptance/tests/idp-goolge.spec.ts rename to acceptance/tests/idp-google.spec.ts index 345ac72494..0c0d777fe4 100644 --- a/acceptance/tests/idp-goolge.spec.ts +++ b/acceptance/tests/idp-google.spec.ts @@ -24,7 +24,6 @@ const test = base.extend<{ user: PasswordUserWithOTP }>({ }, }); - test("login with Google IDP - auto redirect", async ({user, page}) => { // Given idp Google is configure on the organization as only authencation method // Given the user has only idp Google added as auth method diff --git a/acceptance/tests/idp-ldap.spec.ts b/acceptance/tests/idp-ldap.spec.ts new file mode 100644 index 0000000000..8bd71b16b4 --- /dev/null +++ b/acceptance/tests/idp-ldap.spec.ts @@ -0,0 +1,151 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + +test("login with LDAP IDP - auto redirect", async ({user, page}) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given the user has only idp LDAP added as auth method + + // User is automatically redirected to LDAP + // User authenticates in LDAP + // User is redirect to ZITADEL login + // User is redirected to the app +}); + + +test("login with LDAP IDP - auto redirect, error", async ({user, page}) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given the user has only idp LDAP added as auth method + + // User is automatically redirected to LDAP + // User authenticates in LDAP and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in LDAP Login" +}); + + +test("login with LDAP IDP", async ({user, page}) => { + // Given username password and idp LDAP is configure on the organization as authencation method + // Given the user has username password and LDAP configured + + // Login form shows username field and a LDAP Login button + // User clicks on the LDAP button + // User is redirected to LDAP + // User authenticates in LDAP and gets an error + // User is redirect to ZITADEL login automatically + // User is redirected to app automatically +}); + + +test("login with LDAP IDP, error", async ({user, page}) => { + // Given username password and idp LDAP is configure on the organization as authencation method + // Given the user has username password and LDAP configured + + // Login form shows username field and a LDAP Login button + // User clicks on the LDAP button + // User is redirected to LDAP + // User authenticates in LDAP and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in LDAP Login" + // User can choose password for authentication +}); + +test("login with LDAP IDP, no user existing - auto register", async ({user, page}) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given idp LDAP is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to LDAP + // User authenticates in LDAP + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with LDAP IDP, no user existing - auto register not possible", async ({user, page}) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given idp LDAP is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to LDAP + // User authenticates in LDAP + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with LDAP IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given idp LDAP is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to LDAP + // User authenticates in LDAP + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information +}); + +test("login with LDAP IDP, no user linked - auto link", async ({user, page}) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given idp LDAP is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + + // User is automatically redirected to LDAP + // User authenticates in LDAP with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app +}); + +test("login with LDAP IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given idp LDAP is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to LDAP + // User authenticates in LDAP with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible +}); + + +test("login with LDAP IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given idp LDAP is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to LDAP + // User authenticates in LDAP with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app +}); diff --git a/acceptance/tests/idp-microsoft.spec.ts b/acceptance/tests/idp-microsoft.spec.ts new file mode 100644 index 0000000000..8e37d11de1 --- /dev/null +++ b/acceptance/tests/idp-microsoft.spec.ts @@ -0,0 +1,154 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + +// Note for all tests, in case Microsoft doesn't deliver all relevant information per default +// We should add an action in the needed cases + +test("login with Microsoft IDP - auto redirect", async ({user, page}) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given the user has only idp Microsoft added as auth method + + // User is automatically redirected to Microsoft + // User authenticates in Microsoft + // User is redirect to ZITADEL login + // User is redirected to the app +}); + + +test("login with Microsoft IDP - auto redirect, error", async ({user, page}) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given the user has only idp Microsoft added as auth method + + // User is automatically redirected to Microsoft + // User authenticates in Microsoft and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Microsoft Login" +}); + + +test("login with Microsoft IDP", async ({user, page}) => { + // Given username password and idp Microsoft is configure on the organization as authencation method + // Given the user has username password and Microsoft configured + + // Login form shows username field and a Microsoft Login button + // User clicks on the Microsoft button + // User is redirected to Microsoft + // User authenticates in Microsoft and gets an error + // User is redirect to ZITADEL login automatically + // User is redirected to app automatically +}); + + +test("login with Microsoft IDP, error", async ({user, page}) => { + // Given username password and idp Microsoft is configure on the organization as authencation method + // Given the user has username password and Microsoft configured + + // Login form shows username field and a Microsoft Login button + // User clicks on the Microsoft button + // User is redirected to Microsoft + // User authenticates in Microsoft and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in Microsoft Login" + // User can choose password for authentication +}); + +test("login with Microsoft IDP, no user existing - auto register", async ({user, page}) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given idp Microsoft is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Microsoft + // User authenticates in Microsoft + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Microsoft IDP, no user existing - auto register not possible", async ({user, page}) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given idp Microsoft is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Microsoft + // User authenticates in Microsoft + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with Microsoft IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given idp Microsoft is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to Microsoft + // User authenticates in Microsoft + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information +}); + +test("login with Microsoft IDP, no user linked - auto link", async ({user, page}) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given idp Microsoft is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + + // User is automatically redirected to Microsoft + // User authenticates in Microsoft with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app +}); + +test("login with Microsoft IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given idp Microsoft is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Microsoft + // User authenticates in Microsoft with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible +}); + + +test("login with Microsoft IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given idp Microsoft is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to Microsoft + // User authenticates in Microsoft with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app +}); diff --git a/acceptance/tests/idp-saml.spec.ts b/acceptance/tests/idp-saml.spec.ts new file mode 100644 index 0000000000..a57e19d848 --- /dev/null +++ b/acceptance/tests/idp-saml.spec.ts @@ -0,0 +1,157 @@ +import {test as base} from "@playwright/test"; +import {OtpType, PasswordUserWithOTP} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword} from "./login"; +import {startSink} from "./otp"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUserWithOTP }>({ + user: async ({page}, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + + +test("login with SAML IDP - auto redirect", async ({user, page}) => { + // Given idp SAML is configure on the organization as only authencation method + // Given ZITADEL Action is added to autofill missing user information + // Given the user has only idp SAML added as auth method + + // User is automatically redirected to SAML + // User authenticates in SAML + // User is redirect to ZITADEL login + // User is redirected to the app +}); + + +test("login with SAML IDP - auto redirect, error", async ({user, page}) => { + // Given idp SAML is configure on the organization as only authencation method + // Given the user has only idp SAML added as auth method + + // User is automatically redirected to SAML + // User authenticates in SAML and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in SAML Login" +}); + + +test("login with SAML IDP", async ({user, page}) => { + // Given username password and idp SAML is configure on the organization as authencation method + // Given the user has username password and SAML configured + + // Login form shows username field and a SAML Login button + // User clicks on the SAML button + // User is redirected to SAML + // User authenticates in SAML and gets an error + // User is redirect to ZITADEL login automatically + // User is redirected to app automatically +}); + + +test("login with SAML IDP, error", async ({user, page}) => { + // Given username password and idp SAML is configure on the organization as authencation method + // Given the user has username password and SAML configured + + // Login form shows username field and a SAML Login button + // User clicks on the SAML button + // User is redirected to SAML + // User authenticates in SAML and gets an error + // User is redirect to ZITADEL login + // Error is shown to the user "Something went wrong in SAML Login" + // User can choose password for authentication +}); + +test("login with SAML IDP, no user existing - auto register", async ({user, page}) => { + // Given idp SAML is configure on the organization as only authencation method + // Given idp SAML is configure with account creation alloweed, and automatic creation enabled + // Given ZITADEL Action is added to autofill missing user information + // Given no user exists yet + + // User is automatically redirected to SAML + // User authenticates in SAML + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with SAML IDP, no user existing - auto register not possible", async ({user, page}) => { + // Given idp SAML is configure on the organization as only authencation method + // Given idp SAML is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to SAML + // User authenticates in SAML + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app +}); + +test("login with SAML IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { + // Given idp SAML is configure on the organization as only authencation method + // Given idp SAML is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + + // User is automatically redirected to SAML + // User authenticates in SAML + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information +}); + +test("login with SAML IDP, no user linked - auto link", async ({user, page}) => { + // Given idp SAML is configure on the organization as only authencation method + // Given idp SAML is configure with account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com exists + + // User is automatically redirected to SAML + // User authenticates in SAML with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app +}); + +test("login with SAML IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp SAML is configure on the organization as only authencation method + // Given idp SAML is configure with manually account linking not allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to SAML + // User authenticates in SAML with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible +}); + + +test("login with SAML IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { + // Given idp SAML is configure on the organization as only authencation method + // Given idp SAML is configure with manually account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + + // User is automatically redirected to SAML + // User authenticates in SAML with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app +}); From bbc8d05a86de314eeb5b033fa8dfc898636c1d1c Mon Sep 17 00:00:00 2001 From: Fabienne Date: Mon, 18 Nov 2024 15:30:29 +0100 Subject: [PATCH 435/640] add more test scenarios --- acceptance/tests/idp-apple.spec.ts | 12 +- acceptance/tests/idp-generic-jwt.spec.ts | 12 +- acceptance/tests/idp-generic-oauth.spec.ts | 12 +- acceptance/tests/idp-generic-oidc.spec.ts | 12 +- .../tests/idp-github-enterprise.spec.ts | 12 +- acceptance/tests/idp-github.spec.ts | 12 +- .../tests/idp-gitlab-self-hosted.spec.ts | 12 +- acceptance/tests/idp-gitlab.spec.ts | 12 +- acceptance/tests/idp-google.spec.ts | 12 +- acceptance/tests/idp-ldap.spec.ts | 12 +- acceptance/tests/idp-microsoft.spec.ts | 12 +- acceptance/tests/idp-saml.spec.ts | 12 +- .../login-configuration-possiblities.spec.ts | 91 +++++++++++ acceptance/tests/register.spec.ts | 148 ++++++++++++++++++ .../tests/username-password-otp_email.spec.ts | 8 +- .../tests/username-password-otp_sms.spec.ts | 4 +- .../tests/username-password-totp.spec.ts | 4 +- .../tests/username-password-u2f.spec.ts | 4 +- acceptance/tests/username-password.spec.ts | 67 +++++++- 19 files changed, 384 insertions(+), 86 deletions(-) create mode 100644 acceptance/tests/login-configuration-possiblities.spec.ts diff --git a/acceptance/tests/idp-apple.spec.ts b/acceptance/tests/idp-apple.spec.ts index c948505378..57cd320fd2 100644 --- a/acceptance/tests/idp-apple.spec.ts +++ b/acceptance/tests/idp-apple.spec.ts @@ -34,7 +34,7 @@ test("login with Apple IDP - auto redirect", async ({user, page}) => { // User is automatically redirected to Apple // User authenticates in Apple // User is redirect to ZITADEL login - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -58,7 +58,7 @@ test("login with Apple IDP", async ({user, page}) => { // User is redirected to Apple // User authenticates in Apple and gets an error // User is redirect to ZITADEL login automatically - // User is redirected to app automatically + // User is redirected to app automatically (default redirect url) }); @@ -84,7 +84,7 @@ test("login with Apple IDP, no user existing - auto register", async ({user, pag // User authenticates in Apple // User is redirect to ZITADEL login // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Apple IDP, no user existing - auto register not possible", async ({user, page}) => { @@ -100,7 +100,7 @@ test("login with Apple IDP, no user existing - auto register not possible", asyn // User fills missing information // User clicks register button // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Apple IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { @@ -124,7 +124,7 @@ test("login with Apple IDP, no user linked - auto link", async ({user, page}) => // User authenticates in Apple with user@zitadel.com // User is redirect to ZITADEL login // User is linked with existing user in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Apple IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { @@ -150,5 +150,5 @@ test("login with Apple IDP, no user linked, user doesn't exist - no auto link", // User is redirect to ZITADEL login // User with email address user@zitadel.com can not be found // User is prompted to link the account manually - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-generic-jwt.spec.ts b/acceptance/tests/idp-generic-jwt.spec.ts index 2127b10a34..6ca3558dd6 100644 --- a/acceptance/tests/idp-generic-jwt.spec.ts +++ b/acceptance/tests/idp-generic-jwt.spec.ts @@ -31,7 +31,7 @@ test("login with Generic JWT IDP - auto redirect", async ({user, page}) => { // User is automatically redirected to Generic JWT // User authenticates in Generic JWT // User is redirect to ZITADEL login - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -55,7 +55,7 @@ test("login with Generic JWT IDP", async ({user, page}) => { // User is redirected to Generic JWT // User authenticates in Generic JWT and gets an error // User is redirect to ZITADEL login automatically - // User is redirected to app automatically + // User is redirected to app automatically (default redirect url) }); @@ -81,7 +81,7 @@ test("login with Generic JWT IDP, no user existing - auto register", async ({use // User authenticates in Generic JWT // User is redirect to ZITADEL login // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Generic JWT IDP, no user existing - auto register not possible", async ({user, page}) => { @@ -97,7 +97,7 @@ test("login with Generic JWT IDP, no user existing - auto register not possible" // User fills missing information // User clicks register button // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Generic JWT IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { @@ -121,7 +121,7 @@ test("login with Generic JWT IDP, no user linked - auto link", async ({user, pag // User authenticates in Generic JWT with user@zitadel.com // User is redirect to ZITADEL login // User is linked with existing user in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Generic JWT IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { @@ -147,5 +147,5 @@ test("login with Generic JWT IDP, no user linked, user doesn't exist - no auto l // User is redirect to ZITADEL login // User with email address user@zitadel.com can not be found // User is prompted to link the account manually - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-generic-oauth.spec.ts b/acceptance/tests/idp-generic-oauth.spec.ts index 7f27e3167f..0d55f39973 100644 --- a/acceptance/tests/idp-generic-oauth.spec.ts +++ b/acceptance/tests/idp-generic-oauth.spec.ts @@ -32,7 +32,7 @@ test("login with Generic OAuth IDP - auto redirect", async ({user, page}) => { // User is automatically redirected to Generic OAuth // User authenticates in Generic OAuth // User is redirect to ZITADEL login - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -56,7 +56,7 @@ test("login with Generic OAuth IDP", async ({user, page}) => { // User is redirected to Generic OAuth // User authenticates in Generic OAuth and gets an error // User is redirect to ZITADEL login automatically - // User is redirected to app automatically + // User is redirected to app automatically (default redirect url) }); @@ -82,7 +82,7 @@ test("login with Generic OAuth IDP, no user existing - auto register", async ({u // User authenticates in Generic OAuth // User is redirect to ZITADEL login // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Generic OAuth IDP, no user existing - auto register not possible", async ({user, page}) => { @@ -98,7 +98,7 @@ test("login with Generic OAuth IDP, no user existing - auto register not possibl // User fills missing information // User clicks register button // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Generic OAuth IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { @@ -122,7 +122,7 @@ test("login with Generic OAuth IDP, no user linked - auto link", async ({user, p // User authenticates in Generic OAuth with user@zitadel.com // User is redirect to ZITADEL login // User is linked with existing user in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Generic OAuth IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { @@ -148,5 +148,5 @@ test("login with Generic OAuth IDP, no user linked, user doesn't exist - no auto // User is redirect to ZITADEL login // User with email address user@zitadel.com can not be found // User is prompted to link the account manually - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-generic-oidc.spec.ts b/acceptance/tests/idp-generic-oidc.spec.ts index f3c9e59bd6..ed19a50a73 100644 --- a/acceptance/tests/idp-generic-oidc.spec.ts +++ b/acceptance/tests/idp-generic-oidc.spec.ts @@ -33,7 +33,7 @@ test("login with Generic OIDC IDP - auto redirect", async ({user, page}) => { // User is automatically redirected to Generic OIDC // User authenticates in Generic OIDC // User is redirect to ZITADEL login - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -57,7 +57,7 @@ test("login with Generic OIDC IDP", async ({user, page}) => { // User is redirected to Generic OIDC // User authenticates in Generic OIDC and gets an error // User is redirect to ZITADEL login automatically - // User is redirected to app automatically + // User is redirected to app automatically (default redirect url) }); @@ -83,7 +83,7 @@ test("login with Generic OIDC IDP, no user existing - auto register", async ({us // User authenticates in Generic OIDC // User is redirect to ZITADEL login // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Generic OIDC IDP, no user existing - auto register not possible", async ({user, page}) => { @@ -99,7 +99,7 @@ test("login with Generic OIDC IDP, no user existing - auto register not possible // User fills missing information // User clicks register button // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Generic OIDC IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { @@ -123,7 +123,7 @@ test("login with Generic OIDC IDP, no user linked - auto link", async ({user, pa // User authenticates in Generic OIDC with user@zitadel.com // User is redirect to ZITADEL login // User is linked with existing user in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Generic OIDC IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { @@ -149,5 +149,5 @@ test("login with Generic OIDC IDP, no user linked, user doesn't exist - no auto // User is redirect to ZITADEL login // User with email address user@zitadel.com can not be found // User is prompted to link the account manually - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-github-enterprise.spec.ts b/acceptance/tests/idp-github-enterprise.spec.ts index 6bbcbc2026..ee7f5d8a5b 100644 --- a/acceptance/tests/idp-github-enterprise.spec.ts +++ b/acceptance/tests/idp-github-enterprise.spec.ts @@ -32,7 +32,7 @@ test("login with GitHub Enterprise IDP - auto redirect", async ({user, page}) => // User is automatically redirected to GitHub Enterprise // User authenticates in GitHub Enterprise // User is redirect to ZITADEL login - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -56,7 +56,7 @@ test("login with GitHub Enterprise IDP", async ({user, page}) => { // User is redirected to GitHub Enterprise // User authenticates in GitHub Enterprise and gets an error // User is redirect to ZITADEL login automatically - // User is redirected to app automatically + // User is redirected to app automatically (default redirect url) }); @@ -83,7 +83,7 @@ test("login with GitHub Enterprise IDP, no user existing - auto register", async // User authenticates in GitHub Enterprise // User is redirect to ZITADEL login // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with GitHub Enterprise IDP, no user existing - auto register not possible", async ({user, page}) => { @@ -99,7 +99,7 @@ test("login with GitHub Enterprise IDP, no user existing - auto register not pos // User fills missing information // User clicks register button // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with GitHub Enterprise IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { @@ -124,7 +124,7 @@ test("login with GitHub Enterprise IDP, no user linked - auto link", async ({use // User authenticates in GitHub Enterprise with user@zitadel.com // User is redirect to ZITADEL login // User is linked with existing user in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with GitHub Enterprise IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { @@ -152,5 +152,5 @@ test("login with GitHub Enterprise IDP, no user linked, user doesn't exist - no // User is redirect to ZITADEL login // User with email address user@zitadel.com can not be found // User is prompted to link the account manually - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-github.spec.ts b/acceptance/tests/idp-github.spec.ts index d6935f9291..0ac8c13193 100644 --- a/acceptance/tests/idp-github.spec.ts +++ b/acceptance/tests/idp-github.spec.ts @@ -32,7 +32,7 @@ test("login with GitHub IDP - auto redirect", async ({user, page}) => { // User is automatically redirected to GitHub // User authenticates in GitHub // User is redirect to ZITADEL login - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -56,7 +56,7 @@ test("login with GitHub IDP", async ({user, page}) => { // User is redirected to GitHub // User authenticates in GitHub and gets an error // User is redirect to ZITADEL login automatically - // User is redirected to app automatically + // User is redirected to app automatically (default redirect url) }); @@ -83,7 +83,7 @@ test("login with GitHub IDP, no user existing - auto register", async ({user, pa // User authenticates in GitHub // User is redirect to ZITADEL login // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with GitHub IDP, no user existing - auto register not possible", async ({user, page}) => { @@ -99,7 +99,7 @@ test("login with GitHub IDP, no user existing - auto register not possible", asy // User fills missing information // User clicks register button // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with GitHub IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { @@ -124,7 +124,7 @@ test("login with GitHub IDP, no user linked - auto link", async ({user, page}) = // User authenticates in GitHub with user@zitadel.com // User is redirect to ZITADEL login // User is linked with existing user in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with GitHub IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { @@ -152,5 +152,5 @@ test("login with GitHub IDP, no user linked, user doesn't exist - no auto link", // User is redirect to ZITADEL login // User with email address user@zitadel.com can not be found // User is prompted to link the account manually - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-gitlab-self-hosted.spec.ts b/acceptance/tests/idp-gitlab-self-hosted.spec.ts index fe3b29439e..00b0ec7c4a 100644 --- a/acceptance/tests/idp-gitlab-self-hosted.spec.ts +++ b/acceptance/tests/idp-gitlab-self-hosted.spec.ts @@ -32,7 +32,7 @@ test("login with Gitlab Self-Hosted IDP - auto redirect", async ({user, page}) = // User is automatically redirected to Gitlab Self-Hosted // User authenticates in Gitlab Self-Hosted // User is redirect to ZITADEL login - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -56,7 +56,7 @@ test("login with Gitlab Self-Hosted IDP", async ({user, page}) => { // User is redirected to Gitlab Self-Hosted // User authenticates in Gitlab Self-Hosted and gets an error // User is redirect to ZITADEL login automatically - // User is redirected to app automatically + // User is redirected to app automatically (default redirect url) }); @@ -83,7 +83,7 @@ test("login with Gitlab Self-Hosted IDP, no user existing - auto register", asyn // User authenticates in Gitlab Self-Hosted // User is redirect to ZITADEL login // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Gitlab Self-Hosted IDP, no user existing - auto register not possible", async ({user, page}) => { @@ -99,7 +99,7 @@ test("login with Gitlab Self-Hosted IDP, no user existing - auto register not po // User fills missing information // User clicks register button // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Gitlab Self-Hosted IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { @@ -124,7 +124,7 @@ test("login with Gitlab Self-Hosted IDP, no user linked - auto link", async ({us // User authenticates in Gitlab Self-Hosted with user@zitadel.com // User is redirect to ZITADEL login // User is linked with existing user in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Gitlab Self-Hosted IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { @@ -152,5 +152,5 @@ test("login with Gitlab Self-Hosted IDP, no user linked, user doesn't exist - no // User is redirect to ZITADEL login // User with email address user@zitadel.com can not be found // User is prompted to link the account manually - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-gitlab.spec.ts b/acceptance/tests/idp-gitlab.spec.ts index 4504f670f9..a94b1ac77d 100644 --- a/acceptance/tests/idp-gitlab.spec.ts +++ b/acceptance/tests/idp-gitlab.spec.ts @@ -32,7 +32,7 @@ test("login with Gitlab IDP - auto redirect", async ({user, page}) => { // User is automatically redirected to Gitlab // User authenticates in Gitlab // User is redirect to ZITADEL login - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -56,7 +56,7 @@ test("login with Gitlab IDP", async ({user, page}) => { // User is redirected to Gitlab // User authenticates in Gitlab and gets an error // User is redirect to ZITADEL login automatically - // User is redirected to app automatically + // User is redirected to app automatically (default redirect url) }); @@ -83,7 +83,7 @@ test("login with Gitlab IDP, no user existing - auto register", async ({user, pa // User authenticates in Gitlab // User is redirect to ZITADEL login // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Gitlab IDP, no user existing - auto register not possible", async ({user, page}) => { @@ -99,7 +99,7 @@ test("login with Gitlab IDP, no user existing - auto register not possible", asy // User fills missing information // User clicks register button // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Gitlab IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { @@ -124,7 +124,7 @@ test("login with Gitlab IDP, no user linked - auto link", async ({user, page}) = // User authenticates in Gitlab with user@zitadel.com // User is redirect to ZITADEL login // User is linked with existing user in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Gitlab IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { @@ -152,5 +152,5 @@ test("login with Gitlab IDP, no user linked, user doesn't exist - no auto link", // User is redirect to ZITADEL login // User with email address user@zitadel.com can not be found // User is prompted to link the account manually - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-google.spec.ts b/acceptance/tests/idp-google.spec.ts index 0c0d777fe4..8c13449add 100644 --- a/acceptance/tests/idp-google.spec.ts +++ b/acceptance/tests/idp-google.spec.ts @@ -31,7 +31,7 @@ test("login with Google IDP - auto redirect", async ({user, page}) => { // User is automatically redirected to Google // User authenticates in Google // User is redirect to ZITADEL login - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -55,7 +55,7 @@ test("login with Google IDP", async ({user, page}) => { // User is redirected to Google // User authenticates in Google and gets an error // User is redirect to ZITADEL login automatically - // User is redirected to app automatically + // User is redirected to app automatically (default redirect url) }); @@ -81,7 +81,7 @@ test("login with Google IDP, no user existing - auto register", async ({user, pa // User authenticates in Google // User is redirect to ZITADEL login // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Google IDP, no user existing - auto register not possible", async ({user, page}) => { @@ -97,7 +97,7 @@ test("login with Google IDP, no user existing - auto register not possible", asy // User fills missing information // User clicks register button // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Google IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { @@ -121,7 +121,7 @@ test("login with Google IDP, no user linked - auto link", async ({user, page}) = // User authenticates in Google with user@zitadel.com // User is redirect to ZITADEL login // User is linked with existing user in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Google IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { @@ -147,5 +147,5 @@ test("login with Google IDP, no user linked, user doesn't exist - no auto link", // User is redirect to ZITADEL login // User with email address user@zitadel.com can not be found // User is prompted to link the account manually - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-ldap.spec.ts b/acceptance/tests/idp-ldap.spec.ts index 8bd71b16b4..fc667edee4 100644 --- a/acceptance/tests/idp-ldap.spec.ts +++ b/acceptance/tests/idp-ldap.spec.ts @@ -31,7 +31,7 @@ test("login with LDAP IDP - auto redirect", async ({user, page}) => { // User is automatically redirected to LDAP // User authenticates in LDAP // User is redirect to ZITADEL login - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -55,7 +55,7 @@ test("login with LDAP IDP", async ({user, page}) => { // User is redirected to LDAP // User authenticates in LDAP and gets an error // User is redirect to ZITADEL login automatically - // User is redirected to app automatically + // User is redirected to app automatically (default redirect url) }); @@ -81,7 +81,7 @@ test("login with LDAP IDP, no user existing - auto register", async ({user, page // User authenticates in LDAP // User is redirect to ZITADEL login // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with LDAP IDP, no user existing - auto register not possible", async ({user, page}) => { @@ -97,7 +97,7 @@ test("login with LDAP IDP, no user existing - auto register not possible", async // User fills missing information // User clicks register button // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with LDAP IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { @@ -121,7 +121,7 @@ test("login with LDAP IDP, no user linked - auto link", async ({user, page}) => // User authenticates in LDAP with user@zitadel.com // User is redirect to ZITADEL login // User is linked with existing user in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with LDAP IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { @@ -147,5 +147,5 @@ test("login with LDAP IDP, no user linked, user doesn't exist - no auto link", a // User is redirect to ZITADEL login // User with email address user@zitadel.com can not be found // User is prompted to link the account manually - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-microsoft.spec.ts b/acceptance/tests/idp-microsoft.spec.ts index 8e37d11de1..26ffcc1064 100644 --- a/acceptance/tests/idp-microsoft.spec.ts +++ b/acceptance/tests/idp-microsoft.spec.ts @@ -34,7 +34,7 @@ test("login with Microsoft IDP - auto redirect", async ({user, page}) => { // User is automatically redirected to Microsoft // User authenticates in Microsoft // User is redirect to ZITADEL login - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -58,7 +58,7 @@ test("login with Microsoft IDP", async ({user, page}) => { // User is redirected to Microsoft // User authenticates in Microsoft and gets an error // User is redirect to ZITADEL login automatically - // User is redirected to app automatically + // User is redirected to app automatically (default redirect url) }); @@ -84,7 +84,7 @@ test("login with Microsoft IDP, no user existing - auto register", async ({user, // User authenticates in Microsoft // User is redirect to ZITADEL login // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Microsoft IDP, no user existing - auto register not possible", async ({user, page}) => { @@ -100,7 +100,7 @@ test("login with Microsoft IDP, no user existing - auto register not possible", // User fills missing information // User clicks register button // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Microsoft IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { @@ -124,7 +124,7 @@ test("login with Microsoft IDP, no user linked - auto link", async ({user, page} // User authenticates in Microsoft with user@zitadel.com // User is redirect to ZITADEL login // User is linked with existing user in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with Microsoft IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { @@ -150,5 +150,5 @@ test("login with Microsoft IDP, no user linked, user doesn't exist - no auto lin // User is redirect to ZITADEL login // User with email address user@zitadel.com can not be found // User is prompted to link the account manually - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-saml.spec.ts b/acceptance/tests/idp-saml.spec.ts index a57e19d848..890965180b 100644 --- a/acceptance/tests/idp-saml.spec.ts +++ b/acceptance/tests/idp-saml.spec.ts @@ -33,7 +33,7 @@ test("login with SAML IDP - auto redirect", async ({user, page}) => { // User is automatically redirected to SAML // User authenticates in SAML // User is redirect to ZITADEL login - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -57,7 +57,7 @@ test("login with SAML IDP", async ({user, page}) => { // User is redirected to SAML // User authenticates in SAML and gets an error // User is redirect to ZITADEL login automatically - // User is redirected to app automatically + // User is redirected to app automatically (default redirect url) }); @@ -84,7 +84,7 @@ test("login with SAML IDP, no user existing - auto register", async ({user, page // User authenticates in SAML // User is redirect to ZITADEL login // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with SAML IDP, no user existing - auto register not possible", async ({user, page}) => { @@ -100,7 +100,7 @@ test("login with SAML IDP, no user existing - auto register not possible", async // User fills missing information // User clicks register button // User is created in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with SAML IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { @@ -125,7 +125,7 @@ test("login with SAML IDP, no user linked - auto link", async ({user, page}) => // User authenticates in SAML with user@zitadel.com // User is redirect to ZITADEL login // User is linked with existing user in ZITADEL - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("login with SAML IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { @@ -153,5 +153,5 @@ test("login with SAML IDP, no user linked, user doesn't exist - no auto link", a // User is redirect to ZITADEL login // User with email address user@zitadel.com can not be found // User is prompted to link the account manually - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/login-configuration-possiblities.spec.ts b/acceptance/tests/login-configuration-possiblities.spec.ts new file mode 100644 index 0000000000..0fb3b78975 --- /dev/null +++ b/acceptance/tests/login-configuration-possiblities.spec.ts @@ -0,0 +1,91 @@ +import {test as base} from "@playwright/test"; +import {PasswordUser} from './user'; +import path from 'path'; +import dotenv from 'dotenv'; +import {loginScreenExpect, loginWithPassword, startLogin} from "./login"; +import {loginnameScreenExpect} from "./loginname-screen"; +import {passwordScreenExpect} from "./password-screen"; +import {loginname} from "./loginname"; +import {password} from "./password"; + +// Read from ".env" file. +dotenv.config({path: path.resolve(__dirname, '.env.local')}); + +const test = base.extend<{ user: PasswordUser }>({ + user: async ({page}, use) => { + const user = new PasswordUser({ + email: "password@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + }); + await user.ensure(page); + await use(user); + }, +}); + +test("login with mfa setup, mfa setup prompt", async ({user, page}) => { + // Given the organization has set "multifactor init check time" to 40 + // Given the organization has enabled all possible mfa types + // Given the user has a password but no mfa registered and never authenticated + + // enter login name + // enter password + // User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider +}); + +test("login with mfa setup, mfa setup prompt", async ({user, page}) => { + // Given the organization has set "multifactor init check time" to 0 + // Given the organization has enabled all possible mfa types + // Given the user has a password but no mfa registered and never authenticated + + // enter login name + // enter password + // user is redirected to app +}); + +test("login with mfa setup, force mfa for local authenticated users", async ({user, page}) => { + // Given the organization has enabled force mfa for local authentiacted users + // Given the organization has enabled all possible mfa types + // Given the user has a password but no mfa registered + + // enter login name + // enter password + // User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider +}); + + +test("login with mfa setup, force mfa - local user", async ({user, page}) => { + // Given the organization has enabled force mfa for local authentiacted users + // Given the organization has enabled all possible mfa types + // Given the user has a password but no mfa registered + + // enter login name + // enter password + // User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider +}); + + +test("login with mfa setup, force mfa - external user", async ({user, page}) => { + // Given the organization has enabled force mfa + // Given the organization has enabled all possible mfa types + // Given the user has an idp but no mfa registered + + // enter login name + // redirect to configured external idp + // User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider +}); + + +test("login with mfa setup, force mfa - external user", async ({user, page}) => { + // Given the organization has a password lockout policy set to 1 on the max password attempts + // Given the user has only a password as auth methos + + // enter login name + // enter wrong password + // User will get an error "Wrong password" + // enter password + // User will get an error "Max password attempts reached - user is locked. Please reach out to your administrator" +}); + diff --git a/acceptance/tests/register.spec.ts b/acceptance/tests/register.spec.ts index 0e08db5614..f8711e3fff 100644 --- a/acceptance/tests/register.spec.ts +++ b/acceptance/tests/register.spec.ts @@ -28,3 +28,151 @@ test("register with passkey", async ({page}) => { await registerWithPasskey(page, firstname, lastname, username) await loginScreenExpect(page, firstname + " " + lastname); }); + +test("register with username and password - only password enabled", async ({user, page}) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is not enabled + // Given user doesn't exist + + // Click on button "register new user" + // User is redirected to registration page + // Only password is shown as an option - no passkey + // User enters "firstname", "lastname", "username" and "password" + // User is redirected to app (default redirect url) +}); + +test("register with username and password - wrong password not enough characters", async ({user, page}) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is not enabled + // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter + // Given user doesn't exist + + // Click on button "register new user" + // User is redirected to registration page + // Only password is shown as an option - no passkey + // User enters "firstname", "lastname", "username" and a password thats to short + // Error is shown "Password doesn't match the policy - it must have at least 8 characters" +}); + +test("register with username and password - wrong password number missing", async ({user, page}) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is not enabled + // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter + // Given user doesn't exist + + // Click on button "register new user" + // User is redirected to registration page + // Only password is shown as an option - no passkey + // User enters "firstname", "lastname", "username" and a password without a number + // Error is shown "Password doesn't match the policy - number missing" +}); + +test("register with username and password - wrong password upper case missing", async ({user, page}) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is not enabled + // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter + // Given user doesn't exist + + // Click on button "register new user" + // User is redirected to registration page + // Only password is shown as an option - no passkey + // User enters "firstname", "lastname", "username" and a password without an upper case + // Error is shown "Password doesn't match the policy - uppercase letter missing" +}); + +test("register with username and password - wrong password lower case missing", async ({user, page}) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is not enabled + // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter + // Given user doesn't exist + + // Click on button "register new user" + // User is redirected to registration page + // Only password is shown as an option - no passkey + // User enters "firstname", "lastname", "username" and a password without an lower case + // Error is shown "Password doesn't match the policy - lowercase letter missing" +}); + + +test("register with username and password - wrong password symboo missing", async ({user, page}) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is not enabled + // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter + // Given user doesn't exist + + // Click on button "register new user" + // User is redirected to registration page + // Only password is shown as an option - no passkey + // User enters "firstname", "lastname", "username" and a password without an symbol + // Error is shown "Password doesn't match the policy - symbol missing" +}); + +test("register with username and password - password and passkey enabled", async ({user, page}) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is enabled + // Given user doesn't exist + + // Click on button "register new user" + // User is redirected to registration page + // User enters "firstname", "lastname", "username" + // Password and passkey are shown as authentication option + // User clicks password + // User enters password + // User is redirected to app (default redirect url) +}); + +test("register with username and passkey - password and passkey enabled", async ({user, page}) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is enabled + // Given user doesn't exist + + // Click on button "register new user" + // User is redirected to registration page + // User enters "firstname", "lastname", "username" + // Password and passkey are shown as authentication option + // User clicks passkey + // Passkey is opened automatically + // User verifies passkey + // User is redirected to app (default redirect url) +}); + + +test("register with username and password - registration disabled", async ({user, page}) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given user doesn't exist + + // Button "register new user" is not available +}); + +test("register with username and password - multiple registration options", async ({user, page}) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization one idp is configured and enabled + // Given user doesn't exist + + // Click on button "register new user" + // User is redirected to registration options + // Local User and idp button are shown + // User clicks idp button + // User enters "firstname", "lastname", "username" and "password" + // User clicks next + // User is redirected to app (default redirect url) +}); diff --git a/acceptance/tests/username-password-otp_email.spec.ts b/acceptance/tests/username-password-otp_email.spec.ts index 55a05876a5..43749cb203 100644 --- a/acceptance/tests/username-password-otp_email.spec.ts +++ b/acceptance/tests/username-password-otp_email.spec.ts @@ -33,7 +33,7 @@ test("username, password and email otp login, enter code manually", async ({user // User enters password // User receives an email with a verification code // User enters the code into the ui - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -45,7 +45,7 @@ test("username, password and email otp login, click link in email", async ({user // User enters password // User receives an email with a verification code // User clicks link in the email - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("username, password and email otp login, resend code", async ({user, page}) => { @@ -58,7 +58,7 @@ test("username, password and email otp login, resend code", async ({user, page}) // User clicks resend code // User receives a new email with a verification code // User enters the new code in the ui - // User is redirected to the app + // User is redirected to the app (default redirect url) }); test("username, password and email otp login, wrong code", async ({user, page}) => { @@ -82,5 +82,5 @@ test("username, password and email otp login, multiple mfa options", async ({use // User clicks button to use sms otp as second factor // User receives an sms with a verification code // User enters code in ui - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index d80e03fac8..f762dc8c0d 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -42,7 +42,7 @@ test("username, password and sms otp login", async ({user, page}) => { // User enters password // User receives an sms with a verification code // User enters the code into the ui - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -55,7 +55,7 @@ test("username, password and sms otp login, resend code", async ({user, page}) = // User receives an sms with a verification code // User clicks resend code // User receives a new sms with a verification code - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/username-password-totp.spec.ts b/acceptance/tests/username-password-totp.spec.ts index d81ba9f09e..85ea828e08 100644 --- a/acceptance/tests/username-password-totp.spec.ts +++ b/acceptance/tests/username-password-totp.spec.ts @@ -32,7 +32,7 @@ test("username, password and totp login", async ({user, page}) => { // User enters password // Screen for entering the code is shown directly // User enters the code into the ui - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -59,5 +59,5 @@ test("username, password and totp login, multiple mfa options", async ({user, pa // User clicks button to use email otp instead // User receives an email with a verification code // User enters code in ui - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/username-password-u2f.spec.ts b/acceptance/tests/username-password-u2f.spec.ts index fad16a2fc5..0f87175ad0 100644 --- a/acceptance/tests/username-password-u2f.spec.ts +++ b/acceptance/tests/username-password-u2f.spec.ts @@ -33,7 +33,7 @@ test("username, password and u2f login", async ({user, page}) => { // User enters password // Popup for u2f is directly opened // User verifies u2f - // User is redirected to the app + // User is redirected to the app (default redirect url) }); @@ -48,5 +48,5 @@ test("username, password and u2f login, multiple mfa options", async ({user, pag // User clicks button to use email otp as second factor // User receives an email with a verification code // User enters code in ui - // User is redirected to the app + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index ee97f21bc6..c7f4e8227c 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -62,6 +62,15 @@ test("username and password login, initial password change", async ({user, page} // create new password }); + +test("username and password login, reset password hidden", async ({user, page}) => { + // Given the organization has enabled "Password reset hidden" in the login policy + // Given username password login is enabled on the users organization + + // enter login name + // password reset link should not be shown on password screen +}); + test("username and password login, reset password - enter code manually", async ({user, page}) => { // Given user has forgotten password and clicks the forgot password button // Given username password login is enabled on the users organization @@ -69,7 +78,7 @@ test("username and password login, reset password - enter code manually", async // enter login name // click password forgotten // enter code from email - // user is redirected to app + // user is redirected to app (default redirect url) }); test("username and password login, reset password - click link", async ({user, page}) => { @@ -80,7 +89,7 @@ test("username and password login, reset password - click link", async ({user, p // click password forgotten // click link in email // set new password - // redirect to app + // redirect to app (default redirect url) }); test("username and password login, reset password, resend code", async ({user, page}) => { @@ -91,5 +100,55 @@ test("username and password login, reset password, resend code", async ({user, p // click password forgotten // click resend code // enter code from second email - // user is authenticated -}); \ No newline at end of file + // user is redirected to app (default redirect url) +}); + +test("email login enabled", async ({user, page}) => { + // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists + // Given no other user with the same email address exists + + // enter email address "test@zitadel.com " in login screen + // user will get to password screen +}); + +test("email login disabled", async ({user, page}) => { + // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists + // Given no other user with the same email address exists + + // enter email address "test@zitadel.com" in login screen + // user will see error message "user not found" +}); + +test("email login enabled - multiple users", async ({user, page}) => { + // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists + // Given a second user with the username "testuser2", email test@zitadel.com and phone number 0711111111 exists + + // enter email address "test@zitadel.com" in login screen + // user will see error message "user not found" +}); + + +test("phone login enabled", async ({user, page}) => { + // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists + // Given no other user with the same phon number exists + + // enter phone number "0711111111" in login screen + // user will get to password screen +}); + +test("phone login disabled", async ({user, page}) => { + // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists + // Given no other user with the same phone number exists + + // enter phone number "0711111111" in login screen + // user will see error message "user not found" +}); + +test("phone login enabled - multiple users", async ({user, page}) => { + // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists + // Given a second user with the username "testuser2", email test@zitadel.com and phone number 0711111111 exists + + // enter phone number "0711111111" in login screen + // user will see error message "user not found" +}); + From c077b7f16a4e9e249efae03323874658c7bcf11e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 18 Nov 2024 16:54:50 +0100 Subject: [PATCH 436/640] Update acceptance/tests/idp-apple.spec.ts --- acceptance/tests/idp-apple.spec.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/acceptance/tests/idp-apple.spec.ts b/acceptance/tests/idp-apple.spec.ts index 57cd320fd2..e8cdba47a3 100644 --- a/acceptance/tests/idp-apple.spec.ts +++ b/acceptance/tests/idp-apple.spec.ts @@ -27,14 +27,12 @@ const test = base.extend<{ user: PasswordUserWithOTP }>({ // Note for all tests, in case Apple doesn't deliver all relevant information per default // We should add an action in the needed cases -test("login with Apple IDP - auto redirect", async ({user, page}) => { - // Given idp Apple is configure on the organization as only authencation method - // Given the user has only idp Apple added as auth method - - // User is automatically redirected to Apple - // User authenticates in Apple - // User is redirect to ZITADEL login - // User is redirected to the app (default redirect url) +test("login with Apple IDP", async ({user, page}) => { + // Given idp Apple is configured on the organization + // Given the user has Apple added as auth method + // User authenticates with Apple + // User is redirected back to login + // User is redirected to the app }); From 8033fa7dd6b0f645a50fdbde15849e05ce8c99db Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 18 Nov 2024 16:54:57 +0100 Subject: [PATCH 437/640] Update acceptance/tests/idp-apple.spec.ts --- acceptance/tests/idp-apple.spec.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/acceptance/tests/idp-apple.spec.ts b/acceptance/tests/idp-apple.spec.ts index e8cdba47a3..d56f104ce0 100644 --- a/acceptance/tests/idp-apple.spec.ts +++ b/acceptance/tests/idp-apple.spec.ts @@ -47,17 +47,6 @@ test("login with Apple IDP - auto redirect, error", async ({user, page}) => { }); -test("login with Apple IDP", async ({user, page}) => { - // Given username password and idp Apple is configure on the organization as authencation method - // Given the user has username password and Apple configured - - // Login form shows username field and a Apple Login button - // User clicks on the Apple button - // User is redirected to Apple - // User authenticates in Apple and gets an error - // User is redirect to ZITADEL login automatically - // User is redirected to app automatically (default redirect url) -}); test("login with Apple IDP, error", async ({user, page}) => { From 054cb4672f07ef7b1267d5734892c85d72e63b48 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 18 Nov 2024 16:55:03 +0100 Subject: [PATCH 438/640] Update acceptance/tests/login-configuration-possiblities.spec.ts --- .../tests/login-configuration-possiblities.spec.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/acceptance/tests/login-configuration-possiblities.spec.ts b/acceptance/tests/login-configuration-possiblities.spec.ts index 0fb3b78975..f277bdccac 100644 --- a/acceptance/tests/login-configuration-possiblities.spec.ts +++ b/acceptance/tests/login-configuration-possiblities.spec.ts @@ -26,13 +26,10 @@ const test = base.extend<{ user: PasswordUser }>({ }); test("login with mfa setup, mfa setup prompt", async ({user, page}) => { - // Given the organization has set "multifactor init check time" to 40 - // Given the organization has enabled all possible mfa types - // Given the user has a password but no mfa registered and never authenticated - - // enter login name - // enter password - // User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider + // Given the organization has enabled at least one mfa types + // Given the user has a password but no mfa registered + // User authenticates with login name and password + // User is prompted to setup a mfa, mfa providers are listed, the user can choose the provider }); test("login with mfa setup, mfa setup prompt", async ({user, page}) => { From d7be98e6f91097b22b0651da2d111318e2f4fb14 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 18 Nov 2024 16:55:11 +0100 Subject: [PATCH 439/640] Update acceptance/tests/login-configuration-possiblities.spec.ts --- .../tests/login-configuration-possiblities.spec.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/acceptance/tests/login-configuration-possiblities.spec.ts b/acceptance/tests/login-configuration-possiblities.spec.ts index f277bdccac..0f06bd9409 100644 --- a/acceptance/tests/login-configuration-possiblities.spec.ts +++ b/acceptance/tests/login-configuration-possiblities.spec.ts @@ -32,15 +32,13 @@ test("login with mfa setup, mfa setup prompt", async ({user, page}) => { // User is prompted to setup a mfa, mfa providers are listed, the user can choose the provider }); -test("login with mfa setup, mfa setup prompt", async ({user, page}) => { +test("login with mfa setup, no mfa setup prompt", async ({user, page}) => { // Given the organization has set "multifactor init check time" to 0 - // Given the organization has enabled all possible mfa types - // Given the user has a password but no mfa registered and never authenticated - - // enter login name - // enter password - // user is redirected to app -}); + // Given the organization has enabled mfa types + // Given the user has a password but no mfa registered + // User authenticates with loginname and password + // user is directly loged in and not prompted to setup mfa + }); test("login with mfa setup, force mfa for local authenticated users", async ({user, page}) => { // Given the organization has enabled force mfa for local authentiacted users From c9fe0d92c3ceb7a421262c151038561592491248 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 18 Nov 2024 16:55:19 +0100 Subject: [PATCH 440/640] Update acceptance/tests/idp-apple.spec.ts --- acceptance/tests/idp-apple.spec.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/acceptance/tests/idp-apple.spec.ts b/acceptance/tests/idp-apple.spec.ts index d56f104ce0..7de8875eef 100644 --- a/acceptance/tests/idp-apple.spec.ts +++ b/acceptance/tests/idp-apple.spec.ts @@ -49,18 +49,6 @@ test("login with Apple IDP - auto redirect, error", async ({user, page}) => { -test("login with Apple IDP, error", async ({user, page}) => { - // Given username password and idp Apple is configure on the organization as authencation method - // Given the user has username password and Apple configured - - // Login form shows username field and a Apple Login button - // User clicks on the Apple button - // User is redirected to Apple - // User authenticates in Apple and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Apple Login" - // User can choose password for authentication -}); test("login with Apple IDP, no user existing - auto register", async ({user, page}) => { // Given idp Apple is configure on the organization as only authencation method From 7f0c70709c2293fb14056f967fc612dd6c50e8b2 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 18 Nov 2024 16:55:28 +0100 Subject: [PATCH 441/640] Update acceptance/tests/idp-apple.spec.ts --- acceptance/tests/idp-apple.spec.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/acceptance/tests/idp-apple.spec.ts b/acceptance/tests/idp-apple.spec.ts index 7de8875eef..c599cd5bba 100644 --- a/acceptance/tests/idp-apple.spec.ts +++ b/acceptance/tests/idp-apple.spec.ts @@ -36,14 +36,13 @@ test("login with Apple IDP", async ({user, page}) => { }); -test("login with Apple IDP - auto redirect, error", async ({user, page}) => { - // Given idp Apple is configure on the organization as only authencation method - // Given the user has only idp Apple added as auth method - - // User is automatically redirected to Apple - // User authenticates in Apple and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Apple Login" +test("login with Apple IDP - error", async ({user, page}) => { + // Given idp Apple is configure on the organization + // Given the user Apple added as auth method + // User is redirected to Apple + // User authenticates with Apple and gets an error + // User is redirect back to login + // An error is shown to the user "Something went wrong in Apple Login" }); From 56f2b90c76efb89dffabbbce7fcc55289232e876 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 18 Nov 2024 17:30:46 +0100 Subject: [PATCH 442/640] finish flow --- apps/login/locales/de.json | 3 +- apps/login/locales/en.json | 3 +- apps/login/locales/es.json | 3 +- apps/login/locales/it.json | 3 +- apps/login/readme.md | 8 ++++ apps/login/src/app/(login)/signedin/page.tsx | 30 ++++++++++++++- apps/login/src/components/login-passkey.tsx | 6 +-- apps/login/src/lib/login.ts | 40 ++++++++++++-------- 8 files changed, 72 insertions(+), 24 deletions(-) diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index ef71731d1f..c6576de000 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -151,7 +151,8 @@ }, "signedin": { "title": "Willkommen {user}!", - "description": "Sie sind angemeldet." + "description": "Sie sind angemeldet.", + "continue": "Weiter" }, "verify": { "userIdMissing": "Keine Benutzer-ID angegeben!", diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index d6cdc4009e..4bb7bfe552 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -151,7 +151,8 @@ }, "signedin": { "title": "Welcome {user}!", - "description": "You are signed in." + "description": "You are signed in.", + "continue": "Continue" }, "verify": { "userIdMissing": "No userId provided!", diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index 0a9c24f93b..e7f093dc5d 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -151,7 +151,8 @@ }, "signedin": { "title": "¡Bienvenido {user}!", - "description": "Has iniciado sesión." + "description": "Has iniciado sesión.", + "continue": "Continuar" }, "verify": { "userIdMissing": "¡No se proporcionó userId!", diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index 74425a06a0..0b488e00a2 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -151,7 +151,8 @@ }, "signedin": { "title": "Benvenuto {user}!", - "description": "Sei connesso." + "description": "Sei connesso.", + "continue": "Continua" }, "verify": { "userIdMissing": "Nessun userId fornito!", diff --git a/apps/login/readme.md b/apps/login/readme.md index 190fcf6002..38eee55b90 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -386,3 +386,11 @@ In future, self service options to jump to are shown below, like: - logout > NOTE: This page has to be explicitly enabled or act as a fallback if no default redirect is set. + +## Currently NOT Supported + +- loginSettings.disableLoginWithEmail +- loginSettings.disableLoginWithPhone +- loginSettings.allowExternalIdp - this will be deprecated with the new login as it can be determined by the available IDPs +- loginSettings.forceMfaLocalOnly +- loginSettings lifetimes - all besides Multifactor Init Check can be implemented. for the Init Check, an external storage or a timestamp has to be implemented which keeps track of the last verification diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index b2274478ea..3833d1b753 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -1,14 +1,21 @@ +import { Button, ButtonVariants } from "@/components/button"; import { DynamicTheme } from "@/components/dynamic-theme"; import { SelfServiceMenu } from "@/components/self-service-menu"; import { UserAvatar } from "@/components/user-avatar"; import { getMostRecentCookieWithLoginname } from "@/lib/cookies"; -import { createCallback, getBrandingSettings, getSession } from "@/lib/zitadel"; +import { + createCallback, + getBrandingSettings, + getLoginSettings, + getSession, +} from "@/lib/zitadel"; import { create } from "@zitadel/client"; import { CreateCallbackRequestSchema, SessionSchema, } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import Link from "next/link"; import { redirect } from "next/navigation"; async function loadSession(loginName: string, authRequestId?: string) { @@ -48,6 +55,11 @@ export default async function Page({ searchParams }: { searchParams: any }) { const branding = await getBrandingSettings(organization); + let loginSettings; + if (!authRequestId) { + loginSettings = await getLoginSettings(organization); + } + return (
    @@ -66,6 +78,22 @@ export default async function Page({ searchParams }: { searchParams: any }) { {sessionFactors?.id && ( )} + + {loginSettings?.defaultRedirectUri && ( +
    + + + + + +
    + )}
    ); diff --git a/apps/login/src/components/login-passkey.tsx b/apps/login/src/components/login-passkey.tsx index ab801028fe..87691ea47c 100644 --- a/apps/login/src/components/login-passkey.tsx +++ b/apps/login/src/components/login-passkey.tsx @@ -176,15 +176,15 @@ export function LoginPasskey({ }, }; - return submitLogin(data).then((resp) => { + return submitLogin(data).then(async (resp) => { return authRequestId && resp?.sessionId - ? finishFlow({ + ? await finishFlow({ sessionId: resp.sessionId, authRequestId: authRequestId, organization: organization, }) : resp?.factors?.user?.loginName - ? finishFlow({ + ? await finishFlow({ loginName: resp.factors.user.loginName, organization: organization, }) diff --git a/apps/login/src/lib/login.ts b/apps/login/src/lib/login.ts index 765255a945..ec023b8a9c 100644 --- a/apps/login/src/lib/login.ts +++ b/apps/login/src/lib/login.ts @@ -1,4 +1,5 @@ import { redirect } from "next/navigation"; +import { getLoginSettings } from "./zitadel"; type FinishFlowCommand = | { @@ -8,25 +9,32 @@ type FinishFlowCommand = | { loginName: string }; /** - * on client: redirects user back to OIDC application or to a success page + * for client: redirects user back to OIDC application or to a success page when using authRequestId, check if a default redirect and redirect to it, or just redirect to a success page with the loginName * @param command * @returns */ -export function finishFlow( +export async function finishFlow( command: FinishFlowCommand & { organization?: string }, ) { - return "sessionId" in command && "authRequestId" in command - ? redirect( - `/login?` + - new URLSearchParams({ - sessionId: command.sessionId, - authRequest: command.authRequestId, - }), - ) - : redirect( - `/signedin?` + - new URLSearchParams({ - loginName: command.loginName, - }), - ); + if ("sessionId" in command && "authRequestId" in command) { + return redirect( + `/login?` + + new URLSearchParams({ + sessionId: command.sessionId, + authRequest: command.authRequestId, + }), + ); + } + + const loginSettings = await getLoginSettings(command.organization); + if (loginSettings?.defaultRedirectUri) { + return redirect(loginSettings.defaultRedirectUri); + } + + return redirect( + `/signedin?` + + new URLSearchParams({ + loginName: command.loginName, + }), + ); } From 57d6f682c5ab6aeb7a37147390912eef1b93ee61 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 18 Nov 2024 18:01:47 +0100 Subject: [PATCH 443/640] test scenarios cleanup --- acceptance/tests/idp-apple.spec.ts | 186 ++++++------- acceptance/tests/idp-generic-jwt.spec.ts | 210 ++++++--------- acceptance/tests/idp-generic-oauth.spec.ts | 211 ++++++--------- acceptance/tests/idp-generic-oidc.spec.ts | 210 ++++++--------- .../tests/idp-github-enterprise.spec.ts | 219 ++++++--------- acceptance/tests/idp-github.spec.ts | 219 ++++++--------- .../tests/idp-gitlab-self-hosted.spec.ts | 219 ++++++--------- acceptance/tests/idp-gitlab.spec.ts | 219 ++++++--------- acceptance/tests/idp-google.spec.ts | 210 ++++++--------- acceptance/tests/idp-ldap.spec.ts | 210 ++++++--------- acceptance/tests/idp-microsoft.spec.ts | 210 ++++++--------- acceptance/tests/idp-saml.spec.ts | 220 ++++++---------- .../login-configuration-possiblities.spec.ts | 113 +++----- acceptance/tests/register.spec.ts | 249 ++++++++---------- acceptance/tests/username-passkey.spec.ts | 34 ++- .../tests/username-password-otp_email.spec.ts | 131 +++++---- .../tests/username-password-otp_sms.spec.ts | 24 -- .../tests/username-password-totp.spec.ts | 95 ++++--- .../tests/username-password-u2f.spec.ts | 78 +++--- acceptance/tests/username-password.spec.ts | 155 +++++------ 20 files changed, 1303 insertions(+), 2119 deletions(-) diff --git a/acceptance/tests/idp-apple.spec.ts b/acceptance/tests/idp-apple.spec.ts index c599cd5bba..cdfa141f57 100644 --- a/acceptance/tests/idp-apple.spec.ts +++ b/acceptance/tests/idp-apple.spec.ts @@ -1,128 +1,94 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; - -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, -}); - // Note for all tests, in case Apple doesn't deliver all relevant information per default // We should add an action in the needed cases -test("login with Apple IDP", async ({user, page}) => { - // Given idp Apple is configured on the organization - // Given the user has Apple added as auth method - // User authenticates with Apple - // User is redirected back to login - // User is redirected to the app +import test from "@playwright/test"; + +test("login with Apple IDP", async ({ page }) => { + // Given an Apple IDP is configured on the organization + // Given the user has an Apple added as auth method + // User authenticates with Apple + // User is redirected back to login + // User is redirected to the app }); - -test("login with Apple IDP - error", async ({user, page}) => { - // Given idp Apple is configure on the organization - // Given the user Apple added as auth method - // User is redirected to Apple - // User authenticates with Apple and gets an error - // User is redirect back to login - // An error is shown to the user "Something went wrong in Apple Login" +test("login with Apple IDP - error", async ({ page }) => { + // Given an Apple IDP is configured on the organization + // Given the user has an Apple added as auth method + // User is redirected to Apple + // User authenticates with Apple and gets an error + // User is redirect back to login + // An error is shown to the user "Something went wrong in Apple Login" }); - - - - -test("login with Apple IDP, no user existing - auto register", async ({user, page}) => { - // Given idp Apple is configure on the organization as only authencation method - // Given idp Apple is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Apple - // User authenticates in Apple - // User is redirect to ZITADEL login - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Apple IDP, no user existing - auto register", async ({ page }) => { + // Given idp Apple is configure on the organization as only authencation method + // Given idp Apple is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Apple + // User authenticates in Apple + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with Apple IDP, no user existing - auto register not possible", async ({user, page}) => { - // Given idp Apple is configure on the organization as only authencation method - // Given idp Apple is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Apple - // User authenticates in Apple - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // User will see the registration page with pre filled user information - // User fills missing information - // User clicks register button - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Apple IDP, no user existing - auto register not possible", async ({ page }) => { + // Given idp Apple is configure on the organization as only authencation method + // Given idp Apple is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Apple + // User authenticates in Apple + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with Apple IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { - // Given idp Apple is configure on the organization as only authencation method - // Given idp Apple is configure with account creation not allowed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Apple - // User authenticates in Apple - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // Error message is shown, that registration of the user was not possible due to missing information +test("login with Apple IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({ + page, +}) => { + // Given idp Apple is configure on the organization as only authencation method + // Given idp Apple is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Apple + // User authenticates in Apple + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information }); -test("login with Apple IDP, no user linked - auto link", async ({user, page}) => { - // Given idp Apple is configure on the organization as only authencation method - // Given idp Apple is configure with account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com exists - - // User is automatically redirected to Apple - // User authenticates in Apple with user@zitadel.com - // User is redirect to ZITADEL login - // User is linked with existing user in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Apple IDP, no user linked - auto link", async ({ page }) => { + // Given idp Apple is configure on the organization as only authencation method + // Given idp Apple is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + // User is automatically redirected to Apple + // User authenticates in Apple with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with Apple IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Apple is configure on the organization as only authencation method - // Given idp Apple is configure with manually account linking not allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Apple - // User authenticates in Apple with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User will get an error message that account linking wasn't possible +test("login with Apple IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Apple is configure on the organization as only authencation method + // Given idp Apple is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Apple + // User authenticates in Apple with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible }); - -test("login with Apple IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Apple is configure on the organization as only authencation method - // Given idp Apple is configure with manually account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Apple - // User authenticates in Apple with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User is prompted to link the account manually - // User is redirected to the app (default redirect url) +test("login with Apple IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Apple is configure on the organization as only authencation method + // Given idp Apple is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Apple + // User authenticates in Apple with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-generic-jwt.spec.ts b/acceptance/tests/idp-generic-jwt.spec.ts index 6ca3558dd6..b8fbee077e 100644 --- a/acceptance/tests/idp-generic-jwt.spec.ts +++ b/acceptance/tests/idp-generic-jwt.spec.ts @@ -1,151 +1,91 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; +import test from "@playwright/test"; -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, +test("login with Generic JWT IDP", async ({ page }) => { + // Given a Generic JWT IDP is configured on the organization + // Given the user has Generic JWT IDP added as auth method + // User authenticates with the Generic JWT IDP + // User is redirected back to login + // User is redirected to the app }); -test("login with Generic JWT IDP - auto redirect", async ({user, page}) => { - // Given idp Generic JWT is configure on the organization as only authencation method - // Given the user has only idp Generic JWT added as auth method - - // User is automatically redirected to Generic JWT - // User authenticates in Generic JWT - // User is redirect to ZITADEL login - // User is redirected to the app (default redirect url) +test("login with Generic JWT IDP - error", async ({ page }) => { + // Given the Generic JWT IDP is configured on the organization + // Given the user has Generic JWT IDP added as auth method + // User is redirected to the Generic JWT IDP + // User authenticates with the Generic JWT IDP and gets an error + // User is redirected back to login + // An error is shown to the user "Something went wrong" }); - -test("login with Generic JWT IDP - auto redirect, error", async ({user, page}) => { - // Given idp Generic JWT is configure on the organization as only authencation method - // Given the user has only idp Generic JWT added as auth method - - // User is automatically redirected to Generic JWT - // User authenticates in Generic JWT and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Generic JWT Login" +test("login with Generic JWT IDP, no user existing - auto register", async ({ page }) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given idp Generic JWT is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with Generic JWT IDP", async ({user, page}) => { - // Given username password and idp Generic JWT is configure on the organization as authencation method - // Given the user has username password and Generic JWT configured - - // Login form shows username field and a Generic JWT Login button - // User clicks on the Generic JWT button - // User is redirected to Generic JWT - // User authenticates in Generic JWT and gets an error - // User is redirect to ZITADEL login automatically - // User is redirected to app automatically (default redirect url) +test("login with Generic JWT IDP, no user existing - auto register not possible", async ({ page }) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given idp Generic JWT is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with Generic JWT IDP, error", async ({user, page}) => { - // Given username password and idp Generic JWT is configure on the organization as authencation method - // Given the user has username password and Generic JWT configured - - // Login form shows username field and a Generic JWT Login button - // User clicks on the Generic JWT button - // User is redirected to Generic JWT - // User authenticates in Generic JWT and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Generic JWT Login" - // User can choose password for authentication +test("login with Generic JWT IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({ + page, +}) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given idp Generic JWT is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information }); -test("login with Generic JWT IDP, no user existing - auto register", async ({user, page}) => { - // Given idp Generic JWT is configure on the organization as only authencation method - // Given idp Generic JWT is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Generic JWT - // User authenticates in Generic JWT - // User is redirect to ZITADEL login - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Generic JWT IDP, no user linked - auto link", async ({ page }) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given idp Generic JWT is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with Generic JWT IDP, no user existing - auto register not possible", async ({user, page}) => { - // Given idp Generic JWT is configure on the organization as only authencation method - // Given idp Generic JWT is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Generic JWT - // User authenticates in Generic JWT - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // User will see the registration page with pre filled user information - // User fills missing information - // User clicks register button - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Generic JWT IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given idp Generic JWT is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible }); -test("login with Generic JWT IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { - // Given idp Generic JWT is configure on the organization as only authencation method - // Given idp Generic JWT is configure with account creation not allowed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Generic JWT - // User authenticates in Generic JWT - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // Error message is shown, that registration of the user was not possible due to missing information -}); - -test("login with Generic JWT IDP, no user linked - auto link", async ({user, page}) => { - // Given idp Generic JWT is configure on the organization as only authencation method - // Given idp Generic JWT is configure with account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com exists - - // User is automatically redirected to Generic JWT - // User authenticates in Generic JWT with user@zitadel.com - // User is redirect to ZITADEL login - // User is linked with existing user in ZITADEL - // User is redirected to the app (default redirect url) -}); - -test("login with Generic JWT IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Generic JWT is configure on the organization as only authencation method - // Given idp Generic JWT is configure with manually account linking not allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Generic JWT - // User authenticates in Generic JWT with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User will get an error message that account linking wasn't possible -}); - - -test("login with Generic JWT IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Generic JWT is configure on the organization as only authencation method - // Given idp Generic JWT is configure with manually account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Generic JWT - // User authenticates in Generic JWT with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User is prompted to link the account manually - // User is redirected to the app (default redirect url) +test("login with Generic JWT IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Generic JWT is configure on the organization as only authencation method + // Given idp Generic JWT is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Generic JWT + // User authenticates in Generic JWT with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-generic-oauth.spec.ts b/acceptance/tests/idp-generic-oauth.spec.ts index 0d55f39973..39e27616e8 100644 --- a/acceptance/tests/idp-generic-oauth.spec.ts +++ b/acceptance/tests/idp-generic-oauth.spec.ts @@ -1,152 +1,91 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; +import test from "@playwright/test"; -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, +test("login with Generic OAuth IDP", async ({ page }) => { + // Given a Generic OAuth IDP is configured on the organization + // Given the user has Generic OAuth IDP added as auth method + // User authenticates with the Generic OAuth IDP + // User is redirected back to login + // User is redirected to the app }); - -test("login with Generic OAuth IDP - auto redirect", async ({user, page}) => { - // Given idp Generic OAuth is configure on the organization as only authencation method - // Given the user has only idp Generic OAuth added as auth method - - // User is automatically redirected to Generic OAuth - // User authenticates in Generic OAuth - // User is redirect to ZITADEL login - // User is redirected to the app (default redirect url) +test("login with Generic OAuth IDP - error", async ({ page }) => { + // Given the Generic OAuth IDP is configured on the organization + // Given the user has Generic OAuth IDP added as auth method + // User is redirected to the Generic OAuth IDP + // User authenticates with the Generic OAuth IDP and gets an error + // User is redirected back to login + // An error is shown to the user "Something went wrong" }); - -test("login with Generic OAuth IDP - auto redirect, error", async ({user, page}) => { - // Given idp Generic OAuth is configure on the organization as only authencation method - // Given the user has only idp Generic OAuth added as auth method - - // User is automatically redirected to Generic OAuth - // User authenticates in Generic OAuth and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Generic OAuth Login" +test("login with Generic OAuth IDP, no user existing - auto register", async ({ page }) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given idp Generic OAuth is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with Generic OAuth IDP", async ({user, page}) => { - // Given username password and idp Generic OAuth is configure on the organization as authencation method - // Given the user has username password and Generic OAuth configured - - // Login form shows username field and a Generic OAuth Login button - // User clicks on the Generic OAuth button - // User is redirected to Generic OAuth - // User authenticates in Generic OAuth and gets an error - // User is redirect to ZITADEL login automatically - // User is redirected to app automatically (default redirect url) +test("login with Generic OAuth IDP, no user existing - auto register not possible", async ({ page }) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given idp Generic OAuth is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with Generic OAuth IDP, error", async ({user, page}) => { - // Given username password and idp Generic OAuth is configure on the organization as authencation method - // Given the user has username password and Generic OAuth configured - - // Login form shows username field and a Generic OAuth Login button - // User clicks on the Generic OAuth button - // User is redirected to Generic OAuth - // User authenticates in Generic OAuth and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Generic OAuth Login" - // User can choose password for authentication +test("login with Generic OAuth IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({ + page, +}) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given idp Generic OAuth is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information }); -test("login with Generic OAuth IDP, no user existing - auto register", async ({user, page}) => { - // Given idp Generic OAuth is configure on the organization as only authencation method - // Given idp Generic OAuth is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Generic OAuth - // User authenticates in Generic OAuth - // User is redirect to ZITADEL login - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Generic OAuth IDP, no user linked - auto link", async ({ page }) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given idp Generic OAuth is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with Generic OAuth IDP, no user existing - auto register not possible", async ({user, page}) => { - // Given idp Generic OAuth is configure on the organization as only authencation method - // Given idp Generic OAuth is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Generic OAuth - // User authenticates in Generic OAuth - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // User will see the registration page with pre filled user information - // User fills missing information - // User clicks register button - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Generic OAuth IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given idp Generic OAuth is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible }); -test("login with Generic OAuth IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { - // Given idp Generic OAuth is configure on the organization as only authencation method - // Given idp Generic OAuth is configure with account creation not allowed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Generic OAuth - // User authenticates in Generic OAuth - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // Error message is shown, that registration of the user was not possible due to missing information -}); - -test("login with Generic OAuth IDP, no user linked - auto link", async ({user, page}) => { - // Given idp Generic OAuth is configure on the organization as only authencation method - // Given idp Generic OAuth is configure with account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com exists - - // User is automatically redirected to Generic OAuth - // User authenticates in Generic OAuth with user@zitadel.com - // User is redirect to ZITADEL login - // User is linked with existing user in ZITADEL - // User is redirected to the app (default redirect url) -}); - -test("login with Generic OAuth IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Generic OAuth is configure on the organization as only authencation method - // Given idp Generic OAuth is configure with manually account linking not allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Generic OAuth - // User authenticates in Generic OAuth with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User will get an error message that account linking wasn't possible -}); - - -test("login with Generic OAuth IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Generic OAuth is configure on the organization as only authencation method - // Given idp Generic OAuth is configure with manually account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Generic OAuth - // User authenticates in Generic OAuth with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User is prompted to link the account manually - // User is redirected to the app (default redirect url) +test("login with Generic OAuth IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Generic OAuth is configure on the organization as only authencation method + // Given idp Generic OAuth is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Generic OAuth + // User authenticates in Generic OAuth with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-generic-oidc.spec.ts b/acceptance/tests/idp-generic-oidc.spec.ts index ed19a50a73..67a0bed9d1 100644 --- a/acceptance/tests/idp-generic-oidc.spec.ts +++ b/acceptance/tests/idp-generic-oidc.spec.ts @@ -1,153 +1,93 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; - -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, -}); - // Note, we should use a provider such as Google to test this, where we know OIDC standard is properly implemented -test("login with Generic OIDC IDP - auto redirect", async ({user, page}) => { - // Given idp Generic OIDC is configure on the organization as only authencation method - // Given the user has only idp Generic OIDC added as auth method +import test from "@playwright/test"; - // User is automatically redirected to Generic OIDC - // User authenticates in Generic OIDC - // User is redirect to ZITADEL login - // User is redirected to the app (default redirect url) +test("login with Generic OIDC IDP", async ({ page }) => { + // Given a Generic OIDC IDP is configured on the organization + // Given the user has Generic OIDC IDP added as auth method + // User authenticates with the Generic OIDC IDP + // User is redirected back to login + // User is redirected to the app }); - -test("login with Generic OIDC IDP - auto redirect, error", async ({user, page}) => { - // Given idp Generic OIDC is configure on the organization as only authencation method - // Given the user has only idp Generic OIDC added as auth method - - // User is automatically redirected to Generic OIDC - // User authenticates in Generic OIDC and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Generic OIDC Login" +test("login with Generic OIDC IDP - error", async ({ page }) => { + // Given the Generic OIDC IDP is configured on the organization + // Given the user has Generic OIDC IDP added as auth method + // User is redirected to the Generic OIDC IDP + // User authenticates with the Generic OIDC IDP and gets an error + // User is redirected back to login + // An error is shown to the user "Something went wrong" }); - -test("login with Generic OIDC IDP", async ({user, page}) => { - // Given username password and idp Generic OIDC is configure on the organization as authencation method - // Given the user has username password and Generic OIDC configured - - // Login form shows username field and a Generic OIDC Login button - // User clicks on the Generic OIDC button - // User is redirected to Generic OIDC - // User authenticates in Generic OIDC and gets an error - // User is redirect to ZITADEL login automatically - // User is redirected to app automatically (default redirect url) +test("login with Generic OIDC IDP, no user existing - auto register", async ({ page }) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given idp Generic OIDC is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with Generic OIDC IDP, error", async ({user, page}) => { - // Given username password and idp Generic OIDC is configure on the organization as authencation method - // Given the user has username password and Generic OIDC configured - - // Login form shows username field and a Generic OIDC Login button - // User clicks on the Generic OIDC button - // User is redirected to Generic OIDC - // User authenticates in Generic OIDC and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Generic OIDC Login" - // User can choose password for authentication +test("login with Generic OIDC IDP, no user existing - auto register not possible", async ({ page }) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given idp Generic OIDC is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with Generic OIDC IDP, no user existing - auto register", async ({user, page}) => { - // Given idp Generic OIDC is configure on the organization as only authencation method - // Given idp Generic OIDC is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Generic OIDC - // User authenticates in Generic OIDC - // User is redirect to ZITADEL login - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Generic OIDC IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({ + page, +}) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given idp Generic OIDC is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information }); -test("login with Generic OIDC IDP, no user existing - auto register not possible", async ({user, page}) => { - // Given idp Generic OIDC is configure on the organization as only authencation method - // Given idp Generic OIDC is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Generic OIDC - // User authenticates in Generic OIDC - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // User will see the registration page with pre filled user information - // User fills missing information - // User clicks register button - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Generic OIDC IDP, no user linked - auto link", async ({ page }) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given idp Generic OIDC is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with Generic OIDC IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { - // Given idp Generic OIDC is configure on the organization as only authencation method - // Given idp Generic OIDC is configure with account creation not allowed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Generic OIDC - // User authenticates in Generic OIDC - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // Error message is shown, that registration of the user was not possible due to missing information +test("login with Generic OIDC IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given idp Generic OIDC is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible }); -test("login with Generic OIDC IDP, no user linked - auto link", async ({user, page}) => { - // Given idp Generic OIDC is configure on the organization as only authencation method - // Given idp Generic OIDC is configure with account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com exists - - // User is automatically redirected to Generic OIDC - // User authenticates in Generic OIDC with user@zitadel.com - // User is redirect to ZITADEL login - // User is linked with existing user in ZITADEL - // User is redirected to the app (default redirect url) -}); - -test("login with Generic OIDC IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Generic OIDC is configure on the organization as only authencation method - // Given idp Generic OIDC is configure with manually account linking not allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Generic OIDC - // User authenticates in Generic OIDC with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User will get an error message that account linking wasn't possible -}); - - -test("login with Generic OIDC IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Generic OIDC is configure on the organization as only authencation method - // Given idp Generic OIDC is configure with manually account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Generic OIDC - // User authenticates in Generic OIDC with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User is prompted to link the account manually - // User is redirected to the app (default redirect url) +test("login with Generic OIDC IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Generic OIDC is configure on the organization as only authencation method + // Given idp Generic OIDC is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Generic OIDC + // User authenticates in Generic OIDC with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-github-enterprise.spec.ts b/acceptance/tests/idp-github-enterprise.spec.ts index ee7f5d8a5b..871fde4be0 100644 --- a/acceptance/tests/idp-github-enterprise.spec.ts +++ b/acceptance/tests/idp-github-enterprise.spec.ts @@ -1,156 +1,95 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; +import test from "@playwright/test"; -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, +test("login with GitHub Enterprise IDP", async ({ page }) => { + // Given a GitHub Enterprise IDP is configured on the organization + // Given the user has GitHub Enterprise IDP added as auth method + // User authenticates with the GitHub Enterprise IDP + // User is redirected back to login + // User is redirected to the app }); - -test("login with GitHub Enterprise IDP - auto redirect", async ({user, page}) => { - // Given idp GitHub Enterprise is configure on the organization as only authencation method - // Given the user has only idp GitHub Enterprise added as auth method - - // User is automatically redirected to GitHub Enterprise - // User authenticates in GitHub Enterprise - // User is redirect to ZITADEL login - // User is redirected to the app (default redirect url) +test("login with GitHub Enterprise IDP - error", async ({ page }) => { + // Given the GitHub Enterprise IDP is configured on the organization + // Given the user has GitHub Enterprise IDP added as auth method + // User is redirected to the GitHub Enterprise IDP + // User authenticates with the GitHub Enterprise IDP and gets an error + // User is redirected back to login + // An error is shown to the user "Something went wrong" }); - -test("login with GitHub Enterprise IDP - auto redirect, error", async ({user, page}) => { - // Given idp GitHub Enterprise is configure on the organization as only authencation method - // Given the user has only idp GitHub Enterprise added as auth method - - // User is automatically redirected to GitHub Enterprise - // User authenticates in GitHub Enterprise and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in GitHub Enterprise Login" +test("login with GitHub Enterprise IDP, no user existing - auto register", async ({ page }) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given idp GitHub Enterprise is configure with account creation alloweed, and automatic creation enabled + // Given ZITADEL Action is added to autofill missing user information + // Given no user exists yet + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with GitHub Enterprise IDP", async ({user, page}) => { - // Given username password and idp GitHub Enterprise is configure on the organization as authencation method - // Given the user has username password and GitHub Enterprise configured - - // Login form shows username field and a GitHub Enterprise Login button - // User clicks on the GitHub Enterprise button - // User is redirected to GitHub Enterprise - // User authenticates in GitHub Enterprise and gets an error - // User is redirect to ZITADEL login automatically - // User is redirected to app automatically (default redirect url) +test("login with GitHub Enterprise IDP, no user existing - auto register not possible", async ({ page }) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given idp GitHub Enterprise is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with GitHub Enterprise IDP, error", async ({user, page}) => { - // Given username password and idp GitHub Enterprise is configure on the organization as authencation method - // Given the user has username password and GitHub Enterprise configured - - // Login form shows username field and a GitHub Enterprise Login button - // User clicks on the GitHub Enterprise button - // User is redirected to GitHub Enterprise - // User authenticates in GitHub Enterprise and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in GitHub Enterprise Login" - // User can choose password for authentication +test("login with GitHub Enterprise IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({ + page, +}) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given idp GitHub Enterprise is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information }); -test("login with GitHub Enterprise IDP, no user existing - auto register", async ({user, page}) => { - // Given idp GitHub Enterprise is configure on the organization as only authencation method - // Given idp GitHub Enterprise is configure with account creation alloweed, and automatic creation enabled - // Given ZITADEL Action is added to autofill missing user information - // Given no user exists yet - - // User is automatically redirected to GitHub Enterprise - // User authenticates in GitHub Enterprise - // User is redirect to ZITADEL login - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with GitHub Enterprise IDP, no user linked - auto link", async ({ page }) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given idp GitHub Enterprise is configure with account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com exists + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with GitHub Enterprise IDP, no user existing - auto register not possible", async ({user, page}) => { - // Given idp GitHub Enterprise is configure on the organization as only authencation method - // Given idp GitHub Enterprise is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to GitHub Enterprise - // User authenticates in GitHub Enterprise - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // User will see the registration page with pre filled user information - // User fills missing information - // User clicks register button - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with GitHub Enterprise IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given idp GitHub Enterprise is configure with manually account linking not allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible }); -test("login with GitHub Enterprise IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { - // Given idp GitHub Enterprise is configure on the organization as only authencation method - // Given idp GitHub Enterprise is configure with account creation not allowed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to GitHub Enterprise - // User authenticates in GitHub Enterprise - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // Error message is shown, that registration of the user was not possible due to missing information -}); - -test("login with GitHub Enterprise IDP, no user linked - auto link", async ({user, page}) => { - // Given idp GitHub Enterprise is configure on the organization as only authencation method - // Given idp GitHub Enterprise is configure with account linking allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com exists - - // User is automatically redirected to GitHub Enterprise - // User authenticates in GitHub Enterprise with user@zitadel.com - // User is redirect to ZITADEL login - // User is linked with existing user in ZITADEL - // User is redirected to the app (default redirect url) -}); - -test("login with GitHub Enterprise IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp GitHub Enterprise is configure on the organization as only authencation method - // Given idp GitHub Enterprise is configure with manually account linking not allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to GitHub Enterprise - // User authenticates in GitHub Enterprise with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User will get an error message that account linking wasn't possible -}); - - -test("login with GitHub Enterprise IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp GitHub Enterprise is configure on the organization as only authencation method - // Given idp GitHub Enterprise is configure with manually account linking allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to GitHub Enterprise - // User authenticates in GitHub Enterprise with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User is prompted to link the account manually - // User is redirected to the app (default redirect url) +test("login with GitHub Enterprise IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp GitHub Enterprise is configure on the organization as only authencation method + // Given idp GitHub Enterprise is configure with manually account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to GitHub Enterprise + // User authenticates in GitHub Enterprise with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-github.spec.ts b/acceptance/tests/idp-github.spec.ts index 0ac8c13193..3a1df36a67 100644 --- a/acceptance/tests/idp-github.spec.ts +++ b/acceptance/tests/idp-github.spec.ts @@ -1,156 +1,95 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; +import test from "@playwright/test"; -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, +test("login with GitHub IDP", async ({ page }) => { + // Given a GitHub IDP is configured on the organization + // Given the user has GitHub IDP added as auth method + // User authenticates with the GitHub IDP + // User is redirected back to login + // User is redirected to the app }); - -test("login with GitHub IDP - auto redirect", async ({user, page}) => { - // Given idp GitHub is configure on the organization as only authencation method - // Given the user has only idp GitHub added as auth method - - // User is automatically redirected to GitHub - // User authenticates in GitHub - // User is redirect to ZITADEL login - // User is redirected to the app (default redirect url) +test("login with GitHub IDP - error", async ({ page }) => { + // Given the GitHub IDP is configured on the organization + // Given the user has GitHub IDP added as auth method + // User is redirected to the GitHub IDP + // User authenticates with the GitHub IDP and gets an error + // User is redirected back to login + // An error is shown to the user "Something went wrong" }); - -test("login with GitHub IDP - auto redirect, error", async ({user, page}) => { - // Given idp GitHub is configure on the organization as only authencation method - // Given the user has only idp GitHub added as auth method - - // User is automatically redirected to GitHub - // User authenticates in GitHub and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in GitHub Login" +test("login with GitHub IDP, no user existing - auto register", async ({ page }) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given idp GitHub is configure with account creation alloweed, and automatic creation enabled + // Given ZITADEL Action is added to autofill missing user information + // Given no user exists yet + // User is automatically redirected to GitHub + // User authenticates in GitHub + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with GitHub IDP", async ({user, page}) => { - // Given username password and idp GitHub is configure on the organization as authencation method - // Given the user has username password and GitHub configured - - // Login form shows username field and a GitHub Login button - // User clicks on the GitHub button - // User is redirected to GitHub - // User authenticates in GitHub and gets an error - // User is redirect to ZITADEL login automatically - // User is redirected to app automatically (default redirect url) +test("login with GitHub IDP, no user existing - auto register not possible", async ({ page }) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given idp GitHub is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to GitHub + // User authenticates in GitHub + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with GitHub IDP, error", async ({user, page}) => { - // Given username password and idp GitHub is configure on the organization as authencation method - // Given the user has username password and GitHub configured - - // Login form shows username field and a GitHub Login button - // User clicks on the GitHub button - // User is redirected to GitHub - // User authenticates in GitHub and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in GitHub Login" - // User can choose password for authentication +test("login with GitHub IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({ + page, +}) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given idp GitHub is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to GitHub + // User authenticates in GitHub + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information }); -test("login with GitHub IDP, no user existing - auto register", async ({user, page}) => { - // Given idp GitHub is configure on the organization as only authencation method - // Given idp GitHub is configure with account creation alloweed, and automatic creation enabled - // Given ZITADEL Action is added to autofill missing user information - // Given no user exists yet - - // User is automatically redirected to GitHub - // User authenticates in GitHub - // User is redirect to ZITADEL login - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with GitHub IDP, no user linked - auto link", async ({ page }) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given idp GitHub is configure with account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com exists + // User is automatically redirected to GitHub + // User authenticates in GitHub with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with GitHub IDP, no user existing - auto register not possible", async ({user, page}) => { - // Given idp GitHub is configure on the organization as only authencation method - // Given idp GitHub is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to GitHub - // User authenticates in GitHub - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // User will see the registration page with pre filled user information - // User fills missing information - // User clicks register button - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with GitHub IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given idp GitHub is configure with manually account linking not allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to GitHub + // User authenticates in GitHub with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible }); -test("login with GitHub IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { - // Given idp GitHub is configure on the organization as only authencation method - // Given idp GitHub is configure with account creation not allowed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to GitHub - // User authenticates in GitHub - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // Error message is shown, that registration of the user was not possible due to missing information -}); - -test("login with GitHub IDP, no user linked - auto link", async ({user, page}) => { - // Given idp GitHub is configure on the organization as only authencation method - // Given idp GitHub is configure with account linking allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com exists - - // User is automatically redirected to GitHub - // User authenticates in GitHub with user@zitadel.com - // User is redirect to ZITADEL login - // User is linked with existing user in ZITADEL - // User is redirected to the app (default redirect url) -}); - -test("login with GitHub IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp GitHub is configure on the organization as only authencation method - // Given idp GitHub is configure with manually account linking not allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to GitHub - // User authenticates in GitHub with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User will get an error message that account linking wasn't possible -}); - - -test("login with GitHub IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp GitHub is configure on the organization as only authencation method - // Given idp GitHub is configure with manually account linking allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to GitHub - // User authenticates in GitHub with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User is prompted to link the account manually - // User is redirected to the app (default redirect url) +test("login with GitHub IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp GitHub is configure on the organization as only authencation method + // Given idp GitHub is configure with manually account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to GitHub + // User authenticates in GitHub with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-gitlab-self-hosted.spec.ts b/acceptance/tests/idp-gitlab-self-hosted.spec.ts index 00b0ec7c4a..7864fa45c8 100644 --- a/acceptance/tests/idp-gitlab-self-hosted.spec.ts +++ b/acceptance/tests/idp-gitlab-self-hosted.spec.ts @@ -1,156 +1,95 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; +import test from "@playwright/test"; -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, +test("login with GitLab Self-Hosted IDP", async ({ page }) => { + // Given a GitLab Self-Hosted IDP is configured on the organization + // Given the user has GitLab Self-Hosted IDP added as auth method + // User authenticates with the GitLab Self-Hosted IDP + // User is redirected back to login + // User is redirected to the app }); - -test("login with Gitlab Self-Hosted IDP - auto redirect", async ({user, page}) => { - // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method - // Given the user has only idp Gitlab Self-Hosted added as auth method - - // User is automatically redirected to Gitlab Self-Hosted - // User authenticates in Gitlab Self-Hosted - // User is redirect to ZITADEL login - // User is redirected to the app (default redirect url) +test("login with GitLab Self-Hosted IDP - error", async ({ page }) => { + // Given the GitLab Self-Hosted IDP is configured on the organization + // Given the user has GitLab Self-Hosted IDP added as auth method + // User is redirected to the GitLab Self-Hosted IDP + // User authenticates with the GitLab Self-Hosted IDP and gets an error + // User is redirected back to login + // An error is shown to the user "Something went wrong" }); - -test("login with Gitlab Self-Hosted IDP - auto redirect, error", async ({user, page}) => { - // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method - // Given the user has only idp Gitlab Self-Hosted added as auth method - - // User is automatically redirected to Gitlab Self-Hosted - // User authenticates in Gitlab Self-Hosted and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Gitlab Self-Hosted Login" +test("login with Gitlab Self-Hosted IDP, no user existing - auto register", async ({ page }) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given idp Gitlab Self-Hosted is configure with account creation alloweed, and automatic creation enabled + // Given ZITADEL Action is added to autofill missing user information + // Given no user exists yet + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with Gitlab Self-Hosted IDP", async ({user, page}) => { - // Given username password and idp Gitlab Self-Hosted is configure on the organization as authencation method - // Given the user has username password and Gitlab Self-Hosted configured - - // Login form shows username field and a Gitlab Self-Hosted Login button - // User clicks on the Gitlab Self-Hosted button - // User is redirected to Gitlab Self-Hosted - // User authenticates in Gitlab Self-Hosted and gets an error - // User is redirect to ZITADEL login automatically - // User is redirected to app automatically (default redirect url) +test("login with Gitlab Self-Hosted IDP, no user existing - auto register not possible", async ({ page }) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given idp Gitlab Self-Hosted is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with Gitlab Self-Hosted IDP, error", async ({user, page}) => { - // Given username password and idp Gitlab Self-Hosted is configure on the organization as authencation method - // Given the user has username password and Gitlab Self-Hosted configured - - // Login form shows username field and a Gitlab Self-Hosted Login button - // User clicks on the Gitlab Self-Hosted button - // User is redirected to Gitlab Self-Hosted - // User authenticates in Gitlab Self-Hosted and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Gitlab Self-Hosted Login" - // User can choose password for authentication +test("login with Gitlab Self-Hosted IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({ + page, +}) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given idp Gitlab Self-Hosted is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information }); -test("login with Gitlab Self-Hosted IDP, no user existing - auto register", async ({user, page}) => { - // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method - // Given idp Gitlab Self-Hosted is configure with account creation alloweed, and automatic creation enabled - // Given ZITADEL Action is added to autofill missing user information - // Given no user exists yet - - // User is automatically redirected to Gitlab Self-Hosted - // User authenticates in Gitlab Self-Hosted - // User is redirect to ZITADEL login - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Gitlab Self-Hosted IDP, no user linked - auto link", async ({ page }) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given idp Gitlab Self-Hosted is configure with account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com exists + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with Gitlab Self-Hosted IDP, no user existing - auto register not possible", async ({user, page}) => { - // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method - // Given idp Gitlab Self-Hosted is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Gitlab Self-Hosted - // User authenticates in Gitlab Self-Hosted - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // User will see the registration page with pre filled user information - // User fills missing information - // User clicks register button - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Gitlab Self-Hosted IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given idp Gitlab Self-Hosted is configure with manually account linking not allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible }); -test("login with Gitlab Self-Hosted IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { - // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method - // Given idp Gitlab Self-Hosted is configure with account creation not allowed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Gitlab Self-Hosted - // User authenticates in Gitlab Self-Hosted - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // Error message is shown, that registration of the user was not possible due to missing information -}); - -test("login with Gitlab Self-Hosted IDP, no user linked - auto link", async ({user, page}) => { - // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method - // Given idp Gitlab Self-Hosted is configure with account linking allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com exists - - // User is automatically redirected to Gitlab Self-Hosted - // User authenticates in Gitlab Self-Hosted with user@zitadel.com - // User is redirect to ZITADEL login - // User is linked with existing user in ZITADEL - // User is redirected to the app (default redirect url) -}); - -test("login with Gitlab Self-Hosted IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method - // Given idp Gitlab Self-Hosted is configure with manually account linking not allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Gitlab Self-Hosted - // User authenticates in Gitlab Self-Hosted with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User will get an error message that account linking wasn't possible -}); - - -test("login with Gitlab Self-Hosted IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method - // Given idp Gitlab Self-Hosted is configure with manually account linking allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Gitlab Self-Hosted - // User authenticates in Gitlab Self-Hosted with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User is prompted to link the account manually - // User is redirected to the app (default redirect url) +test("login with Gitlab Self-Hosted IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method + // Given idp Gitlab Self-Hosted is configure with manually account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Gitlab Self-Hosted + // User authenticates in Gitlab Self-Hosted with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-gitlab.spec.ts b/acceptance/tests/idp-gitlab.spec.ts index a94b1ac77d..5b2483ac02 100644 --- a/acceptance/tests/idp-gitlab.spec.ts +++ b/acceptance/tests/idp-gitlab.spec.ts @@ -1,156 +1,95 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; +import test from "@playwright/test"; -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, +test("login with GitLab IDP", async ({ page }) => { + // Given a GitLab IDP is configured on the organization + // Given the user has GitLab IDP added as auth method + // User authenticates with the GitLab IDP + // User is redirected back to login + // User is redirected to the app }); - -test("login with Gitlab IDP - auto redirect", async ({user, page}) => { - // Given idp Gitlab is configure on the organization as only authencation method - // Given the user has only idp Gitlab added as auth method - - // User is automatically redirected to Gitlab - // User authenticates in Gitlab - // User is redirect to ZITADEL login - // User is redirected to the app (default redirect url) +test("login with GitLab IDP - error", async ({ page }) => { + // Given the GitLab IDP is configured on the organization + // Given the user has GitLab IDP added as auth method + // User is redirected to the GitLab IDP + // User authenticates with the GitLab IDP and gets an error + // User is redirected back to login + // An error is shown to the user "Something went wrong" }); - -test("login with Gitlab IDP - auto redirect, error", async ({user, page}) => { - // Given idp Gitlab is configure on the organization as only authencation method - // Given the user has only idp Gitlab added as auth method - - // User is automatically redirected to Gitlab - // User authenticates in Gitlab and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Gitlab Login" +test("login with Gitlab IDP, no user existing - auto register", async ({ page }) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given idp Gitlab is configure with account creation alloweed, and automatic creation enabled + // Given ZITADEL Action is added to autofill missing user information + // Given no user exists yet + // User is automatically redirected to Gitlab + // User authenticates in Gitlab + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with Gitlab IDP", async ({user, page}) => { - // Given username password and idp Gitlab is configure on the organization as authencation method - // Given the user has username password and Gitlab configured - - // Login form shows username field and a Gitlab Login button - // User clicks on the Gitlab button - // User is redirected to Gitlab - // User authenticates in Gitlab and gets an error - // User is redirect to ZITADEL login automatically - // User is redirected to app automatically (default redirect url) +test("login with Gitlab IDP, no user existing - auto register not possible", async ({ page }) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given idp Gitlab is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Gitlab + // User authenticates in Gitlab + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with Gitlab IDP, error", async ({user, page}) => { - // Given username password and idp Gitlab is configure on the organization as authencation method - // Given the user has username password and Gitlab configured - - // Login form shows username field and a Gitlab Login button - // User clicks on the Gitlab button - // User is redirected to Gitlab - // User authenticates in Gitlab and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Gitlab Login" - // User can choose password for authentication +test("login with Gitlab IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({ + page, +}) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given idp Gitlab is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Gitlab + // User authenticates in Gitlab + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information }); -test("login with Gitlab IDP, no user existing - auto register", async ({user, page}) => { - // Given idp Gitlab is configure on the organization as only authencation method - // Given idp Gitlab is configure with account creation alloweed, and automatic creation enabled - // Given ZITADEL Action is added to autofill missing user information - // Given no user exists yet - - // User is automatically redirected to Gitlab - // User authenticates in Gitlab - // User is redirect to ZITADEL login - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Gitlab IDP, no user linked - auto link", async ({ page }) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given idp Gitlab is configure with account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com exists + // User is automatically redirected to Gitlab + // User authenticates in Gitlab with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with Gitlab IDP, no user existing - auto register not possible", async ({user, page}) => { - // Given idp Gitlab is configure on the organization as only authencation method - // Given idp Gitlab is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Gitlab - // User authenticates in Gitlab - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // User will see the registration page with pre filled user information - // User fills missing information - // User clicks register button - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Gitlab IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given idp Gitlab is configure with manually account linking not allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Gitlab + // User authenticates in Gitlab with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible }); -test("login with Gitlab IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { - // Given idp Gitlab is configure on the organization as only authencation method - // Given idp Gitlab is configure with account creation not allowed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Gitlab - // User authenticates in Gitlab - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // Error message is shown, that registration of the user was not possible due to missing information -}); - -test("login with Gitlab IDP, no user linked - auto link", async ({user, page}) => { - // Given idp Gitlab is configure on the organization as only authencation method - // Given idp Gitlab is configure with account linking allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com exists - - // User is automatically redirected to Gitlab - // User authenticates in Gitlab with user@zitadel.com - // User is redirect to ZITADEL login - // User is linked with existing user in ZITADEL - // User is redirected to the app (default redirect url) -}); - -test("login with Gitlab IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Gitlab is configure on the organization as only authencation method - // Given idp Gitlab is configure with manually account linking not allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Gitlab - // User authenticates in Gitlab with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User will get an error message that account linking wasn't possible -}); - - -test("login with Gitlab IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Gitlab is configure on the organization as only authencation method - // Given idp Gitlab is configure with manually account linking allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Gitlab - // User authenticates in Gitlab with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User is prompted to link the account manually - // User is redirected to the app (default redirect url) +test("login with Gitlab IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Gitlab is configure on the organization as only authencation method + // Given idp Gitlab is configure with manually account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Gitlab + // User authenticates in Gitlab with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-google.spec.ts b/acceptance/tests/idp-google.spec.ts index 8c13449add..e4d0dc1367 100644 --- a/acceptance/tests/idp-google.spec.ts +++ b/acceptance/tests/idp-google.spec.ts @@ -1,151 +1,91 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; +import test from "@playwright/test"; -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, +test("login with Google IDP", async ({ page }) => { + // Given a Google IDP is configured on the organization + // Given the user has Google IDP added as auth method + // User authenticates with the Google IDP + // User is redirected back to login + // User is redirected to the app }); -test("login with Google IDP - auto redirect", async ({user, page}) => { - // Given idp Google is configure on the organization as only authencation method - // Given the user has only idp Google added as auth method - - // User is automatically redirected to Google - // User authenticates in Google - // User is redirect to ZITADEL login - // User is redirected to the app (default redirect url) +test("login with Google IDP - error", async ({ page }) => { + // Given the Google IDP is configured on the organization + // Given the user has Google IDP added as auth method + // User is redirected to the Google IDP + // User authenticates with the Google IDP and gets an error + // User is redirected back to login + // An error is shown to the user "Something went wrong" }); - -test("login with Google IDP - auto redirect, error", async ({user, page}) => { - // Given idp Google is configure on the organization as only authencation method - // Given the user has only idp Google added as auth method - - // User is automatically redirected to Google - // User authenticates in Google and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Google Login" +test("login with Google IDP, no user existing - auto register", async ({ page }) => { + // Given idp Google is configure on the organization as only authencation method + // Given idp Google is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Google + // User authenticates in Google + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with Google IDP", async ({user, page}) => { - // Given username password and idp Google is configure on the organization as authencation method - // Given the user has username password and Google configured - - // Login form shows username field and a Google Login button - // User clicks on the Google button - // User is redirected to Google - // User authenticates in Google and gets an error - // User is redirect to ZITADEL login automatically - // User is redirected to app automatically (default redirect url) +test("login with Google IDP, no user existing - auto register not possible", async ({ page }) => { + // Given idp Google is configure on the organization as only authencation method + // Given idp Google is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Google + // User authenticates in Google + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with Google IDP, error", async ({user, page}) => { - // Given username password and idp Google is configure on the organization as authencation method - // Given the user has username password and Google configured - - // Login form shows username field and a Google Login button - // User clicks on the Google button - // User is redirected to Google - // User authenticates in Google and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Google Login" - // User can choose password for authentication +test("login with Google IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({ + page, +}) => { + // Given idp Google is configure on the organization as only authencation method + // Given idp Google is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Google + // User authenticates in Google + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information }); -test("login with Google IDP, no user existing - auto register", async ({user, page}) => { - // Given idp Google is configure on the organization as only authencation method - // Given idp Google is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Google - // User authenticates in Google - // User is redirect to ZITADEL login - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Google IDP, no user linked - auto link", async ({ page }) => { + // Given idp Google is configure on the organization as only authencation method + // Given idp Google is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + // User is automatically redirected to Google + // User authenticates in Google with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with Google IDP, no user existing - auto register not possible", async ({user, page}) => { - // Given idp Google is configure on the organization as only authencation method - // Given idp Google is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Google - // User authenticates in Google - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // User will see the registration page with pre filled user information - // User fills missing information - // User clicks register button - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Google IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Google is configure on the organization as only authencation method + // Given idp Google is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Google + // User authenticates in Google with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible }); -test("login with Google IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { - // Given idp Google is configure on the organization as only authencation method - // Given idp Google is configure with account creation not allowed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Google - // User authenticates in Google - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // Error message is shown, that registration of the user was not possible due to missing information -}); - -test("login with Google IDP, no user linked - auto link", async ({user, page}) => { - // Given idp Google is configure on the organization as only authencation method - // Given idp Google is configure with account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com exists - - // User is automatically redirected to Google - // User authenticates in Google with user@zitadel.com - // User is redirect to ZITADEL login - // User is linked with existing user in ZITADEL - // User is redirected to the app (default redirect url) -}); - -test("login with Google IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Google is configure on the organization as only authencation method - // Given idp Google is configure with manually account linking not allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Google - // User authenticates in Google with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User will get an error message that account linking wasn't possible -}); - - -test("login with Google IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Google is configure on the organization as only authencation method - // Given idp Google is configure with manually account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Google - // User authenticates in Google with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User is prompted to link the account manually - // User is redirected to the app (default redirect url) +test("login with Google IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Google is configure on the organization as only authencation method + // Given idp Google is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Google + // User authenticates in Google with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-ldap.spec.ts b/acceptance/tests/idp-ldap.spec.ts index fc667edee4..dc4018f015 100644 --- a/acceptance/tests/idp-ldap.spec.ts +++ b/acceptance/tests/idp-ldap.spec.ts @@ -1,151 +1,91 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; +import test from "@playwright/test"; -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, +test("login with LDAP IDP", async ({ page }) => { + // Given a LDAP IDP is configured on the organization + // Given the user has LDAP IDP added as auth method + // User authenticates with the LDAP IDP + // User is redirected back to login + // User is redirected to the app }); -test("login with LDAP IDP - auto redirect", async ({user, page}) => { - // Given idp LDAP is configure on the organization as only authencation method - // Given the user has only idp LDAP added as auth method - - // User is automatically redirected to LDAP - // User authenticates in LDAP - // User is redirect to ZITADEL login - // User is redirected to the app (default redirect url) +test("login with LDAP IDP - error", async ({ page }) => { + // Given the LDAP IDP is configured on the organization + // Given the user has LDAP IDP added as auth method + // User is redirected to the LDAP IDP + // User authenticates with the LDAP IDP and gets an error + // User is redirected back to login + // An error is shown to the user "Something went wrong" }); - -test("login with LDAP IDP - auto redirect, error", async ({user, page}) => { - // Given idp LDAP is configure on the organization as only authencation method - // Given the user has only idp LDAP added as auth method - - // User is automatically redirected to LDAP - // User authenticates in LDAP and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in LDAP Login" +test("login with LDAP IDP, no user existing - auto register", async ({ page }) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given idp LDAP is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to LDAP + // User authenticates in LDAP + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with LDAP IDP", async ({user, page}) => { - // Given username password and idp LDAP is configure on the organization as authencation method - // Given the user has username password and LDAP configured - - // Login form shows username field and a LDAP Login button - // User clicks on the LDAP button - // User is redirected to LDAP - // User authenticates in LDAP and gets an error - // User is redirect to ZITADEL login automatically - // User is redirected to app automatically (default redirect url) +test("login with LDAP IDP, no user existing - auto register not possible", async ({ page }) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given idp LDAP is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to LDAP + // User authenticates in LDAP + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with LDAP IDP, error", async ({user, page}) => { - // Given username password and idp LDAP is configure on the organization as authencation method - // Given the user has username password and LDAP configured - - // Login form shows username field and a LDAP Login button - // User clicks on the LDAP button - // User is redirected to LDAP - // User authenticates in LDAP and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in LDAP Login" - // User can choose password for authentication +test("login with LDAP IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({ + page, +}) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given idp LDAP is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to LDAP + // User authenticates in LDAP + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information }); -test("login with LDAP IDP, no user existing - auto register", async ({user, page}) => { - // Given idp LDAP is configure on the organization as only authencation method - // Given idp LDAP is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to LDAP - // User authenticates in LDAP - // User is redirect to ZITADEL login - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with LDAP IDP, no user linked - auto link", async ({ page }) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given idp LDAP is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + // User is automatically redirected to LDAP + // User authenticates in LDAP with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with LDAP IDP, no user existing - auto register not possible", async ({user, page}) => { - // Given idp LDAP is configure on the organization as only authencation method - // Given idp LDAP is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to LDAP - // User authenticates in LDAP - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // User will see the registration page with pre filled user information - // User fills missing information - // User clicks register button - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with LDAP IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given idp LDAP is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to LDAP + // User authenticates in LDAP with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible }); -test("login with LDAP IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { - // Given idp LDAP is configure on the organization as only authencation method - // Given idp LDAP is configure with account creation not allowed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to LDAP - // User authenticates in LDAP - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // Error message is shown, that registration of the user was not possible due to missing information -}); - -test("login with LDAP IDP, no user linked - auto link", async ({user, page}) => { - // Given idp LDAP is configure on the organization as only authencation method - // Given idp LDAP is configure with account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com exists - - // User is automatically redirected to LDAP - // User authenticates in LDAP with user@zitadel.com - // User is redirect to ZITADEL login - // User is linked with existing user in ZITADEL - // User is redirected to the app (default redirect url) -}); - -test("login with LDAP IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp LDAP is configure on the organization as only authencation method - // Given idp LDAP is configure with manually account linking not allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to LDAP - // User authenticates in LDAP with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User will get an error message that account linking wasn't possible -}); - - -test("login with LDAP IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp LDAP is configure on the organization as only authencation method - // Given idp LDAP is configure with manually account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to LDAP - // User authenticates in LDAP with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User is prompted to link the account manually - // User is redirected to the app (default redirect url) +test("login with LDAP IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp LDAP is configure on the organization as only authencation method + // Given idp LDAP is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to LDAP + // User authenticates in LDAP with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-microsoft.spec.ts b/acceptance/tests/idp-microsoft.spec.ts index 26ffcc1064..9cfb5730de 100644 --- a/acceptance/tests/idp-microsoft.spec.ts +++ b/acceptance/tests/idp-microsoft.spec.ts @@ -1,154 +1,94 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; - -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, -}); - // Note for all tests, in case Microsoft doesn't deliver all relevant information per default // We should add an action in the needed cases -test("login with Microsoft IDP - auto redirect", async ({user, page}) => { - // Given idp Microsoft is configure on the organization as only authencation method - // Given the user has only idp Microsoft added as auth method +import test from "@playwright/test"; - // User is automatically redirected to Microsoft - // User authenticates in Microsoft - // User is redirect to ZITADEL login - // User is redirected to the app (default redirect url) +test("login with Microsoft IDP", async ({ page }) => { + // Given a Microsoft IDP is configured on the organization + // Given the user has Microsoft IDP added as auth method + // User authenticates with the Microsoft IDP + // User is redirected back to login + // User is redirected to the app }); - -test("login with Microsoft IDP - auto redirect, error", async ({user, page}) => { - // Given idp Microsoft is configure on the organization as only authencation method - // Given the user has only idp Microsoft added as auth method - - // User is automatically redirected to Microsoft - // User authenticates in Microsoft and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Microsoft Login" +test("login with Microsoft IDP - error", async ({ page }) => { + // Given the Microsoft IDP is configured on the organization + // Given the user has Microsoft IDP added as auth method + // User is redirected to the Microsoft IDP + // User authenticates with the Microsoft IDP and gets an error + // User is redirected back to login + // An error is shown to the user "Something went wrong" }); - -test("login with Microsoft IDP", async ({user, page}) => { - // Given username password and idp Microsoft is configure on the organization as authencation method - // Given the user has username password and Microsoft configured - - // Login form shows username field and a Microsoft Login button - // User clicks on the Microsoft button - // User is redirected to Microsoft - // User authenticates in Microsoft and gets an error - // User is redirect to ZITADEL login automatically - // User is redirected to app automatically (default redirect url) +test("login with Microsoft IDP, no user existing - auto register", async ({ page }) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given idp Microsoft is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Microsoft + // User authenticates in Microsoft + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with Microsoft IDP, error", async ({user, page}) => { - // Given username password and idp Microsoft is configure on the organization as authencation method - // Given the user has username password and Microsoft configured - - // Login form shows username field and a Microsoft Login button - // User clicks on the Microsoft button - // User is redirected to Microsoft - // User authenticates in Microsoft and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in Microsoft Login" - // User can choose password for authentication +test("login with Microsoft IDP, no user existing - auto register not possible", async ({ page }) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given idp Microsoft is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Microsoft + // User authenticates in Microsoft + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with Microsoft IDP, no user existing - auto register", async ({user, page}) => { - // Given idp Microsoft is configure on the organization as only authencation method - // Given idp Microsoft is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Microsoft - // User authenticates in Microsoft - // User is redirect to ZITADEL login - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Microsoft IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({ + page, +}) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given idp Microsoft is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to Microsoft + // User authenticates in Microsoft + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information }); -test("login with Microsoft IDP, no user existing - auto register not possible", async ({user, page}) => { - // Given idp Microsoft is configure on the organization as only authencation method - // Given idp Microsoft is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Microsoft - // User authenticates in Microsoft - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // User will see the registration page with pre filled user information - // User fills missing information - // User clicks register button - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with Microsoft IDP, no user linked - auto link", async ({ page }) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given idp Microsoft is configure with account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com exists + // User is automatically redirected to Microsoft + // User authenticates in Microsoft with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with Microsoft IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { - // Given idp Microsoft is configure on the organization as only authencation method - // Given idp Microsoft is configure with account creation not allowed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to Microsoft - // User authenticates in Microsoft - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // Error message is shown, that registration of the user was not possible due to missing information +test("login with Microsoft IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given idp Microsoft is configure with manually account linking not allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Microsoft + // User authenticates in Microsoft with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible }); -test("login with Microsoft IDP, no user linked - auto link", async ({user, page}) => { - // Given idp Microsoft is configure on the organization as only authencation method - // Given idp Microsoft is configure with account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com exists - - // User is automatically redirected to Microsoft - // User authenticates in Microsoft with user@zitadel.com - // User is redirect to ZITADEL login - // User is linked with existing user in ZITADEL - // User is redirected to the app (default redirect url) -}); - -test("login with Microsoft IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Microsoft is configure on the organization as only authencation method - // Given idp Microsoft is configure with manually account linking not allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Microsoft - // User authenticates in Microsoft with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User will get an error message that account linking wasn't possible -}); - - -test("login with Microsoft IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp Microsoft is configure on the organization as only authencation method - // Given idp Microsoft is configure with manually account linking allowed, and linking set to existing email - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to Microsoft - // User authenticates in Microsoft with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User is prompted to link the account manually - // User is redirected to the app (default redirect url) +test("login with Microsoft IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp Microsoft is configure on the organization as only authencation method + // Given idp Microsoft is configure with manually account linking allowed, and linking set to existing email + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to Microsoft + // User authenticates in Microsoft with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/idp-saml.spec.ts b/acceptance/tests/idp-saml.spec.ts index 890965180b..8f81cd82d6 100644 --- a/acceptance/tests/idp-saml.spec.ts +++ b/acceptance/tests/idp-saml.spec.ts @@ -1,157 +1,95 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; +import test from "@playwright/test"; -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, +test("login with SAML IDP", async ({ page }) => { + // Given a SAML IDP is configured on the organization + // Given the user has SAML IDP added as auth method + // User authenticates with the SAML IDP + // User is redirected back to login + // User is redirected to the app }); - -test("login with SAML IDP - auto redirect", async ({user, page}) => { - // Given idp SAML is configure on the organization as only authencation method - // Given ZITADEL Action is added to autofill missing user information - // Given the user has only idp SAML added as auth method - - // User is automatically redirected to SAML - // User authenticates in SAML - // User is redirect to ZITADEL login - // User is redirected to the app (default redirect url) +test("login with SAML IDP - error", async ({ page }) => { + // Given the SAML IDP is configured on the organization + // Given the user has SAML IDP added as auth method + // User is redirected to the SAML IDP + // User authenticates with the SAML IDP and gets an error + // User is redirected back to login + // An error is shown to the user "Something went wrong" }); - -test("login with SAML IDP - auto redirect, error", async ({user, page}) => { - // Given idp SAML is configure on the organization as only authencation method - // Given the user has only idp SAML added as auth method - - // User is automatically redirected to SAML - // User authenticates in SAML and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in SAML Login" +test("login with SAML IDP, no user existing - auto register", async ({ page }) => { + // Given idp SAML is configure on the organization as only authencation method + // Given idp SAML is configure with account creation alloweed, and automatic creation enabled + // Given ZITADEL Action is added to autofill missing user information + // Given no user exists yet + // User is automatically redirected to SAML + // User authenticates in SAML + // User is redirect to ZITADEL login + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with SAML IDP", async ({user, page}) => { - // Given username password and idp SAML is configure on the organization as authencation method - // Given the user has username password and SAML configured - - // Login form shows username field and a SAML Login button - // User clicks on the SAML button - // User is redirected to SAML - // User authenticates in SAML and gets an error - // User is redirect to ZITADEL login automatically - // User is redirected to app automatically (default redirect url) +test("login with SAML IDP, no user existing - auto register not possible", async ({ page }) => { + // Given idp SAML is configure on the organization as only authencation method + // Given idp SAML is configure with account creation alloweed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to SAML + // User authenticates in SAML + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // User will see the registration page with pre filled user information + // User fills missing information + // User clicks register button + // User is created in ZITADEL + // User is redirected to the app (default redirect url) }); - -test("login with SAML IDP, error", async ({user, page}) => { - // Given username password and idp SAML is configure on the organization as authencation method - // Given the user has username password and SAML configured - - // Login form shows username field and a SAML Login button - // User clicks on the SAML button - // User is redirected to SAML - // User authenticates in SAML and gets an error - // User is redirect to ZITADEL login - // Error is shown to the user "Something went wrong in SAML Login" - // User can choose password for authentication +test("login with SAML IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({ + page, +}) => { + // Given idp SAML is configure on the organization as only authencation method + // Given idp SAML is configure with account creation not allowed, and automatic creation enabled + // Given no user exists yet + // User is automatically redirected to SAML + // User authenticates in SAML + // User is redirect to ZITADEL login + // Because of missing informaiton on the user auto creation is not possible + // Error message is shown, that registration of the user was not possible due to missing information }); -test("login with SAML IDP, no user existing - auto register", async ({user, page}) => { - // Given idp SAML is configure on the organization as only authencation method - // Given idp SAML is configure with account creation alloweed, and automatic creation enabled - // Given ZITADEL Action is added to autofill missing user information - // Given no user exists yet - - // User is automatically redirected to SAML - // User authenticates in SAML - // User is redirect to ZITADEL login - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with SAML IDP, no user linked - auto link", async ({ page }) => { + // Given idp SAML is configure on the organization as only authencation method + // Given idp SAML is configure with account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com exists + // User is automatically redirected to SAML + // User authenticates in SAML with user@zitadel.com + // User is redirect to ZITADEL login + // User is linked with existing user in ZITADEL + // User is redirected to the app (default redirect url) }); -test("login with SAML IDP, no user existing - auto register not possible", async ({user, page}) => { - // Given idp SAML is configure on the organization as only authencation method - // Given idp SAML is configure with account creation alloweed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to SAML - // User authenticates in SAML - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // User will see the registration page with pre filled user information - // User fills missing information - // User clicks register button - // User is created in ZITADEL - // User is redirected to the app (default redirect url) +test("login with SAML IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp SAML is configure on the organization as only authencation method + // Given idp SAML is configure with manually account linking not allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to SAML + // User authenticates in SAML with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User will get an error message that account linking wasn't possible }); -test("login with SAML IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({user, page}) => { - // Given idp SAML is configure on the organization as only authencation method - // Given idp SAML is configure with account creation not allowed, and automatic creation enabled - // Given no user exists yet - - // User is automatically redirected to SAML - // User authenticates in SAML - // User is redirect to ZITADEL login - // Because of missing informaiton on the user auto creation is not possible - // Error message is shown, that registration of the user was not possible due to missing information -}); - -test("login with SAML IDP, no user linked - auto link", async ({user, page}) => { - // Given idp SAML is configure on the organization as only authencation method - // Given idp SAML is configure with account linking allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com exists - - // User is automatically redirected to SAML - // User authenticates in SAML with user@zitadel.com - // User is redirect to ZITADEL login - // User is linked with existing user in ZITADEL - // User is redirected to the app (default redirect url) -}); - -test("login with SAML IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp SAML is configure on the organization as only authencation method - // Given idp SAML is configure with manually account linking not allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to SAML - // User authenticates in SAML with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User will get an error message that account linking wasn't possible -}); - - -test("login with SAML IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { - // Given idp SAML is configure on the organization as only authencation method - // Given idp SAML is configure with manually account linking allowed, and linking set to existing email - // Given ZITADEL Action is added to autofill missing user information - // Given user with email address user@zitadel.com doesn't exists - - // User is automatically redirected to SAML - // User authenticates in SAML with user@zitadel.com - // User is redirect to ZITADEL login - // User with email address user@zitadel.com can not be found - // User is prompted to link the account manually - // User is redirected to the app (default redirect url) +test("login with SAML IDP, no user linked, user doesn't exist - no auto link", async ({ page }) => { + // Given idp SAML is configure on the organization as only authencation method + // Given idp SAML is configure with manually account linking allowed, and linking set to existing email + // Given ZITADEL Action is added to autofill missing user information + // Given user with email address user@zitadel.com doesn't exists + // User is automatically redirected to SAML + // User authenticates in SAML with user@zitadel.com + // User is redirect to ZITADEL login + // User with email address user@zitadel.com can not be found + // User is prompted to link the account manually + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/login-configuration-possiblities.spec.ts b/acceptance/tests/login-configuration-possiblities.spec.ts index 0f06bd9409..7f7fec5890 100644 --- a/acceptance/tests/login-configuration-possiblities.spec.ts +++ b/acceptance/tests/login-configuration-possiblities.spec.ts @@ -1,86 +1,51 @@ -import {test as base} from "@playwright/test"; -import {PasswordUser} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword, startLogin} from "./login"; -import {loginnameScreenExpect} from "./loginname-screen"; -import {passwordScreenExpect} from "./password-screen"; -import {loginname} from "./loginname"; -import {password} from "./password"; +import test from "@playwright/test"; -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); - -const test = base.extend<{ user: PasswordUser }>({ - user: async ({page}, use) => { - const user = new PasswordUser({ - email: "password@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - }); - await user.ensure(page); - await use(user); - }, +test("login with mfa setup, mfa setup prompt", async ({ page }) => { + // Given the organization has enabled at least one mfa types + // Given the user has a password but no mfa registered + // User authenticates with login name and password + // User is prompted to setup a mfa, mfa providers are listed, the user can choose the provider }); -test("login with mfa setup, mfa setup prompt", async ({user, page}) => { - // Given the organization has enabled at least one mfa types - // Given the user has a password but no mfa registered - // User authenticates with login name and password - // User is prompted to setup a mfa, mfa providers are listed, the user can choose the provider +test("login with mfa setup, no mfa setup prompt", async ({ page }) => { + // Given the organization has set "multifactor init check time" to 0 + // Given the organization has enabled mfa types + // Given the user has a password but no mfa registered + // User authenticates with loginname and password + // user is directly loged in and not prompted to setup mfa }); -test("login with mfa setup, no mfa setup prompt", async ({user, page}) => { - // Given the organization has set "multifactor init check time" to 0 - // Given the organization has enabled mfa types - // Given the user has a password but no mfa registered - // User authenticates with loginname and password - // user is directly loged in and not prompted to setup mfa - }); - -test("login with mfa setup, force mfa for local authenticated users", async ({user, page}) => { - // Given the organization has enabled force mfa for local authentiacted users - // Given the organization has enabled all possible mfa types - // Given the user has a password but no mfa registered - - // enter login name - // enter password - // User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider +test("login with mfa setup, force mfa for local authenticated users", async ({ page }) => { + // Given the organization has enabled force mfa for local authentiacted users + // Given the organization has enabled all possible mfa types + // Given the user has a password but no mfa registered + // User authenticates with loginname and password + // User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider }); - -test("login with mfa setup, force mfa - local user", async ({user, page}) => { - // Given the organization has enabled force mfa for local authentiacted users - // Given the organization has enabled all possible mfa types - // Given the user has a password but no mfa registered - - // enter login name - // enter password - // User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider +test("login with mfa setup, force mfa - local user", async ({ page }) => { + // Given the organization has enabled force mfa for local authentiacted users + // Given the organization has enabled all possible mfa types + // Given the user has a password but no mfa registered + // User authenticates with loginname and password + // User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider }); - -test("login with mfa setup, force mfa - external user", async ({user, page}) => { - // Given the organization has enabled force mfa - // Given the organization has enabled all possible mfa types - // Given the user has an idp but no mfa registered - - // enter login name - // redirect to configured external idp - // User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider +test("login with mfa setup, force mfa - external user", async ({ page }) => { + // Given the organization has enabled force mfa + // Given the organization has enabled all possible mfa types + // Given the user has an idp but no mfa registered + // enter login name + // redirect to configured external idp + // User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider }); - -test("login with mfa setup, force mfa - external user", async ({user, page}) => { - // Given the organization has a password lockout policy set to 1 on the max password attempts - // Given the user has only a password as auth methos - - // enter login name - // enter wrong password - // User will get an error "Wrong password" - // enter password - // User will get an error "Max password attempts reached - user is locked. Please reach out to your administrator" +test("login with mfa setup, force mfa - external user", async ({ page }) => { + // Given the organization has a password lockout policy set to 1 on the max password attempts + // Given the user has only a password as auth methos + // enter login name + // enter wrong password + // User will get an error "Wrong password" + // enter password + // User will get an error "Max password attempts reached - user is locked. Please reach out to your administrator" }); - diff --git a/acceptance/tests/register.spec.ts b/acceptance/tests/register.spec.ts index f11ed673a6..4f0b6d486d 100644 --- a/acceptance/tests/register.spec.ts +++ b/acceptance/tests/register.spec.ts @@ -1,13 +1,8 @@ import { test } from "@playwright/test"; -import dotenv from "dotenv"; -import path from "path"; import { loginScreenExpect } from "./login"; import { registerWithPasskey, registerWithPassword } from "./register"; import { removeUserByUsername } from "./zitadel"; -// Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, ".env.local") }); - test("register with password", async ({ page }) => { const username = "register-password@example.com"; const password = "Password1!"; @@ -29,150 +24,138 @@ test("register with passkey", async ({ page }) => { await loginScreenExpect(page, firstname + " " + lastname); }); -test("register with username and password - only password enabled", async ({user, page}) => { - // Given on the default organization "username and password is allowed" is enabled - // Given on the default organization "username registeration allowed" is enabled - // Given on the default organization no idp is configured and enabled - // Given on the default organization passkey is not enabled - // Given user doesn't exist - - // Click on button "register new user" - // User is redirected to registration page - // Only password is shown as an option - no passkey - // User enters "firstname", "lastname", "username" and "password" - // User is redirected to app (default redirect url) +test("register with username and password - only password enabled", async ({ page }) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is not enabled + // Given user doesn't exist + // Click on button "register new user" + // User is redirected to registration page + // Only password is shown as an option - no passkey + // User enters "firstname", "lastname", "username" and "password" + // User is redirected to app (default redirect url) }); -test("register with username and password - wrong password not enough characters", async ({user, page}) => { - // Given on the default organization "username and password is allowed" is enabled - // Given on the default organization "username registeration allowed" is enabled - // Given on the default organization no idp is configured and enabled - // Given on the default organization passkey is not enabled - // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter - // Given user doesn't exist - - // Click on button "register new user" - // User is redirected to registration page - // Only password is shown as an option - no passkey - // User enters "firstname", "lastname", "username" and a password thats to short - // Error is shown "Password doesn't match the policy - it must have at least 8 characters" +test("register with username and password - wrong password not enough characters", async ({ page }) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is not enabled + // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter + // Given user doesn't exist + // Click on button "register new user" + // User is redirected to registration page + // Only password is shown as an option - no passkey + // User enters "firstname", "lastname", "username" and a password thats to short + // Error is shown "Password doesn't match the policy - it must have at least 8 characters" }); -test("register with username and password - wrong password number missing", async ({user, page}) => { - // Given on the default organization "username and password is allowed" is enabled - // Given on the default organization "username registeration allowed" is enabled - // Given on the default organization no idp is configured and enabled - // Given on the default organization passkey is not enabled - // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter - // Given user doesn't exist - - // Click on button "register new user" - // User is redirected to registration page - // Only password is shown as an option - no passkey - // User enters "firstname", "lastname", "username" and a password without a number - // Error is shown "Password doesn't match the policy - number missing" +test("register with username and password - wrong password number missing", async ({ page }) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is not enabled + // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter + // Given user doesn't exist + // Click on button "register new user" + // User is redirected to registration page + // Only password is shown as an option - no passkey + // User enters "firstname", "lastname", "username" and a password without a number + // Error is shown "Password doesn't match the policy - number missing" }); -test("register with username and password - wrong password upper case missing", async ({user, page}) => { - // Given on the default organization "username and password is allowed" is enabled - // Given on the default organization "username registeration allowed" is enabled - // Given on the default organization no idp is configured and enabled - // Given on the default organization passkey is not enabled - // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter - // Given user doesn't exist - - // Click on button "register new user" - // User is redirected to registration page - // Only password is shown as an option - no passkey - // User enters "firstname", "lastname", "username" and a password without an upper case - // Error is shown "Password doesn't match the policy - uppercase letter missing" +test("register with username and password - wrong password upper case missing", async ({ page }) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is not enabled + // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter + // Given user doesn't exist + // Click on button "register new user" + // User is redirected to registration page + // Only password is shown as an option - no passkey + // User enters "firstname", "lastname", "username" and a password without an upper case + // Error is shown "Password doesn't match the policy - uppercase letter missing" }); -test("register with username and password - wrong password lower case missing", async ({user, page}) => { - // Given on the default organization "username and password is allowed" is enabled - // Given on the default organization "username registeration allowed" is enabled - // Given on the default organization no idp is configured and enabled - // Given on the default organization passkey is not enabled - // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter - // Given user doesn't exist - - // Click on button "register new user" - // User is redirected to registration page - // Only password is shown as an option - no passkey - // User enters "firstname", "lastname", "username" and a password without an lower case - // Error is shown "Password doesn't match the policy - lowercase letter missing" +test("register with username and password - wrong password lower case missing", async ({ page }) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is not enabled + // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter + // Given user doesn't exist + // Click on button "register new user" + // User is redirected to registration page + // Only password is shown as an option - no passkey + // User enters "firstname", "lastname", "username" and a password without an lower case + // Error is shown "Password doesn't match the policy - lowercase letter missing" }); - -test("register with username and password - wrong password symboo missing", async ({user, page}) => { - // Given on the default organization "username and password is allowed" is enabled - // Given on the default organization "username registeration allowed" is enabled - // Given on the default organization no idp is configured and enabled - // Given on the default organization passkey is not enabled - // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter - // Given user doesn't exist - - // Click on button "register new user" - // User is redirected to registration page - // Only password is shown as an option - no passkey - // User enters "firstname", "lastname", "username" and a password without an symbol - // Error is shown "Password doesn't match the policy - symbol missing" +test("register with username and password - wrong password symboo missing", async ({ page }) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is not enabled + // Given password policy is set to 8 characters and must include number, symbol, lower and upper letter + // Given user doesn't exist + // Click on button "register new user" + // User is redirected to registration page + // Only password is shown as an option - no passkey + // User enters "firstname", "lastname", "username" and a password without an symbol + // Error is shown "Password doesn't match the policy - symbol missing" }); -test("register with username and password - password and passkey enabled", async ({user, page}) => { - // Given on the default organization "username and password is allowed" is enabled - // Given on the default organization "username registeration allowed" is enabled - // Given on the default organization no idp is configured and enabled - // Given on the default organization passkey is enabled - // Given user doesn't exist - - // Click on button "register new user" - // User is redirected to registration page - // User enters "firstname", "lastname", "username" - // Password and passkey are shown as authentication option - // User clicks password - // User enters password - // User is redirected to app (default redirect url) +test("register with username and password - password and passkey enabled", async ({ page }) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is enabled + // Given user doesn't exist + // Click on button "register new user" + // User is redirected to registration page + // User enters "firstname", "lastname", "username" + // Password and passkey are shown as authentication option + // User clicks password + // User enters password + // User is redirected to app (default redirect url) }); -test("register with username and passkey - password and passkey enabled", async ({user, page}) => { - // Given on the default organization "username and password is allowed" is enabled - // Given on the default organization "username registeration allowed" is enabled - // Given on the default organization no idp is configured and enabled - // Given on the default organization passkey is enabled - // Given user doesn't exist - - // Click on button "register new user" - // User is redirected to registration page - // User enters "firstname", "lastname", "username" - // Password and passkey are shown as authentication option - // User clicks passkey - // Passkey is opened automatically - // User verifies passkey - // User is redirected to app (default redirect url) +test("register with username and passkey - password and passkey enabled", async ({ page }) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given on the default organization passkey is enabled + // Given user doesn't exist + // Click on button "register new user" + // User is redirected to registration page + // User enters "firstname", "lastname", "username" + // Password and passkey are shown as authentication option + // User clicks passkey + // Passkey is opened automatically + // User verifies passkey + // User is redirected to app (default redirect url) }); - -test("register with username and password - registration disabled", async ({user, page}) => { - // Given on the default organization "username and password is allowed" is enabled - // Given on the default organization "username registeration allowed" is enabled - // Given on the default organization no idp is configured and enabled - // Given user doesn't exist - - // Button "register new user" is not available +test("register with username and password - registration disabled", async ({ page }) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization no idp is configured and enabled + // Given user doesn't exist + // Button "register new user" is not available }); -test("register with username and password - multiple registration options", async ({user, page}) => { - // Given on the default organization "username and password is allowed" is enabled - // Given on the default organization "username registeration allowed" is enabled - // Given on the default organization one idp is configured and enabled - // Given user doesn't exist - - // Click on button "register new user" - // User is redirected to registration options - // Local User and idp button are shown - // User clicks idp button - // User enters "firstname", "lastname", "username" and "password" - // User clicks next - // User is redirected to app (default redirect url) +test("register with username and password - multiple registration options", async ({ page }) => { + // Given on the default organization "username and password is allowed" is enabled + // Given on the default organization "username registeration allowed" is enabled + // Given on the default organization one idp is configured and enabled + // Given user doesn't exist + // Click on button "register new user" + // User is redirected to registration options + // Local User and idp button are shown + // User clicks idp button + // User enters "firstname", "lastname", "username" and "password" + // User clicks next + // User is redirected to app (default redirect url) }); diff --git a/acceptance/tests/username-passkey.spec.ts b/acceptance/tests/username-passkey.spec.ts index f015049c5f..d4d5e38ebe 100644 --- a/acceptance/tests/username-passkey.spec.ts +++ b/acceptance/tests/username-passkey.spec.ts @@ -25,24 +25,22 @@ test("username and passkey login", async ({ user, page }) => { await loginScreenExpect(page, user.getFullName()); }); -test("username and passkey login, if passkey enabled", async ({user, page}) => { - // Given passkey is enabled on the organization of the user - // Given the user has only passkey enabled as authentication - - // enter username - // passkey popup is directly shown - // user verifies passkey - // user is redirected to app +test("username and passkey login, if passkey enabled", async ({ user, page }) => { + // Given passkey is enabled on the organization of the user + // Given the user has only passkey enabled as authentication + // enter username + // passkey popup is directly shown + // user verifies passkey + // user is redirected to app }); -test("username and passkey login, multiple auth methods", async ({user, page}) => { - // Given passkey and password is enabled on the organization of the user - // Given the user has password and passkey registered - - // enter username - // passkey popup is directly shown - // user aborts passkey authentication - // user switches to password authentication - // user enters password - // user is redirected to app +test("username and passkey login, multiple auth methods", async ({ user, page }) => { + // Given passkey and password is enabled on the organization of the user + // Given the user has password and passkey registered + // enter username + // passkey popup is directly shown + // user aborts passkey authentication + // user switches to password authentication + // user enters password + // user is redirected to app }); diff --git a/acceptance/tests/username-password-otp_email.spec.ts b/acceptance/tests/username-password-otp_email.spec.ts index 43749cb203..aab826f6fb 100644 --- a/acceptance/tests/username-password-otp_email.spec.ts +++ b/acceptance/tests/username-password-otp_email.spec.ts @@ -1,86 +1,77 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; +import { test as base } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { OtpType, PasswordUserWithOTP } from "./user"; // Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.email, - }); + user: async ({ page }, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.email, + }); - await user.ensure(page); - await use(user); - }, + await user.ensure(page); + await use(user); + }, }); - -test("username, password and email otp login, enter code manually", async ({user, page}) => { - // Given email otp is enabled on the organizaiton of the user - // Given the user has only email otp configured as second factor - - // User enters username - // User enters password - // User receives an email with a verification code - // User enters the code into the ui - // User is redirected to the app (default redirect url) +test("username, password and email otp login, enter code manually", async ({ user, page }) => { + // Given email otp is enabled on the organizaiton of the user + // Given the user has only email otp configured as second factor + // User enters username + // User enters password + // User receives an email with a verification code + // User enters the code into the ui + // User is redirected to the app (default redirect url) }); - -test("username, password and email otp login, click link in email", async ({user, page}) => { - // Given email otp is enabled on the organizaiton of the user - // Given the user has only email otp configured as second factor - - // User enters username - // User enters password - // User receives an email with a verification code - // User clicks link in the email - // User is redirected to the app (default redirect url) +test("username, password and email otp login, click link in email", async ({ user, page }) => { + // Given email otp is enabled on the organizaiton of the user + // Given the user has only email otp configured as second factor + // User enters username + // User enters password + // User receives an email with a verification code + // User clicks link in the email + // User is redirected to the app (default redirect url) }); -test("username, password and email otp login, resend code", async ({user, page}) => { - // Given email otp is enabled on the organizaiton of the user - // Given the user has only email otp configured as second factor - - // User enters username - // User enters password - // User receives an email with a verification code - // User clicks resend code - // User receives a new email with a verification code - // User enters the new code in the ui - // User is redirected to the app (default redirect url) +test("username, password and email otp login, resend code", async ({ user, page }) => { + // Given email otp is enabled on the organizaiton of the user + // Given the user has only email otp configured as second factor + // User enters username + // User enters password + // User receives an email with a verification code + // User clicks resend code + // User receives a new email with a verification code + // User enters the new code in the ui + // User is redirected to the app (default redirect url) }); -test("username, password and email otp login, wrong code", async ({user, page}) => { - // Given email otp is enabled on the organizaiton of the user - // Given the user has only email otp configured as second factor - - // User enters username - // User enters password - // User receives an email with a verification code - // User enters a wrond code - // Error message - "Invalid code" is shown +test("username, password and email otp login, wrong code", async ({ user, page }) => { + // Given email otp is enabled on the organizaiton of the user + // Given the user has only email otp configured as second factor + // User enters username + // User enters password + // User receives an email with a verification code + // User enters a wrond code + // Error message - "Invalid code" is shown }); -test("username, password and email otp login, multiple mfa options", async ({user, page}) => { - // Given email otp and sms otp is enabled on the organizaiton of the user - // Given the user has email and sms otp configured as second factor - - // User enters username - // User enters password - // User receives an email with a verification code - // User clicks button to use sms otp as second factor - // User receives an sms with a verification code - // User enters code in ui - // User is redirected to the app (default redirect url) +test("username, password and email otp login, multiple mfa options", async ({ user, page }) => { + // Given email otp and sms otp is enabled on the organizaiton of the user + // Given the user has email and sms otp configured as second factor + // User enters username + // User enters password + // User receives an email with a verification code + // User clicks button to use sms otp as second factor + // User receives an sms with a verification code + // User enters code in ui + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index 1f6873a438..a839ca1450 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -1,27 +1,3 @@ -import { test as base } from "@playwright/test"; -import dotenv from "dotenv"; -import path from "path"; -import { OtpType, PasswordUserWithOTP } from "./user"; - -// Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, ".env.local") }); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({ page }, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, -}); - /* test("username, password and otp login", async ({ user, page }) => { //const server = startSink() diff --git a/acceptance/tests/username-password-totp.spec.ts b/acceptance/tests/username-password-totp.spec.ts index 85ea828e08..ee59d72030 100644 --- a/acceptance/tests/username-password-totp.spec.ts +++ b/acceptance/tests/username-password-totp.spec.ts @@ -1,63 +1,56 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; +import { test as base } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { OtpType, PasswordUserWithOTP } from "./user"; // Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); + user: async ({ page }, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); - await user.ensure(page); - await use(user); - }, + await user.ensure(page); + await use(user); + }, }); -test("username, password and totp login", async ({user, page}) => { - // Given totp is enabled on the organizaiton of the user - // Given the user has only totp configured as second factor - - // User enters username - // User enters password - // Screen for entering the code is shown directly - // User enters the code into the ui - // User is redirected to the app (default redirect url) +test("username, password and totp login", async ({ user, page }) => { + // Given totp is enabled on the organizaiton of the user + // Given the user has only totp configured as second factor + // User enters username + // User enters password + // Screen for entering the code is shown directly + // User enters the code into the ui + // User is redirected to the app (default redirect url) }); - -test("username, password and totp otp login, wrong code", async ({user, page}) => { - // Given totp is enabled on the organizaiton of the user - // Given the user has only totp configured as second factor - - // User enters username - // User enters password - // Screen for entering the code is shown directly - // User enters a wrond code - // Error message - "Invalid code" is shown +test("username, password and totp otp login, wrong code", async ({ user, page }) => { + // Given totp is enabled on the organizaiton of the user + // Given the user has only totp configured as second factor + // User enters username + // User enters password + // Screen for entering the code is shown directly + // User enters a wrond code + // Error message - "Invalid code" is shown }); - -test("username, password and totp login, multiple mfa options", async ({user, page}) => { - // Given totp and email otp is enabled on the organizaiton of the user - // Given the user has totp and email otp configured as second factor - - // User enters username - // User enters password - // Screen for entering the code is shown directly - // Button to switch to email otp is shown - // User clicks button to use email otp instead - // User receives an email with a verification code - // User enters code in ui - // User is redirected to the app (default redirect url) +test("username, password and totp login, multiple mfa options", async ({ user, page }) => { + // Given totp and email otp is enabled on the organizaiton of the user + // Given the user has totp and email otp configured as second factor + // User enters username + // User enters password + // Screen for entering the code is shown directly + // Button to switch to email otp is shown + // User clicks button to use email otp instead + // User receives an email with a verification code + // User enters code in ui + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/username-password-u2f.spec.ts b/acceptance/tests/username-password-u2f.spec.ts index 0f87175ad0..1e297ad31d 100644 --- a/acceptance/tests/username-password-u2f.spec.ts +++ b/acceptance/tests/username-password-u2f.spec.ts @@ -1,52 +1,46 @@ -import {test as base} from "@playwright/test"; -import {OtpType, PasswordUserWithOTP} from './user'; -import path from 'path'; -import dotenv from 'dotenv'; -import {loginScreenExpect, loginWithPassword} from "./login"; -import {startSink} from "./otp"; +import { test as base } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { OtpType, PasswordUserWithOTP } from "./user"; // Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, '.env.local')}); +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({page}, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); + user: async ({ page }, use) => { + const user = new PasswordUserWithOTP({ + email: "otp_sms@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + type: OtpType.sms, + }); - await user.ensure(page); - await use(user); - }, + await user.ensure(page); + await use(user); + }, }); - -test("username, password and u2f login", async ({user, page}) => { - // Given u2f is enabled on the organizaiton of the user - // Given the user has only u2f configured as second factor - - // User enters username - // User enters password - // Popup for u2f is directly opened - // User verifies u2f - // User is redirected to the app (default redirect url) +test("username, password and u2f login", async ({ user, page }) => { + // Given u2f is enabled on the organizaiton of the user + // Given the user has only u2f configured as second factor + // User enters username + // User enters password + // Popup for u2f is directly opened + // User verifies u2f + // User is redirected to the app (default redirect url) }); - -test("username, password and u2f login, multiple mfa options", async ({user, page}) => { - // Given u2f and semailms otp is enabled on the organizaiton of the user - // Given the user has u2f and email otp configured as second factor - - // User enters username - // User enters password - // Popup for u2f is directly opened - // User aborts u2f verification - // User clicks button to use email otp as second factor - // User receives an email with a verification code - // User enters code in ui - // User is redirected to the app (default redirect url) +test("username, password and u2f login, multiple mfa options", async ({ user, page }) => { + // Given u2f and semailms otp is enabled on the organizaiton of the user + // Given the user has u2f and email otp configured as second factor + // User enters username + // User enters password + // Popup for u2f is directly opened + // User aborts u2f verification + // User clicks button to use email otp as second factor + // User receives an email with a verification code + // User enters code in ui + // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index 40b8246a28..d905170a44 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -44,111 +44,96 @@ test("username and password login, wrong password", async ({ user, page }) => { await passwordScreenExpect(page, "wrong"); }); -test("username and password login, wrong username, ignore unknown usernames", async ({user, page}) => { - // Given user doesn't exist but ignore unknown usernames setting is set to true - // Given username password login is enabled on the users organization - - // enter login name - // enter password - // redirect to loginname page --> error message username or password wrong +test("username and password login, wrong username, ignore unknown usernames", async ({ user, page }) => { + // Given user doesn't exist but ignore unknown usernames setting is set to true + // Given username password login is enabled on the users organization + // enter login name + // enter password + // redirect to loginname page --> error message username or password wrong }); -test("username and password login, initial password change", async ({user, page}) => { - // Given user is created and has changePassword set to true - // Given username password login is enabled on the users organization - - // enter login name - // enter password - // create new password +test("username and password login, initial password change", async ({ user, page }) => { + // Given user is created and has changePassword set to true + // Given username password login is enabled on the users organization + // enter login name + // enter password + // create new password }); - -test("username and password login, reset password hidden", async ({user, page}) => { - // Given the organization has enabled "Password reset hidden" in the login policy - // Given username password login is enabled on the users organization - - // enter login name - // password reset link should not be shown on password screen +test("username and password login, reset password hidden", async ({ user, page }) => { + // Given the organization has enabled "Password reset hidden" in the login policy + // Given username password login is enabled on the users organization + // enter login name + // password reset link should not be shown on password screen }); -test("username and password login, reset password - enter code manually", async ({user, page}) => { - // Given user has forgotten password and clicks the forgot password button - // Given username password login is enabled on the users organization - - // enter login name - // click password forgotten - // enter code from email - // user is redirected to app (default redirect url) +test("username and password login, reset password - enter code manually", async ({ user, page }) => { + // Given user has forgotten password and clicks the forgot password button + // Given username password login is enabled on the users organization + // enter login name + // click password forgotten + // enter code from email + // user is redirected to app (default redirect url) }); -test("username and password login, reset password - click link", async ({user, page}) => { - // Given user has forgotten password and clicks the forgot password button, and then the link in the email - // Given username password login is enabled on the users organization - - // enter login name - // click password forgotten - // click link in email - // set new password - // redirect to app (default redirect url) +test("username and password login, reset password - click link", async ({ user, page }) => { + // Given user has forgotten password and clicks the forgot password button, and then the link in the email + // Given username password login is enabled on the users organization + // enter login name + // click password forgotten + // click link in email + // set new password + // redirect to app (default redirect url) }); -test("username and password login, reset password, resend code", async ({user, page}) => { - // Given user has forgotten password and clicks the forgot password button and then resend code - // Given username password login is enabled on the users organization - - // enter login name - // click password forgotten - // click resend code - // enter code from second email - // user is redirected to app (default redirect url) +test("username and password login, reset password, resend code", async ({ user, page }) => { + // Given user has forgotten password and clicks the forgot password button and then resend code + // Given username password login is enabled on the users organization + // enter login name + // click password forgotten + // click resend code + // enter code from second email + // user is redirected to app (default redirect url) }); -test("email login enabled", async ({user, page}) => { - // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists - // Given no other user with the same email address exists - - // enter email address "test@zitadel.com " in login screen - // user will get to password screen +test("email login enabled", async ({ user, page }) => { + // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists + // Given no other user with the same email address exists + // enter email address "test@zitadel.com " in login screen + // user will get to password screen }); -test("email login disabled", async ({user, page}) => { - // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists - // Given no other user with the same email address exists - - // enter email address "test@zitadel.com" in login screen - // user will see error message "user not found" +test("email login disabled", async ({ user, page }) => { + // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists + // Given no other user with the same email address exists + // enter email address "test@zitadel.com" in login screen + // user will see error message "user not found" }); -test("email login enabled - multiple users", async ({user, page}) => { - // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists - // Given a second user with the username "testuser2", email test@zitadel.com and phone number 0711111111 exists - - // enter email address "test@zitadel.com" in login screen - // user will see error message "user not found" +test("email login enabled - multiple users", async ({ user, page }) => { + // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists + // Given a second user with the username "testuser2", email test@zitadel.com and phone number 0711111111 exists + // enter email address "test@zitadel.com" in login screen + // user will see error message "user not found" }); - -test("phone login enabled", async ({user, page}) => { - // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists - // Given no other user with the same phon number exists - - // enter phone number "0711111111" in login screen - // user will get to password screen +test("phone login enabled", async ({ user, page }) => { + // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists + // Given no other user with the same phon number exists + // enter phone number "0711111111" in login screen + // user will get to password screen }); -test("phone login disabled", async ({user, page}) => { - // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists - // Given no other user with the same phone number exists - - // enter phone number "0711111111" in login screen - // user will see error message "user not found" +test("phone login disabled", async ({ user, page }) => { + // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists + // Given no other user with the same phone number exists + // enter phone number "0711111111" in login screen + // user will see error message "user not found" }); -test("phone login enabled - multiple users", async ({user, page}) => { - // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists - // Given a second user with the username "testuser2", email test@zitadel.com and phone number 0711111111 exists - - // enter phone number "0711111111" in login screen - // user will see error message "user not found" +test("phone login enabled - multiple users", async ({ user, page }) => { + // Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists + // Given a second user with the username "testuser2", email test@zitadel.com and phone number 0711111111 exists + // enter phone number "0711111111" in login screen + // user will see error message "user not found" }); - From a50674559b79e2f4616e39e794662f4842940c82 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:10:24 +0100 Subject: [PATCH 444/640] chore: fixes to tests --- acceptance/tests/idp-apple.spec.ts | 4 +- acceptance/tests/idp-generic-jwt.spec.ts | 6 +- acceptance/tests/idp-generic-oauth.spec.ts | 4 +- acceptance/tests/idp-generic-oidc.spec.ts | 4 +- .../tests/idp-github-enterprise.spec.ts | 4 +- acceptance/tests/idp-github.spec.ts | 4 +- .../tests/idp-gitlab-self-hosted.spec.ts | 4 +- acceptance/tests/idp-gitlab.spec.ts | 4 +- acceptance/tests/idp-google.spec.ts | 4 +- acceptance/tests/idp-ldap.spec.ts | 4 +- acceptance/tests/idp-microsoft.spec.ts | 4 +- acceptance/tests/idp-saml.spec.ts | 4 +- .../login-configuration-possiblities.spec.ts | 2 +- .../tests/username-password-changed.spec.ts | 69 ++++++++++--------- 14 files changed, 62 insertions(+), 59 deletions(-) diff --git a/acceptance/tests/idp-apple.spec.ts b/acceptance/tests/idp-apple.spec.ts index c599cd5bba..44431d0615 100644 --- a/acceptance/tests/idp-apple.spec.ts +++ b/acceptance/tests/idp-apple.spec.ts @@ -101,7 +101,7 @@ test("login with Apple IDP, no user linked - auto link", async ({user, page}) => // User is redirected to the app (default redirect url) }); -test("login with Apple IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Apple IDP, no user linked, linking not possible", async ({user, page}) => { // Given idp Apple is configure on the organization as only authencation method // Given idp Apple is configure with manually account linking not allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists @@ -114,7 +114,7 @@ test("login with Apple IDP, no user linked, user doesn't exist - no auto link", }); -test("login with Apple IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Apple IDP, no user linked, user link successful", async ({user, page}) => { // Given idp Apple is configure on the organization as only authencation method // Given idp Apple is configure with manually account linking allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists diff --git a/acceptance/tests/idp-generic-jwt.spec.ts b/acceptance/tests/idp-generic-jwt.spec.ts index 6ca3558dd6..c12bd3b8dc 100644 --- a/acceptance/tests/idp-generic-jwt.spec.ts +++ b/acceptance/tests/idp-generic-jwt.spec.ts @@ -58,7 +58,7 @@ test("login with Generic JWT IDP", async ({user, page}) => { // User is redirected to app automatically (default redirect url) }); - + test("login with Generic JWT IDP, error", async ({user, page}) => { // Given username password and idp Generic JWT is configure on the organization as authencation method // Given the user has username password and Generic JWT configured @@ -124,7 +124,7 @@ test("login with Generic JWT IDP, no user linked - auto link", async ({user, pag // User is redirected to the app (default redirect url) }); -test("login with Generic JWT IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Generic JWT IDP, no user linked, linking not possible", async ({user, page}) => { // Given idp Generic JWT is configure on the organization as only authencation method // Given idp Generic JWT is configure with manually account linking not allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists @@ -137,7 +137,7 @@ test("login with Generic JWT IDP, no user linked, user doesn't exist - no auto l }); -test("login with Generic JWT IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Generic JWT IDP, no user linked, linking successful", async ({user, page}) => { // Given idp Generic JWT is configure on the organization as only authencation method // Given idp Generic JWT is configure with manually account linking allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists diff --git a/acceptance/tests/idp-generic-oauth.spec.ts b/acceptance/tests/idp-generic-oauth.spec.ts index 0d55f39973..4f46019bc6 100644 --- a/acceptance/tests/idp-generic-oauth.spec.ts +++ b/acceptance/tests/idp-generic-oauth.spec.ts @@ -125,7 +125,7 @@ test("login with Generic OAuth IDP, no user linked - auto link", async ({user, p // User is redirected to the app (default redirect url) }); -test("login with Generic OAuth IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Generic OAuth IDP, no user linked, linking not possible", async ({user, page}) => { // Given idp Generic OAuth is configure on the organization as only authencation method // Given idp Generic OAuth is configure with manually account linking not allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists @@ -138,7 +138,7 @@ test("login with Generic OAuth IDP, no user linked, user doesn't exist - no auto }); -test("login with Generic OAuth IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Generic OAuth IDP, no user linked, linking successful", async ({user, page}) => { // Given idp Generic OAuth is configure on the organization as only authencation method // Given idp Generic OAuth is configure with manually account linking allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists diff --git a/acceptance/tests/idp-generic-oidc.spec.ts b/acceptance/tests/idp-generic-oidc.spec.ts index ed19a50a73..c066b04b86 100644 --- a/acceptance/tests/idp-generic-oidc.spec.ts +++ b/acceptance/tests/idp-generic-oidc.spec.ts @@ -126,7 +126,7 @@ test("login with Generic OIDC IDP, no user linked - auto link", async ({user, pa // User is redirected to the app (default redirect url) }); -test("login with Generic OIDC IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Generic OIDC IDP, no user linked, linking not possible", async ({user, page}) => { // Given idp Generic OIDC is configure on the organization as only authencation method // Given idp Generic OIDC is configure with manually account linking not allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists @@ -139,7 +139,7 @@ test("login with Generic OIDC IDP, no user linked, user doesn't exist - no auto }); -test("login with Generic OIDC IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Generic OIDC IDP, no user linked, linking successful", async ({user, page}) => { // Given idp Generic OIDC is configure on the organization as only authencation method // Given idp Generic OIDC is configure with manually account linking allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists diff --git a/acceptance/tests/idp-github-enterprise.spec.ts b/acceptance/tests/idp-github-enterprise.spec.ts index ee7f5d8a5b..168a7ee9f3 100644 --- a/acceptance/tests/idp-github-enterprise.spec.ts +++ b/acceptance/tests/idp-github-enterprise.spec.ts @@ -127,7 +127,7 @@ test("login with GitHub Enterprise IDP, no user linked - auto link", async ({use // User is redirected to the app (default redirect url) }); -test("login with GitHub Enterprise IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with GitHub Enterprise IDP, no user linked, linking not possible", async ({user, page}) => { // Given idp GitHub Enterprise is configure on the organization as only authencation method // Given idp GitHub Enterprise is configure with manually account linking not allowed, and linking set to existing email // Given ZITADEL Action is added to autofill missing user information @@ -141,7 +141,7 @@ test("login with GitHub Enterprise IDP, no user linked, user doesn't exist - no }); -test("login with GitHub Enterprise IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with GitHub Enterprise IDP, no user linked, linking successful", async ({user, page}) => { // Given idp GitHub Enterprise is configure on the organization as only authencation method // Given idp GitHub Enterprise is configure with manually account linking allowed, and linking set to existing email // Given ZITADEL Action is added to autofill missing user information diff --git a/acceptance/tests/idp-github.spec.ts b/acceptance/tests/idp-github.spec.ts index 0ac8c13193..c38ac4dee0 100644 --- a/acceptance/tests/idp-github.spec.ts +++ b/acceptance/tests/idp-github.spec.ts @@ -127,7 +127,7 @@ test("login with GitHub IDP, no user linked - auto link", async ({user, page}) = // User is redirected to the app (default redirect url) }); -test("login with GitHub IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with GitHub IDP, no user linked, linking not possible", async ({user, page}) => { // Given idp GitHub is configure on the organization as only authencation method // Given idp GitHub is configure with manually account linking not allowed, and linking set to existing email // Given ZITADEL Action is added to autofill missing user information @@ -141,7 +141,7 @@ test("login with GitHub IDP, no user linked, user doesn't exist - no auto link", }); -test("login with GitHub IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with GitHub IDP, no user linked, linking successful", async ({user, page}) => { // Given idp GitHub is configure on the organization as only authencation method // Given idp GitHub is configure with manually account linking allowed, and linking set to existing email // Given ZITADEL Action is added to autofill missing user information diff --git a/acceptance/tests/idp-gitlab-self-hosted.spec.ts b/acceptance/tests/idp-gitlab-self-hosted.spec.ts index 00b0ec7c4a..6e996c10af 100644 --- a/acceptance/tests/idp-gitlab-self-hosted.spec.ts +++ b/acceptance/tests/idp-gitlab-self-hosted.spec.ts @@ -127,7 +127,7 @@ test("login with Gitlab Self-Hosted IDP, no user linked - auto link", async ({us // User is redirected to the app (default redirect url) }); -test("login with Gitlab Self-Hosted IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Gitlab Self-Hosted IDP, no user linked, linking not possible", async ({user, page}) => { // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method // Given idp Gitlab Self-Hosted is configure with manually account linking not allowed, and linking set to existing email // Given ZITADEL Action is added to autofill missing user information @@ -141,7 +141,7 @@ test("login with Gitlab Self-Hosted IDP, no user linked, user doesn't exist - no }); -test("login with Gitlab Self-Hosted IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Gitlab Self-Hosted IDP, no user linked, linking successful", async ({user, page}) => { // Given idp Gitlab Self-Hosted is configure on the organization as only authencation method // Given idp Gitlab Self-Hosted is configure with manually account linking allowed, and linking set to existing email // Given ZITADEL Action is added to autofill missing user information diff --git a/acceptance/tests/idp-gitlab.spec.ts b/acceptance/tests/idp-gitlab.spec.ts index a94b1ac77d..126d901aaa 100644 --- a/acceptance/tests/idp-gitlab.spec.ts +++ b/acceptance/tests/idp-gitlab.spec.ts @@ -127,7 +127,7 @@ test("login with Gitlab IDP, no user linked - auto link", async ({user, page}) = // User is redirected to the app (default redirect url) }); -test("login with Gitlab IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Gitlab IDP, no user linked, linking not possible", async ({user, page}) => { // Given idp Gitlab is configure on the organization as only authencation method // Given idp Gitlab is configure with manually account linking not allowed, and linking set to existing email // Given ZITADEL Action is added to autofill missing user information @@ -141,7 +141,7 @@ test("login with Gitlab IDP, no user linked, user doesn't exist - no auto link", }); -test("login with Gitlab IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Gitlab IDP, no user linked, linking successful", async ({user, page}) => { // Given idp Gitlab is configure on the organization as only authencation method // Given idp Gitlab is configure with manually account linking allowed, and linking set to existing email // Given ZITADEL Action is added to autofill missing user information diff --git a/acceptance/tests/idp-google.spec.ts b/acceptance/tests/idp-google.spec.ts index 8c13449add..9d564799b9 100644 --- a/acceptance/tests/idp-google.spec.ts +++ b/acceptance/tests/idp-google.spec.ts @@ -124,7 +124,7 @@ test("login with Google IDP, no user linked - auto link", async ({user, page}) = // User is redirected to the app (default redirect url) }); -test("login with Google IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Google IDP, no user linked, linking not possible", async ({user, page}) => { // Given idp Google is configure on the organization as only authencation method // Given idp Google is configure with manually account linking not allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists @@ -137,7 +137,7 @@ test("login with Google IDP, no user linked, user doesn't exist - no auto link", }); -test("login with Google IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Google IDP, no user linked, linking successful", async ({user, page}) => { // Given idp Google is configure on the organization as only authencation method // Given idp Google is configure with manually account linking allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists diff --git a/acceptance/tests/idp-ldap.spec.ts b/acceptance/tests/idp-ldap.spec.ts index fc667edee4..890eda0165 100644 --- a/acceptance/tests/idp-ldap.spec.ts +++ b/acceptance/tests/idp-ldap.spec.ts @@ -124,7 +124,7 @@ test("login with LDAP IDP, no user linked - auto link", async ({user, page}) => // User is redirected to the app (default redirect url) }); -test("login with LDAP IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with LDAP IDP, no user linked, linking not possible", async ({user, page}) => { // Given idp LDAP is configure on the organization as only authencation method // Given idp LDAP is configure with manually account linking not allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists @@ -137,7 +137,7 @@ test("login with LDAP IDP, no user linked, user doesn't exist - no auto link", a }); -test("login with LDAP IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with LDAP IDP, no user linked, linking successful", async ({user, page}) => { // Given idp LDAP is configure on the organization as only authencation method // Given idp LDAP is configure with manually account linking allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists diff --git a/acceptance/tests/idp-microsoft.spec.ts b/acceptance/tests/idp-microsoft.spec.ts index 26ffcc1064..79bc2fffb5 100644 --- a/acceptance/tests/idp-microsoft.spec.ts +++ b/acceptance/tests/idp-microsoft.spec.ts @@ -127,7 +127,7 @@ test("login with Microsoft IDP, no user linked - auto link", async ({user, page} // User is redirected to the app (default redirect url) }); -test("login with Microsoft IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Microsoft IDP, no user linked, linking not possible", async ({user, page}) => { // Given idp Microsoft is configure on the organization as only authencation method // Given idp Microsoft is configure with manually account linking not allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists @@ -140,7 +140,7 @@ test("login with Microsoft IDP, no user linked, user doesn't exist - no auto lin }); -test("login with Microsoft IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with Microsoft IDP, no user linked, linking successful", async ({user, page}) => { // Given idp Microsoft is configure on the organization as only authencation method // Given idp Microsoft is configure with manually account linking allowed, and linking set to existing email // Given user with email address user@zitadel.com doesn't exists diff --git a/acceptance/tests/idp-saml.spec.ts b/acceptance/tests/idp-saml.spec.ts index 890965180b..f0790c3e7d 100644 --- a/acceptance/tests/idp-saml.spec.ts +++ b/acceptance/tests/idp-saml.spec.ts @@ -128,7 +128,7 @@ test("login with SAML IDP, no user linked - auto link", async ({user, page}) => // User is redirected to the app (default redirect url) }); -test("login with SAML IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with SAML IDP, no user linked, linking not possible", async ({user, page}) => { // Given idp SAML is configure on the organization as only authencation method // Given idp SAML is configure with manually account linking not allowed, and linking set to existing email // Given ZITADEL Action is added to autofill missing user information @@ -142,7 +142,7 @@ test("login with SAML IDP, no user linked, user doesn't exist - no auto link", a }); -test("login with SAML IDP, no user linked, user doesn't exist - no auto link", async ({user, page}) => { +test("login with SAML IDP, no user linked, linking successful", async ({user, page}) => { // Given idp SAML is configure on the organization as only authencation method // Given idp SAML is configure with manually account linking allowed, and linking set to existing email // Given ZITADEL Action is added to autofill missing user information diff --git a/acceptance/tests/login-configuration-possiblities.spec.ts b/acceptance/tests/login-configuration-possiblities.spec.ts index 0f06bd9409..a0dae0e2a2 100644 --- a/acceptance/tests/login-configuration-possiblities.spec.ts +++ b/acceptance/tests/login-configuration-possiblities.spec.ts @@ -73,7 +73,7 @@ test("login with mfa setup, force mfa - external user", async ({user, page}) => }); -test("login with mfa setup, force mfa - external user", async ({user, page}) => { +test("login with mfa setup, force mfa - local user, wrong password", async ({user, page}) => { // Given the organization has a password lockout policy set to 1 on the max password attempts // Given the user has only a password as auth methos diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index e1949ff9fe..089e365619 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -1,47 +1,50 @@ -import { test as base } from "@playwright/test"; +import {test as base} from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; -import { loginScreenExpect, loginWithPassword } from "./login"; -import { changePassword, startChangePassword } from "./password"; -import { changePasswordScreen, changePasswordScreenExpect } from "./password-screen"; -import { PasswordUser } from "./user"; +import {loginWithPassword} from "./login"; +import {startChangePassword} from "./password"; +import {changePasswordScreen, changePasswordScreenExpect} from "./password-screen"; +import {PasswordUser} from "./user"; // Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, ".env.local") }); +dotenv.config({path: path.resolve(__dirname, ".env.local")}); const test = base.extend<{ user: PasswordUser }>({ - user: async ({ page }, use) => { - const user = new PasswordUser({ - email: "password-changed@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - }); - await user.ensure(page); - await use(user); - }, + user: async ({page}, use) => { + const user = new PasswordUser({ + email: "password-changed@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + }); + await user.ensure(page); + await use(user); + }, }); -test("username and password changed login", async ({ user, page }) => { - const changedPw = "ChangedPw1!"; - await loginWithPassword(page, user.getUsername(), user.getPassword()); +test("username and password changed login", async ({user, page}) => { + // commented, fix in https://github.com/zitadel/zitadel/pull/8807 + /* + const changedPw = "ChangedPw1!"; + await loginWithPassword(page, user.getUsername(), user.getPassword()); - // wait for projection of token - await page.waitForTimeout(2000); + // wait for projection of token + await page.waitForTimeout(2000); - await changePassword(page, user.getUsername(), changedPw); - await loginScreenExpect(page, user.getFullName()); + await changePassword(page, user.getUsername(), changedPw); + await loginScreenExpect(page, user.getFullName()); - await loginWithPassword(page, user.getUsername(), changedPw); - await loginScreenExpect(page, user.getFullName()); + await loginWithPassword(page, user.getUsername(), changedPw); + await loginScreenExpect(page, user.getFullName()); + */ }); -test("password not with desired complexity", async ({ user, page }) => { - const changedPw1 = "change"; - const changedPw2 = "chang"; - await loginWithPassword(page, user.getUsername(), user.getPassword()); - await startChangePassword(page, user.getUsername()); - await changePasswordScreen(page, changedPw1, changedPw2); - await changePasswordScreenExpect(page, changedPw1, changedPw2, false, false, false, false, true, false); +test("password not with desired complexity", async ({user, page}) => { + const changedPw1 = "change"; + const changedPw2 = "chang"; + await loginWithPassword(page, user.getUsername(), user.getPassword()); + await startChangePassword(page, user.getUsername()); + await changePasswordScreen(page, changedPw1, changedPw2); + await changePasswordScreenExpect(page, changedPw1, changedPw2, false, false, false, false, true, false); }); From adaf3e56dfb52fd880922a07024c159a5f446550 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:15:27 +0100 Subject: [PATCH 445/640] chore: fixes to tests --- .../tests/username-password-changed.spec.ts | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index 089e365619..5e2b31344b 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -1,31 +1,31 @@ -import {test as base} from "@playwright/test"; +import { test as base } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; -import {loginWithPassword} from "./login"; -import {startChangePassword} from "./password"; -import {changePasswordScreen, changePasswordScreenExpect} from "./password-screen"; -import {PasswordUser} from "./user"; +import { loginWithPassword } from "./login"; +import { startChangePassword } from "./password"; +import { changePasswordScreen, changePasswordScreenExpect } from "./password-screen"; +import { PasswordUser } from "./user"; // Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, ".env.local")}); +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); const test = base.extend<{ user: PasswordUser }>({ - user: async ({page}, use) => { - const user = new PasswordUser({ - email: "password-changed@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - }); - await user.ensure(page); - await use(user); - }, + user: async ({ page }, use) => { + const user = new PasswordUser({ + email: "password-changed@example.com", + firstName: "first", + lastName: "last", + password: "Password1!", + organization: "", + }); + await user.ensure(page); + await use(user); + }, }); -test("username and password changed login", async ({user, page}) => { - // commented, fix in https://github.com/zitadel/zitadel/pull/8807 - /* +test("username and password changed login", async ({ user, page }) => { + // commented, fix in https://github.com/zitadel/zitadel/pull/8807 + /* const changedPw = "ChangedPw1!"; await loginWithPassword(page, user.getUsername(), user.getPassword()); @@ -40,11 +40,11 @@ test("username and password changed login", async ({user, page}) => { */ }); -test("password not with desired complexity", async ({user, page}) => { - const changedPw1 = "change"; - const changedPw2 = "chang"; - await loginWithPassword(page, user.getUsername(), user.getPassword()); - await startChangePassword(page, user.getUsername()); - await changePasswordScreen(page, changedPw1, changedPw2); - await changePasswordScreenExpect(page, changedPw1, changedPw2, false, false, false, false, true, false); +test("password not with desired complexity", async ({ user, page }) => { + const changedPw1 = "change"; + const changedPw2 = "chang"; + await loginWithPassword(page, user.getUsername(), user.getPassword()); + await startChangePassword(page, user.getUsername()); + await changePasswordScreen(page, changedPw1, changedPw2); + await changePasswordScreenExpect(page, changedPw1, changedPw2, false, false, false, false, true, false); }); From 18e3d3ba877546adad8b837d105254a44af10971 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:33:42 +0100 Subject: [PATCH 446/640] chore: fixes to tests --- .../tests/username-password-otp_email.spec.ts | 34 ++-------- .../tests/username-password-otp_sms.spec.ts | 63 ++++++++----------- .../tests/username-password-totp.spec.ts | 30 ++------- .../tests/username-password-u2f.spec.ts | 28 +-------- 4 files changed, 39 insertions(+), 116 deletions(-) diff --git a/acceptance/tests/username-password-otp_email.spec.ts b/acceptance/tests/username-password-otp_email.spec.ts index aab826f6fb..829003d7d0 100644 --- a/acceptance/tests/username-password-otp_email.spec.ts +++ b/acceptance/tests/username-password-otp_email.spec.ts @@ -1,28 +1,6 @@ -import { test as base } from "@playwright/test"; -import dotenv from "dotenv"; -import path from "path"; -import { OtpType, PasswordUserWithOTP } from "./user"; +import { test } from "@playwright/test"; -// Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, ".env.local") }); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({ page }, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.email, - }); - - await user.ensure(page); - await use(user); - }, -}); - -test("username, password and email otp login, enter code manually", async ({ user, page }) => { +test("username, password and email otp login, enter code manually", async ({ page }) => { // Given email otp is enabled on the organizaiton of the user // Given the user has only email otp configured as second factor // User enters username @@ -32,7 +10,7 @@ test("username, password and email otp login, enter code manually", async ({ use // User is redirected to the app (default redirect url) }); -test("username, password and email otp login, click link in email", async ({ user, page }) => { +test("username, password and email otp login, click link in email", async ({ page }) => { // Given email otp is enabled on the organizaiton of the user // Given the user has only email otp configured as second factor // User enters username @@ -42,7 +20,7 @@ test("username, password and email otp login, click link in email", async ({ use // User is redirected to the app (default redirect url) }); -test("username, password and email otp login, resend code", async ({ user, page }) => { +test("username, password and email otp login, resend code", async ({ page }) => { // Given email otp is enabled on the organizaiton of the user // Given the user has only email otp configured as second factor // User enters username @@ -54,7 +32,7 @@ test("username, password and email otp login, resend code", async ({ user, page // User is redirected to the app (default redirect url) }); -test("username, password and email otp login, wrong code", async ({ user, page }) => { +test("username, password and email otp login, wrong code", async ({ page }) => { // Given email otp is enabled on the organizaiton of the user // Given the user has only email otp configured as second factor // User enters username @@ -64,7 +42,7 @@ test("username, password and email otp login, wrong code", async ({ user, page } // Error message - "Invalid code" is shown }); -test("username, password and email otp login, multiple mfa options", async ({ user, page }) => { +test("username, password and email otp login, multiple mfa options", async ({ page }) => { // Given email otp and sms otp is enabled on the organizaiton of the user // Given the user has email and sms otp configured as second factor // User enters username diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index a839ca1450..fba9b8d9c5 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -1,43 +1,32 @@ -/* -test("username, password and otp login", async ({ user, page }) => { - //const server = startSink() - await loginWithPassword(page, user.getUsername(), user.getPassword()); +import { test } from "@playwright/test"; - await loginScreenExpect(page, user.getFullName()); - //server.close() +test("username, password and sms otp login", async ({ page }) => { + // Given sms otp is enabled on the organizaiton of the user + // Given the user has only sms otp configured as second factor + // User enters username + // User enters password + // User receives an sms with a verification code + // User enters the code into the ui + // User is redirected to the app (default redirect url) }); -test("username, password and sms otp login", async ({user, page}) => { - // Given sms otp is enabled on the organizaiton of the user - // Given the user has only sms otp configured as second factor - - // User enters username - // User enters password - // User receives an sms with a verification code - // User enters the code into the ui - // User is redirected to the app (default redirect url) +test("username, password and sms otp login, resend code", async ({ page }) => { + // Given sms otp is enabled on the organizaiton of the user + // Given the user has only sms otp configured as second factor + // User enters username + // User enters password + // User receives an sms with a verification code + // User clicks resend code + // User receives a new sms with a verification code + // User is redirected to the app (default redirect url) }); -test("username, password and sms otp login, resend code", async ({user, page}) => { - // Given sms otp is enabled on the organizaiton of the user - // Given the user has only sms otp configured as second factor - - // User enters username - // User enters password - // User receives an sms with a verification code - // User clicks resend code - // User receives a new sms with a verification code - // User is redirected to the app (default redirect url) +test("username, password and sms otp login, wrong code", async ({ page }) => { + // Given sms otp is enabled on the organizaiton of the user + // Given the user has only sms otp configured as second factor + // User enters username + // User enters password + // User receives an sms with a verification code + // User enters a wrond code + // Error message - "Invalid code" is shown }); - -test("username, password and sms otp login, wrong code", async ({user, page}) => { - // Given sms otp is enabled on the organizaiton of the user - // Given the user has only sms otp configured as second factor - - // User enters username - // User enters password - // User receives an sms with a verification code - // User enters a wrond code - // Error message - "Invalid code" is shown -}); -*/ diff --git a/acceptance/tests/username-password-totp.spec.ts b/acceptance/tests/username-password-totp.spec.ts index ee59d72030..8079e33e30 100644 --- a/acceptance/tests/username-password-totp.spec.ts +++ b/acceptance/tests/username-password-totp.spec.ts @@ -1,28 +1,6 @@ -import { test as base } from "@playwright/test"; -import dotenv from "dotenv"; -import path from "path"; -import { OtpType, PasswordUserWithOTP } from "./user"; +import { test } from "@playwright/test"; -// Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, ".env.local") }); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({ page }, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, -}); - -test("username, password and totp login", async ({ user, page }) => { +test("username, password and totp login", async ({ page }) => { // Given totp is enabled on the organizaiton of the user // Given the user has only totp configured as second factor // User enters username @@ -32,7 +10,7 @@ test("username, password and totp login", async ({ user, page }) => { // User is redirected to the app (default redirect url) }); -test("username, password and totp otp login, wrong code", async ({ user, page }) => { +test("username, password and totp otp login, wrong code", async ({ page }) => { // Given totp is enabled on the organizaiton of the user // Given the user has only totp configured as second factor // User enters username @@ -42,7 +20,7 @@ test("username, password and totp otp login, wrong code", async ({ user, page }) // Error message - "Invalid code" is shown }); -test("username, password and totp login, multiple mfa options", async ({ user, page }) => { +test("username, password and totp login, multiple mfa options", async ({ page }) => { // Given totp and email otp is enabled on the organizaiton of the user // Given the user has totp and email otp configured as second factor // User enters username diff --git a/acceptance/tests/username-password-u2f.spec.ts b/acceptance/tests/username-password-u2f.spec.ts index 1e297ad31d..f1b5b7847b 100644 --- a/acceptance/tests/username-password-u2f.spec.ts +++ b/acceptance/tests/username-password-u2f.spec.ts @@ -1,28 +1,6 @@ -import { test as base } from "@playwright/test"; -import dotenv from "dotenv"; -import path from "path"; -import { OtpType, PasswordUserWithOTP } from "./user"; +import { test } from "@playwright/test"; -// Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, ".env.local") }); - -const test = base.extend<{ user: PasswordUserWithOTP }>({ - user: async ({ page }, use) => { - const user = new PasswordUserWithOTP({ - email: "otp_sms@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", - organization: "", - type: OtpType.sms, - }); - - await user.ensure(page); - await use(user); - }, -}); - -test("username, password and u2f login", async ({ user, page }) => { +test("username, password and u2f login", async ({ page }) => { // Given u2f is enabled on the organizaiton of the user // Given the user has only u2f configured as second factor // User enters username @@ -32,7 +10,7 @@ test("username, password and u2f login", async ({ user, page }) => { // User is redirected to the app (default redirect url) }); -test("username, password and u2f login, multiple mfa options", async ({ user, page }) => { +test("username, password and u2f login, multiple mfa options", async ({ page }) => { // Given u2f and semailms otp is enabled on the organizaiton of the user // Given the user has u2f and email otp configured as second factor // User enters username From dcee01d09fb708f9b2b760478407240e510d7f22 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 19 Nov 2024 15:57:40 +0100 Subject: [PATCH 447/640] cleanup register, check for allowed methods --- apps/login/locales/de.json | 8 +++ apps/login/locales/en.json | 8 +++ apps/login/locales/es.json | 8 +++ apps/login/locales/it.json | 8 +++ apps/login/src/app/(login)/register/page.tsx | 32 ++++----- .../app/(login)/register/password/page.tsx | 70 +++++++++++++++++++ .../register-form-without-password.tsx | 31 +++++--- 7 files changed, 135 insertions(+), 30 deletions(-) create mode 100644 apps/login/src/app/(login)/register/password/page.tsx diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index c6576de000..62691051a9 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -122,6 +122,14 @@ } }, "register": { + "disabled": { + "title": "Registrierung deaktiviert", + "description": "Die Registrierung ist deaktiviert. Bitte wenden Sie sich an den Administrator." + }, + "missingdata": { + "title": "Registrierung fehlgeschlagen", + "description": "Einige Daten fehlen. Bitte überprüfen Sie Ihre Eingaben." + }, "title": "Registrieren", "description": "Erstellen Sie Ihr ZITADEL-Konto.", "selectMethod": "Wählen Sie die Methode, mit der Sie sich authentifizieren möchten", diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 4bb7bfe552..68fa95e810 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -122,6 +122,14 @@ } }, "register": { + "disabled": { + "title": "Registration disabled", + "description": "The registration is disabled. Please contact your administrator." + }, + "missingdata": { + "title": "Missing data", + "description": "Provide email, first and last name to register." + }, "title": "Register", "description": "Create your ZITADEL account.", "selectMethod": "Select the method you would like to authenticate", diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index e7f093dc5d..4b658bbdd0 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -122,6 +122,14 @@ } }, "register": { + "disabled": { + "title": "Registro deshabilitado", + "description": "Registrarse está deshabilitado en este momento." + }, + "missingdata": { + "title": "Datos faltantes", + "description": "No se proporcionaron datos suficientes para el registro." + }, "title": "Registrarse", "description": "Crea tu cuenta ZITADEL.", "selectMethod": "Selecciona el método con el que deseas autenticarte", diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index 0b488e00a2..2e6a7686b2 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -122,6 +122,14 @@ } }, "register": { + "disabled": { + "title": "Registration disabled", + "description": "Registrazione disabilitata. Contatta l'amministratore di sistema per assistenza." + }, + "missingdata": { + "title": "Registrazione", + "description": "Inserisci i tuoi dati per registrarti." + }, "title": "Registrati", "description": "Crea il tuo account ZITADEL.", "selectMethod": "Seleziona il metodo con cui desideri autenticarti", diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index f23582ae3f..06cb928d9a 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -1,10 +1,10 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { RegisterFormWithoutPassword } from "@/components/register-form-without-password"; -import { SetRegisterPasswordForm } from "@/components/set-register-password-form"; import { getBrandingSettings, getDefaultOrg, getLegalAndSupportSettings, + getLoginSettings, getPasswordComplexitySettings, } from "@/lib/zitadel"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; @@ -36,25 +36,18 @@ export default async function Page({ const branding = await getBrandingSettings(organization); - return setPassword ? ( - -
    -

    {t("password.title")}

    -

    {t("description")}

    + const loginSettings = await getLoginSettings(organization); - {legal && passwordComplexitySettings && ( - - )} -
    -
    - ) : ( + if (!loginSettings?.allowRegister) { + return ( + +
    {t("disabled.title")}
    +

    {t("disabled.description")}

    +
    + ); + } + + return (

    {t("title")}

    @@ -68,6 +61,7 @@ export default async function Page({ lastname={lastname} email={email} authRequestId={authRequestId} + loginSettings={loginSettings} > )}
    diff --git a/apps/login/src/app/(login)/register/password/page.tsx b/apps/login/src/app/(login)/register/password/page.tsx new file mode 100644 index 0000000000..66843d45ac --- /dev/null +++ b/apps/login/src/app/(login)/register/password/page.tsx @@ -0,0 +1,70 @@ +import { DynamicTheme } from "@/components/dynamic-theme"; +import { SetRegisterPasswordForm } from "@/components/set-register-password-form"; +import { + getBrandingSettings, + getDefaultOrg, + getLegalAndSupportSettings, + getLoginSettings, + getPasswordComplexitySettings, +} from "@/lib/zitadel"; +import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; +import { getLocale, getTranslations } from "next-intl/server"; + +export default async function Page({ + searchParams, +}: { + searchParams: Record; +}) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "register" }); + + let { firstname, lastname, email, organization, authRequestId } = + searchParams; + + if (!organization) { + const org: Organization | null = await getDefaultOrg(); + if (org) { + organization = org.id; + } + } + + const missingData = !firstname || !lastname || !email; + + const legal = await getLegalAndSupportSettings(organization); + const passwordComplexitySettings = + await getPasswordComplexitySettings(organization); + + const branding = await getBrandingSettings(organization); + + const loginSettings = await getLoginSettings(organization); + + return missingData ? ( + +
    {t("missingdata.title")}
    +

    {t("missingdata.description")}

    +
    + ) : loginSettings?.allowRegister && loginSettings.allowUsernamePassword ? ( + +
    +

    {t("password.title")}

    +

    {t("description")}

    + + {legal && passwordComplexitySettings && ( + + )} +
    +
    + ) : ( + +
    {t("disabled.title")}
    +

    {t("disabled.description")}

    +
    + ); +} diff --git a/apps/login/src/components/register-form-without-password.tsx b/apps/login/src/components/register-form-without-password.tsx index 52b6c3b9a4..8392c9e903 100644 --- a/apps/login/src/components/register-form-without-password.tsx +++ b/apps/login/src/components/register-form-without-password.tsx @@ -2,6 +2,10 @@ import { registerUser } from "@/lib/server/register"; import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; +import { + LoginSettings, + PasskeysType, +} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; @@ -32,6 +36,7 @@ type Props = { email?: string; organization?: string; authRequestId?: string; + loginSettings?: LoginSettings; }; export function RegisterFormWithoutPassword({ @@ -41,6 +46,7 @@ export function RegisterFormWithoutPassword({ lastname, organization, authRequestId, + loginSettings, }: Props) { const t = useTranslations("register"); @@ -99,7 +105,9 @@ export function RegisterFormWithoutPassword({ } if (withPassword) { - return router.push(`/register?` + new URLSearchParams(registerParams)); + return router.push( + `/register/password?` + new URLSearchParams(registerParams), + ); } else { return submitAndRegister(value); } @@ -143,29 +151,30 @@ export function RegisterFormWithoutPassword({ />
    - {legal && ( )} -

    {t("selectMethod")}

    - -
    - -
    + {/* show chooser if both methods are allowed */} + {loginSettings && + loginSettings.allowUsernamePassword && + loginSettings.passkeysType === PasskeysType.ALLOWED && ( +
    + +
    + )} {error && (
    {error}
    )} -
    @@ -226,11 +227,12 @@ export function LoginOTP({ {...register("code", { required: "This field is required" })} label="Code" autoComplete="one-time-code" + data-testid="code-text-input" />
    {error && ( -
    +
    {error}
    )} diff --git a/package.json b/package.json index 3636acdfb6..2760d8e04b 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "changeset": "changeset", "version-packages": "changeset version", "release": "turbo run build --filter=login^... && changeset publish", - "run-zitadel": "docker compose -f ./acceptance/docker-compose.yaml run setup" + "run-zitadel": "docker compose -f ./acceptance/docker-compose.yaml run setup", + "run-sink": "docker compose -f ./acceptance/docker-compose.yaml up -d sink" }, "pnpm": { "overrides": { @@ -29,6 +30,7 @@ } }, "devDependencies": { + "@faker-js/faker": "^9.2.0", "@changesets/cli": "^2.27.9", "@playwright/test": "^1.48.2", "@types/node": "^22.9.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0381141c34..283bcc4864 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@changesets/cli': specifier: ^2.27.9 version: 2.27.9 + '@faker-js/faker': + specifier: ^9.2.0 + version: 9.2.0 '@playwright/test': specifier: ^1.48.2 version: 1.48.2 @@ -28,7 +31,7 @@ importers: version: link:packages/zitadel-prettier-config axios: specifier: ^1.7.7 - version: 1.7.7 + version: 1.7.7(debug@4.3.7) dotenv: specifier: ^16.4.5 version: 16.4.5 @@ -847,6 +850,10 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@faker-js/faker@9.2.0': + resolution: {integrity: sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==} + engines: {node: '>=18.0.0', npm: '>=9.0.0'} + '@fastify/busboy@2.1.1': resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} @@ -4633,7 +4640,7 @@ snapshots: '@babel/traverse': 7.25.9 '@babel/types': 7.26.0 convert-source-map: 2.0.0 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -4720,7 +4727,7 @@ snapshots: '@babel/parser': 7.26.2 '@babel/template': 7.25.9 '@babel/types': 7.26.0 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5110,7 +5117,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -5123,6 +5130,8 @@ snapshots: '@eslint/js@8.57.1': {} + '@faker-js/faker@9.2.0': {} + '@fastify/busboy@2.1.1': {} '@floating-ui/core@1.6.8': @@ -5209,7 +5218,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -5756,7 +5765,7 @@ snapshots: agent-base@7.1.1: dependencies: - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -5931,14 +5940,6 @@ snapshots: axe-core@4.10.0: {} - axios@1.7.7: - dependencies: - follow-redirects: 1.15.6 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - axios@1.7.7(debug@4.3.7): dependencies: follow-redirects: 1.15.6(debug@4.3.7) @@ -6285,10 +6286,6 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.3.7: - dependencies: - ms: 2.1.3 - debug@4.3.7(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -6765,7 +6762,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -6955,8 +6952,6 @@ snapshots: flatted@3.3.1: {} - follow-redirects@1.15.6: {} - follow-redirects@1.15.6(debug@4.3.7): optionalDependencies: debug: 4.3.7(supports-color@5.5.0) @@ -7191,7 +7186,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -7211,7 +7206,7 @@ snapshots: https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.1 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -8735,7 +8730,7 @@ snapshots: cac: 6.7.14 chokidar: 4.0.1 consola: 3.2.3 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) esbuild: 0.24.0 joycon: 3.1.1 picocolors: 1.1.1 @@ -8893,7 +8888,7 @@ snapshots: vite-node@2.1.4(@types/node@22.9.0)(sass@1.80.7): dependencies: cac: 6.7.14 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) pathe: 1.1.2 vite: 5.4.11(@types/node@22.9.0)(sass@1.80.7) transitivePeerDependencies: @@ -8909,7 +8904,7 @@ snapshots: vite-tsconfig-paths@5.1.2(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(sass@1.80.7)): dependencies: - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) globrex: 0.1.2 tsconfck: 3.1.4(typescript@5.6.3) optionalDependencies: @@ -8938,7 +8933,7 @@ snapshots: '@vitest/spy': 2.1.4 '@vitest/utils': 2.1.4 chai: 5.1.2 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) expect-type: 1.1.0 magic-string: 0.30.12 pathe: 1.1.2 From 5bc15174d14688923178c8467c9d1fe5059986af Mon Sep 17 00:00:00 2001 From: Slava Date: Wed, 20 Nov 2024 16:15:56 -0800 Subject: [PATCH 452/640] Add support for http based instances in middleware --- apps/login/src/middleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 28be3cbdbe..93cb65581c 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -23,7 +23,7 @@ export function middleware(request: NextRequest) { // this is a workaround for the next.js server not forwarding the host header requestHeaders.set( "x-zitadel-instance-host", - `${INSTANCE}`.replace("https://", ""), + `${INSTANCE}`.replace(/^https?:\/\//, ""), ); const responseHeaders = new Headers(); From ea95a20f361e5d89b3c25a7ca9f614f03bdfa865 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 21 Nov 2024 10:48:00 +0100 Subject: [PATCH 453/640] cleanup utils --- .../src/app/(login)/otp/[method]/page.tsx | 5 +- .../src/app/(login)/otp/[method]/set/page.tsx | 4 ++ apps/login/src/app/(login)/passkey/page.tsx | 9 ++- apps/login/src/components/login-otp.tsx | 42 +++++++++----- apps/login/src/components/login-passkey.tsx | 40 ++++++++----- apps/login/src/components/register-u2f.tsx | 39 ++++++++----- apps/login/src/components/totp-register.tsx | 42 +++++++++----- apps/login/src/lib/{login.ts => client.ts} | 44 +++++++------- apps/login/src/lib/server/register.ts | 13 ++++- apps/login/src/lib/server/session.ts | 58 ++++++++++++++----- 10 files changed, 199 insertions(+), 97 deletions(-) rename apps/login/src/lib/{login.ts => client.ts} (51%) diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 7109d58f11..63bdbf551d 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -3,7 +3,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { LoginOTP } from "@/components/login-otp"; import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; -import { getBrandingSettings } from "@/lib/zitadel"; +import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ @@ -29,6 +29,8 @@ export default async function Page({ const branding = await getBrandingSettings(organization); + const loginSettings = await getLoginSettings(organization); + return (
    @@ -65,6 +67,7 @@ export default async function Page({ authRequestId={authRequestId} organization={organization} method={method} + loginSettings={loginSettings} > )}
    diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index b08a1facde..9fb279e5f9 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -9,6 +9,7 @@ import { addOTPEmail, addOTPSMS, getBrandingSettings, + getLoginSettings, registerTOTP, } from "@/lib/zitadel"; import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; @@ -32,6 +33,8 @@ export default async function Page({ const { method } = params; const branding = await getBrandingSettings(organization); + const loginSettings = await getLoginSettings(organization); + const session = await loadMostRecentSession({ loginName, organization, @@ -137,6 +140,7 @@ export default async function Page({ authRequestId={authRequestId} organization={organization} checkAfter={checkAfter === "true"} + loginSettings={loginSettings} >
    {" "} diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index fc0f701e80..09605ad382 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -4,7 +4,11 @@ import { LoginPasskey } from "@/components/login-passkey"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { loadMostRecentSession } from "@/lib/session"; -import { getBrandingSettings, getSession } from "@/lib/zitadel"; +import { + getBrandingSettings, + getLoginSettings, + getSession, +} from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; export default async function Page({ @@ -37,6 +41,8 @@ export default async function Page({ const branding = await getBrandingSettings(organization); + const loginSettings = await getLoginSettings(organization); + return (
    @@ -61,6 +67,7 @@ export default async function Page({ authRequestId={authRequestId} altPassword={altPassword === "true"} organization={organization} + loginSettings={loginSettings} /> )}
    diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index cc1e272be9..74c0c0b262 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -1,10 +1,11 @@ "use client"; -import { finishFlow } from "@/lib/login"; +import { getNextUrl } from "@/lib/client"; import { updateSession } from "@/lib/server/session"; import { create } from "@zitadel/client"; import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; @@ -23,6 +24,7 @@ type Props = { organization?: string; method: string; code?: string; + loginSettings?: LoginSettings; }; type Inputs = { @@ -36,6 +38,7 @@ export function LoginOTP({ organization, method, code, + loginSettings, }: Props) { const t = useTranslations("otp"); @@ -153,20 +156,31 @@ export function LoginOTP({ } function setCodeAndContinue(values: Inputs, organization?: string) { - return submitCode(values, organization).then((response) => { + return submitCode(values, organization).then(async (response) => { if (response) { - return authRequestId && response.sessionId - ? finishFlow({ - sessionId: response.sessionId, - authRequestId: authRequestId, - organization: response.factors?.user?.organizationId, - }) - : response.factors?.user - ? finishFlow({ - loginName: response.factors.user.loginName, - organization: response.factors?.user?.organizationId, - }) - : null; + const url = + authRequestId && response.sessionId + ? await getNextUrl( + { + sessionId: response.sessionId, + authRequestId: authRequestId, + organization: response.factors?.user?.organizationId, + }, + loginSettings?.defaultRedirectUri, + ) + : response.factors?.user + ? await getNextUrl( + { + loginName: response.factors.user.loginName, + organization: response.factors?.user?.organizationId, + }, + loginSettings?.defaultRedirectUri, + ) + : null; + + if (url) { + router.push(url); + } } }); } diff --git a/apps/login/src/components/login-passkey.tsx b/apps/login/src/components/login-passkey.tsx index c4dfb7dfe1..dda6b1ef56 100644 --- a/apps/login/src/components/login-passkey.tsx +++ b/apps/login/src/components/login-passkey.tsx @@ -1,7 +1,7 @@ "use client"; import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64"; -import { finishFlow } from "@/lib/login"; +import { getNextUrl } from "@/lib/client"; import { updateSession } from "@/lib/server/session"; import { create } from "@zitadel/client"; import { @@ -9,6 +9,7 @@ import { UserVerificationRequirement, } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; @@ -25,6 +26,7 @@ type Props = { altPassword: boolean; login?: boolean; organization?: string; + loginSettings?: LoginSettings; }; export function LoginPasskey({ @@ -34,6 +36,7 @@ export function LoginPasskey({ altPassword, organization, login = true, + loginSettings, }: Props) { const t = useTranslations("passkey"); @@ -177,18 +180,29 @@ export function LoginPasskey({ }; return submitLogin(data).then(async (resp) => { - return authRequestId && resp?.sessionId - ? await finishFlow({ - sessionId: resp.sessionId, - authRequestId: authRequestId, - organization: organization, - }) - : resp?.factors?.user?.loginName - ? await finishFlow({ - loginName: resp.factors.user.loginName, - organization: organization, - }) - : null; + const url = + authRequestId && resp?.sessionId + ? await getNextUrl( + { + sessionId: resp.sessionId, + authRequestId: authRequestId, + organization: organization, + }, + loginSettings?.defaultRedirectUri, + ) + : resp?.factors?.user?.loginName + ? await getNextUrl( + { + loginName: resp.factors.user.loginName, + organization: organization, + }, + loginSettings?.defaultRedirectUri, + ) + : null; + + if (url) { + router.push(url); + } }); }); } diff --git a/apps/login/src/components/register-u2f.tsx b/apps/login/src/components/register-u2f.tsx index b749c72f13..8c8cd503d9 100644 --- a/apps/login/src/components/register-u2f.tsx +++ b/apps/login/src/components/register-u2f.tsx @@ -1,8 +1,9 @@ "use client"; import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64"; -import { finishFlow } from "@/lib/login"; +import { getNextUrl } from "@/lib/client"; import { addU2F, verifyU2F } from "@/lib/server/u2f"; +import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { RegisterU2FResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; @@ -18,6 +19,7 @@ type Props = { authRequestId?: string; organization?: string; checkAfter: boolean; + loginSettings?: LoginSettings; }; export function RegisterU2f({ @@ -26,6 +28,7 @@ export function RegisterU2f({ organization, authRequestId, checkAfter, + loginSettings, }: Props) { const t = useTranslations("u2f"); @@ -165,18 +168,28 @@ export function RegisterU2f({ return router.push(`/u2f?` + paramsToContinue); } else { - return authRequestId && sessionId - ? finishFlow({ - sessionId: sessionId, - authRequestId: authRequestId, - organization: organization, - }) - : loginName - ? finishFlow({ - loginName: loginName, - organization: organization, - }) - : null; + const url = + authRequestId && sessionId + ? await getNextUrl( + { + sessionId: sessionId, + authRequestId: authRequestId, + organization: organization, + }, + loginSettings?.defaultRedirectUri, + ) + : loginName + ? await getNextUrl( + { + loginName: loginName, + organization: organization, + }, + loginSettings?.defaultRedirectUri, + ) + : null; + if (url) { + return router.push(url); + } } } } diff --git a/apps/login/src/components/totp-register.tsx b/apps/login/src/components/totp-register.tsx index ee40f202f1..e851998171 100644 --- a/apps/login/src/components/totp-register.tsx +++ b/apps/login/src/components/totp-register.tsx @@ -1,6 +1,7 @@ "use client"; -import { finishFlow } from "@/lib/login"; +import { getNextUrl } from "@/lib/client"; import { verifyTOTP } from "@/lib/server-actions"; +import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { useTranslations } from "next-intl"; import Link from "next/link"; import { useRouter } from "next/navigation"; @@ -25,6 +26,7 @@ type Props = { authRequestId?: string; organization?: string; checkAfter?: boolean; + loginSettings?: LoginSettings; }; export function TotpRegister({ uri, @@ -34,6 +36,7 @@ export function TotpRegister({ authRequestId, organization, checkAfter, + loginSettings, }: Props) { const t = useTranslations("otp"); @@ -51,7 +54,7 @@ export function TotpRegister({ async function continueWithCode(values: Inputs) { setLoading(true); return verifyTOTP(values.code, loginName, organization) - .then((response) => { + .then(async () => { // if attribute is set, validate MFA after it is setup, otherwise proceed as usual (when mfa is enforced to login) if (checkAfter) { const params = new URLSearchParams({}); @@ -68,18 +71,29 @@ export function TotpRegister({ return router.push(`/otp/time-based?` + params); } else { - return authRequestId && sessionId - ? finishFlow({ - sessionId: sessionId, - authRequestId: authRequestId, - organization: organization, - }) - : loginName - ? finishFlow({ - loginName: loginName, - organization: organization, - }) - : null; + const url = + authRequestId && sessionId + ? await getNextUrl( + { + sessionId: sessionId, + authRequestId: authRequestId, + organization: organization, + }, + loginSettings?.defaultRedirectUri, + ) + : loginName + ? await getNextUrl( + { + loginName: loginName, + organization: organization, + }, + loginSettings?.defaultRedirectUri, + ) + : null; + + if (url) { + return router.push(url); + } } }) .catch((e) => { diff --git a/apps/login/src/lib/login.ts b/apps/login/src/lib/client.ts similarity index 51% rename from apps/login/src/lib/login.ts rename to apps/login/src/lib/client.ts index ec023b8a9c..7dc2b83cbb 100644 --- a/apps/login/src/lib/login.ts +++ b/apps/login/src/lib/client.ts @@ -1,6 +1,3 @@ -import { redirect } from "next/navigation"; -import { getLoginSettings } from "./zitadel"; - type FinishFlowCommand = | { sessionId: string; @@ -13,28 +10,29 @@ type FinishFlowCommand = * @param command * @returns */ -export async function finishFlow( +export async function getNextUrl( command: FinishFlowCommand & { organization?: string }, -) { + defaultRedirectUri?: string, +): Promise { if ("sessionId" in command && "authRequestId" in command) { - return redirect( + const url = `/login?` + - new URLSearchParams({ - sessionId: command.sessionId, - authRequest: command.authRequestId, - }), - ); - } - - const loginSettings = await getLoginSettings(command.organization); - if (loginSettings?.defaultRedirectUri) { - return redirect(loginSettings.defaultRedirectUri); - } - - return redirect( - `/signedin?` + new URLSearchParams({ - loginName: command.loginName, - }), - ); + sessionId: command.sessionId, + authRequest: command.authRequestId, + }); + return url; + } + + if (defaultRedirectUri) { + return defaultRedirectUri; + } + + const signedInUrl = + `/signedin?` + + new URLSearchParams({ + loginName: command.loginName, + }); + + return signedInUrl; } diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index eebb9ccd69..fc37466a56 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -1,7 +1,7 @@ "use server"; import { createSessionAndUpdateCookie } from "@/lib/server/cookie"; -import { addHumanUser } from "@/lib/zitadel"; +import { addHumanUser, getLoginSettings } from "@/lib/zitadel"; import { create } from "@zitadel/client"; import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { @@ -9,7 +9,7 @@ import { ChecksSchema, } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { redirect } from "next/navigation"; -import { finishFlow } from "../login"; +import { getNextUrl } from "../client"; type RegisterUserCommand = { email: string; @@ -73,7 +73,11 @@ export async function registerUser(command: RegisterUserCommand) { return redirect("/passkey/set?" + params); } else { - return finishFlow( + const loginSettings = await getLoginSettings( + session.factors.user.organizationId, + ); + + const url = await getNextUrl( command.authRequestId && session.id ? { sessionId: session.id, @@ -84,6 +88,9 @@ export async function registerUser(command: RegisterUserCommand) { loginName: session.factors.user.loginName, organization: session.factors.user.organizationId, }, + loginSettings?.defaultRedirectUri, ); + + return redirect(url); } } diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index aad5f97cc7..bb08b05d36 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -4,17 +4,22 @@ import { createSessionForIdpAndUpdateCookie, setSessionAndUpdateCookie, } from "@/lib/server/cookie"; -import { deleteSession, listAuthenticationMethodTypes } from "@/lib/zitadel"; +import { + deleteSession, + getLoginSettings, + listAuthenticationMethodTypes, +} from "@/lib/zitadel"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { redirect } from "next/navigation"; +import { getNextUrl } from "../client"; import { getMostRecentSessionCookie, getSessionCookieById, getSessionCookieByLoginName, removeSessionFromCookie, } from "../cookies"; -import { finishFlow } from "../login"; type CreateNewSessionCommand = { userId: string; @@ -43,7 +48,11 @@ export async function createNewSessionForIdp(options: CreateNewSessionCommand) { return { error: "Could not create session" }; } - return finishFlow( + const loginSettings = await getLoginSettings( + session.factors.user.organizationId, + ); + + const url = await getNextUrl( authRequestId && session.id ? { sessionId: session.id, @@ -54,25 +63,44 @@ export async function createNewSessionForIdp(options: CreateNewSessionCommand) { loginName: session.factors.user.loginName, organization: session.factors.user.organizationId, }, + loginSettings?.defaultRedirectUri, ); + + if (url) { + return redirect(url); + } } export async function continueWithSession({ authRequestId, ...session }: Session & { authRequestId?: string }) { - return authRequestId && session.id && session.factors?.user - ? finishFlow({ - sessionId: session.id, - authRequestId: authRequestId, - organization: session.factors.user.organizationId, - }) - : session.factors?.user - ? finishFlow({ - loginName: session.factors.user.loginName, - organization: session.factors.user.organizationId, - }) - : null; + const loginSettings = await getLoginSettings( + session.factors?.user?.organizationId, + ); + + const url = + authRequestId && session.id && session.factors?.user + ? await getNextUrl( + { + sessionId: session.id, + authRequestId: authRequestId, + organization: session.factors.user.organizationId, + }, + loginSettings?.defaultRedirectUri, + ) + : session.factors?.user + ? await getNextUrl( + { + loginName: session.factors.user.loginName, + organization: session.factors.user.organizationId, + }, + loginSettings?.defaultRedirectUri, + ) + : null; + if (url) { + return redirect(url); + } } export type UpdateSessionCommand = { From 21cd5e7c1cde5ba8bf0c4ca2296faa1a23e579f4 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 21 Nov 2024 10:53:23 +0100 Subject: [PATCH 454/640] initial stub for loginsettings --- .../zitadel.settings.v2.SettingsService.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json b/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json index 6ddf5989ed..816adbfe8c 100644 --- a/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json +++ b/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json @@ -46,5 +46,17 @@ } } } + }, + { + "service": "zitadel.settings.v2.SettingsService", + "method": "GetLoginSettings", + "out": { + "data": { + "settings": { + "passkeysType": 1, + "defaultRedirectUri": "" + } + } + } } ] From 68f7ea5ef04ab3d48db2b127042a99351c3bb12b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 21 Nov 2024 14:12:13 +0100 Subject: [PATCH 455/640] cleanup register --- apps/login/src/app/(login)/register/page.tsx | 8 +-- apps/login/src/components/password-form.tsx | 4 -- ...without-password.tsx => register-form.tsx} | 10 ++-- apps/login/src/lib/server/password.ts | 58 +++++++++---------- 4 files changed, 35 insertions(+), 45 deletions(-) rename apps/login/src/components/{register-form-without-password.tsx => register-form.tsx} (93%) diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 06cb928d9a..00adc174b2 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -1,5 +1,5 @@ import { DynamicTheme } from "@/components/dynamic-theme"; -import { RegisterFormWithoutPassword } from "@/components/register-form-without-password"; +import { RegisterForm } from "@/components/register-form"; import { getBrandingSettings, getDefaultOrg, @@ -28,8 +28,6 @@ export default async function Page({ } } - const setPassword = !!(firstname && lastname && email); - const legal = await getLegalAndSupportSettings(organization); const passwordComplexitySettings = await getPasswordComplexitySettings(organization); @@ -54,7 +52,7 @@ export default async function Page({

    {t("description")}

    {legal && passwordComplexitySettings && ( - + > )}
    diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index 5885ecc6d4..8c71523649 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -73,10 +73,6 @@ export function PasswordForm({ setError(response.error); return; } - - if (response && response.nextStep) { - return router.push(response.nextStep); - } } async function resetPasswordAndContinue() { diff --git a/apps/login/src/components/register-form-without-password.tsx b/apps/login/src/components/register-form.tsx similarity index 93% rename from apps/login/src/components/register-form-without-password.tsx rename to apps/login/src/components/register-form.tsx index 19abe482a6..0655a15f4b 100644 --- a/apps/login/src/components/register-form-without-password.tsx +++ b/apps/login/src/components/register-form.tsx @@ -39,7 +39,7 @@ type Props = { loginSettings?: LoginSettings; }; -export function RegisterFormWithoutPassword({ +export function RegisterForm({ legal, email, firstname, @@ -104,6 +104,7 @@ export function RegisterFormWithoutPassword({ registerParams.authRequestId = authRequestId; } + // redirect user to /register/password if password is chosen if (withPassword) { return router.push( `/register/password?` + new URLSearchParams(registerParams), @@ -116,7 +117,7 @@ export function RegisterFormWithoutPassword({ const { errors } = formState; const [tosAndPolicyAccepted, setTosAndPolicyAccepted] = useState(false); - + console.log(loginSettings); return (
    @@ -188,8 +189,9 @@ export function RegisterFormWithoutPassword({ const usePasswordToContinue: boolean = loginSettings?.allowUsernamePassword && loginSettings?.passkeysType === PasskeysType.ALLOWED - ? !!!(selected.name === methods[0].name) - : !!loginSettings?.allowUsernamePassword; + ? !!!(selected.name === methods[0].name) // choose selection if both available + : !!loginSettings?.allowUsernamePassword; // if password is chosen + // set password as default if only password is allowed return submitAndContinue(values, usePasswordToContinue); })} data-testid="submit-button" diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index c01b7fb4de..24ba10faf8 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -5,6 +5,7 @@ import { setSessionAndUpdateCookie, } from "@/lib/server/cookie"; import { + getLoginSettings, getUserByID, listAuthenticationMethodTypes, listUsers, @@ -20,6 +21,7 @@ import { User, UserState } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; import { redirect } from "next/navigation"; +import { getNextUrl } from "../client"; import { getSessionCookieByLoginName } from "../cookies"; type ResetPasswordCommand = { @@ -240,42 +242,34 @@ export async function sendPassword(command: UpdateSessionCommand) { // return router.push(`/passkey/set?` + params); // } else if (command.authRequestId && session.id) { - const params = new URLSearchParams({ - sessionId: session.id, - authRequest: command.authRequestId, - }); - - if (command.organization || session.factors?.user?.organizationId) { - params.append( - "organization", - command.organization ?? session.factors?.user?.organizationId, - ); - } - - return { nextStep: `/login?${params}` }; - } - - // without OIDC flow - const params = new URLSearchParams( - command.authRequestId - ? { - loginName: session.factors.user.loginName, - authRequestId: command.authRequestId, - organization: session.factors.user.organizationId, - } - : { - loginName: session.factors.user.loginName, - }, - ); - - if (command.organization || session.factors?.user?.organizationId) { - params.append( - "organization", + const loginSettings = await getLoginSettings( command.organization ?? session.factors?.user?.organizationId, ); + const nextUrl = await getNextUrl( + { + sessionId: session.id, + authRequestId: command.authRequestId, + organization: + command.organization ?? session.factors?.user?.organizationId, + }, + loginSettings?.defaultRedirectUri, + ); + + return redirect(nextUrl); } - return { nextStep: `/signedin?${params}` }; + const loginSettings = await getLoginSettings( + command.organization ?? session.factors?.user?.organizationId, + ); + const url = await getNextUrl( + { + loginName: session.factors.user.loginName, + organization: session.factors?.user?.organizationId, + }, + loginSettings?.defaultRedirectUri, + ); + + return redirect(url); } export async function changePassword(command: { From 7e1445dda26006c8f14d3b71a91148824288ba03 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 21 Nov 2024 14:16:45 +0100 Subject: [PATCH 456/640] stub to allow register --- .../mock/initial-stubs/zitadel.settings.v2.SettingsService.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json b/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json index 816adbfe8c..fa1e4018f5 100644 --- a/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json +++ b/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json @@ -53,7 +53,9 @@ "out": { "data": { "settings": { + "allowRegister": "true", "passkeysType": 1, + "allowUsernamePassword": true, "defaultRedirectUri": "" } } From b1f6257e1073203c97b8f6db4e5eed10c3c15f67 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 21 Nov 2024 14:17:56 +0100 Subject: [PATCH 457/640] remove nextstep on pwd pages --- apps/login/src/components/change-password-form.tsx | 5 ----- apps/login/src/components/set-password-form.tsx | 5 ----- 2 files changed, 10 deletions(-) diff --git a/apps/login/src/components/change-password-form.tsx b/apps/login/src/components/change-password-form.tsx index c2e891ffb8..7a08687ac0 100644 --- a/apps/login/src/components/change-password-form.tsx +++ b/apps/login/src/components/change-password-form.tsx @@ -12,7 +12,6 @@ import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { useTranslations } from "next-intl"; -import { redirect } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; import { Alert } from "./alert"; @@ -107,10 +106,6 @@ export function ChangePasswordForm({ return; } - if (passwordResponse && passwordResponse.nextStep) { - return redirect(passwordResponse.nextStep); - } - return; } diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 65405ed8f0..b194065d2a 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -11,7 +11,6 @@ import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { useTranslations } from "next-intl"; -import { redirect } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; import { Alert } from "./alert"; @@ -127,10 +126,6 @@ export function SetPasswordForm({ return; } - if (passwordResponse && passwordResponse.nextStep) { - return redirect(passwordResponse.nextStep); - } - return; } From 5931c56fb51d5992dbddb4955121f2655efd6507 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 21 Nov 2024 14:26:55 +0100 Subject: [PATCH 458/640] boolean --- .../mock/initial-stubs/zitadel.settings.v2.SettingsService.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json b/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json index fa1e4018f5..264b483c19 100644 --- a/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json +++ b/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json @@ -53,7 +53,7 @@ "out": { "data": { "settings": { - "allowRegister": "true", + "allowRegister": true, "passkeysType": 1, "allowUsernamePassword": true, "defaultRedirectUri": "" From de787899de1126b97fff1375ea957d5c6861c12a Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 21 Nov 2024 16:31:51 +0100 Subject: [PATCH 459/640] auth method change --- apps/login/locales/de.json | 4 + apps/login/locales/en.json | 4 + apps/login/locales/es.json | 4 + apps/login/locales/it.json | 4 + .../authentication-method-radio.tsx | 109 ++++++++---------- apps/login/src/components/register-form.tsx | 5 +- 6 files changed, 68 insertions(+), 62 deletions(-) diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 62691051a9..4d5f0c1530 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -122,6 +122,10 @@ } }, "register": { + "methods": { + "passkey": "Passkey", + "password": "Password" + }, "disabled": { "title": "Registrierung deaktiviert", "description": "Die Registrierung ist deaktiviert. Bitte wenden Sie sich an den Administrator." diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 68fa95e810..c7fd5e30b9 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -122,6 +122,10 @@ } }, "register": { + "methods": { + "passkey": "Passkey", + "password": "Password" + }, "disabled": { "title": "Registration disabled", "description": "The registration is disabled. Please contact your administrator." diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index 4b658bbdd0..e722db5812 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -122,6 +122,10 @@ } }, "register": { + "methods": { + "passkey": "Clave de acceso", + "password": "Contraseña" + }, "disabled": { "title": "Registro deshabilitado", "description": "Registrarse está deshabilitado en este momento." diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index 2e6a7686b2..9467f0ba84 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -122,6 +122,10 @@ } }, "register": { + "methods": { + "passkey": "Passkey", + "password": "Password" + }, "disabled": { "title": "Registration disabled", "description": "Registrazione disabilitata. Contatta l'amministratore di sistema per assistenza." diff --git a/apps/login/src/components/authentication-method-radio.tsx b/apps/login/src/components/authentication-method-radio.tsx index 9e0af95921..1b2af2d167 100644 --- a/apps/login/src/components/authentication-method-radio.tsx +++ b/apps/login/src/components/authentication-method-radio.tsx @@ -1,16 +1,16 @@ "use client"; import { RadioGroup } from "@headlessui/react"; +import { useTranslations } from "next-intl"; + +export enum AuthenticationMethod { + Passkey = "passkey", + Password = "password", +} export const methods = [ - { - name: "Passkeys", - description: "Authenticate with your device.", - }, - { - name: "Password", - description: "Authenticate with a password", - }, + AuthenticationMethod.Passkey, + AuthenticationMethod.Password, ]; export function AuthenticationMethodRadio({ @@ -20,58 +20,68 @@ export function AuthenticationMethodRadio({ selected: any; selectionChanged: (value: any) => void; }) { + const t = useTranslations("register"); + return (
    Server size -
    +
    {methods.map((method) => ( `${ active - ? "h-full ring-2 ring-opacity-60 ring-primary-light-500 dark:ring-white/20" - : "h-full " + ? "ring-2 ring-opacity-60 ring-primary-light-500 dark:ring-white/20" + : "" } ${ checked - ? "bg-background-light-400 dark:bg-background-dark-400" + ? "bg-background-light-400 dark:bg-background-dark-400 ring-2 ring-primary-light-500 dark:ring-primary-dark-500" : "bg-background-light-400 dark:bg-background-dark-400" } - relative border boder-divider-light dark:border-divider-dark flex cursor-pointer rounded-lg px-5 py-4 focus:outline-none hover:shadow-lg dark:hover:bg-white/10` + h-full flex-1 relative border boder-divider-light dark:border-divider-dark flex cursor-pointer rounded-lg px-5 py-4 focus:outline-none hover:shadow-lg dark:hover:bg-white/10` } > {({ active, checked }) => ( <> -
    -
    -
    - - {method.name} - - - {method.description} - {" "} - -
    -
    - {checked && ( -
    - -
    +
    + {method === "passkey" && ( + + + )} + {method === "password" && ( + + form-textbox-password + + + )} + + {t(`methods.${method}`)} +
    )} @@ -83,24 +93,3 @@ export function AuthenticationMethodRadio({
    ); } - -function CheckIcon(props: any) { - return ( - - - - - ); -} diff --git a/apps/login/src/components/register-form.tsx b/apps/login/src/components/register-form.tsx index 0655a15f4b..cf3f1631da 100644 --- a/apps/login/src/components/register-form.tsx +++ b/apps/login/src/components/register-form.tsx @@ -12,6 +12,7 @@ import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; import { Alert } from "./alert"; import { + AuthenticationMethod, AuthenticationMethodRadio, methods, } from "./authentication-method-radio"; @@ -60,7 +61,7 @@ export function RegisterForm({ }); const [loading, setLoading] = useState(false); - const [selected, setSelected] = useState(methods[0]); + const [selected, setSelected] = useState(methods[0]); const [error, setError] = useState(""); const router = useRouter(); @@ -189,7 +190,7 @@ export function RegisterForm({ const usePasswordToContinue: boolean = loginSettings?.allowUsernamePassword && loginSettings?.passkeysType === PasskeysType.ALLOWED - ? !!!(selected.name === methods[0].name) // choose selection if both available + ? !!!(selected === methods[0]) // choose selection if both available : !!loginSettings?.allowUsernamePassword; // if password is chosen // set password as default if only password is allowed return submitAndContinue(values, usePasswordToContinue); From f38b8b753caee255b281bdef33b9e04f56959d32 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:01:12 +0100 Subject: [PATCH 460/640] chore: add sms otp and password set acceptance tests --- README.md | 18 +++++++ acceptance/tests/code.ts | 6 +-- acceptance/tests/login.ts | 6 +-- acceptance/tests/password-screen.ts | 25 ++++++++++ acceptance/tests/password.ts | 13 ++++- acceptance/tests/sink.ts | 29 ++++++++++- acceptance/tests/user.ts | 13 ++--- acceptance/tests/username-passkey.spec.ts | 9 ---- .../tests/username-password-changed.spec.ts | 2 +- .../tests/username-password-otp_email.spec.ts | 4 +- .../tests/username-password-otp_sms.spec.ts | 25 ++++------ .../tests/username-password-set.spec.ts | 50 +++++++++++++++++++ apps/login/src/components/login-otp.tsx | 2 +- apps/login/src/components/password-form.tsx | 1 + .../src/components/set-password-form.tsx | 8 ++- 15 files changed, 165 insertions(+), 46 deletions(-) create mode 100644 acceptance/tests/username-password-set.spec.ts diff --git a/README.md b/README.md index f4878ecacb..429ce84c78 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,24 @@ pnpm test To satisfy your unique workflow requirements, check out the package.json in the root directory for more detailed scripts. +### Run Login UI Acceptance tests + +To run the acceptance tests you need a running ZITADEL environment and a component which receives HTTP requests for the emails and sms's. +This component should also be able to return the content of these notifications, as the codes and links are used in the login flows. +There is a basic implementation in Golang available under [the sink package](./acceptance/sink). + +To setup ZITADEL with the additional Sink container for handling the notifications: + +```sh +pnpm run-sink +``` + +Then you can start the acceptance tests with: + +```sh +pnpm test:acceptance +``` + ### Deploy to Vercel To deploy your own version on Vercel, navigate to your instance and create a service user. diff --git a/acceptance/tests/code.ts b/acceptance/tests/code.ts index ff8643e404..1ae8f69791 100644 --- a/acceptance/tests/code.ts +++ b/acceptance/tests/code.ts @@ -1,11 +1,11 @@ import { Page } from "@playwright/test"; import { codeScreen } from "./code-screen"; -import { getCodeFromSink } from "./sink"; +import { getOtpFromSink } from "./sink"; -export async function codeFromSink(page: Page, key: string) { +export async function otpFromSink(page: Page, key: string) { // wait for send of the code await page.waitForTimeout(3000); - const c = await getCodeFromSink(key); + const c = await getOtpFromSink(key); await code(page, c); } diff --git a/acceptance/tests/login.ts b/acceptance/tests/login.ts index f86bd712b2..9af0ecfb76 100644 --- a/acceptance/tests/login.ts +++ b/acceptance/tests/login.ts @@ -1,5 +1,5 @@ import { expect, Page } from "@playwright/test"; -import { codeFromSink } from "./code"; +import { otpFromSink } from "./code"; import { loginname } from "./loginname"; import { password } from "./password"; @@ -26,10 +26,10 @@ export async function loginScreenExpect(page: Page, fullName: string) { export async function loginWithPasswordAndEmailOTP(page: Page, username: string, password: string, email: string) { await loginWithPassword(page, username, password); - await codeFromSink(page, email); + await otpFromSink(page, email); } export async function loginWithPasswordAndPhoneOTP(page: Page, username: string, password: string, phone: string) { await loginWithPassword(page, username, password); - await codeFromSink(page, phone); + await otpFromSink(page, phone); } diff --git a/acceptance/tests/password-screen.ts b/acceptance/tests/password-screen.ts index 49e8843822..57334a07d2 100644 --- a/acceptance/tests/password-screen.ts +++ b/acceptance/tests/password-screen.ts @@ -1,5 +1,7 @@ import { expect, Page } from "@playwright/test"; +import { getCodeFromSink } from "./sink"; +const codeField = "code-text-input"; const passwordField = "password-text-input"; const passwordConfirmField = "password-confirm-text-input"; const lengthCheck = "length-check"; @@ -55,3 +57,26 @@ async function checkContent(page: Page, testid: string, match: boolean) { await expect(page.getByTestId(testid)).toContainText(noMatchText); } } + +export async function resetPasswordScreen(page: Page, username: string, password1: string, password2: string) { + // wait for send of the code + await page.waitForTimeout(3000); + const c = await getCodeFromSink(username); + await page.getByTestId(codeField).pressSequentially(c); + await page.getByTestId(passwordField).pressSequentially(password1); + await page.getByTestId(passwordConfirmField).pressSequentially(password2); +} + +export async function resetPasswordScreenExpect( + page: Page, + password1: string, + password2: string, + length: boolean, + symbol: boolean, + number: boolean, + uppercase: boolean, + lowercase: boolean, + equals: boolean, +) { + await changePasswordScreenExpect(page, password1, password2, length, symbol, number, uppercase, lowercase, equals); +} diff --git a/acceptance/tests/password.ts b/acceptance/tests/password.ts index e8cd787b04..b3d31fcaee 100644 --- a/acceptance/tests/password.ts +++ b/acceptance/tests/password.ts @@ -1,7 +1,8 @@ import { Page } from "@playwright/test"; -import { changePasswordScreen, passwordScreen } from "./password-screen"; +import { changePasswordScreen, passwordScreen, resetPasswordScreen } from "./password-screen"; const passwordSubmitButton = "submit-button"; +const passwordResetButton = "reset-button"; export async function startChangePassword(page: Page, loginname: string) { await page.goto("/password/change?" + new URLSearchParams({ loginName: loginname })); @@ -17,3 +18,13 @@ export async function password(page: Page, password: string) { await passwordScreen(page, password); await page.getByTestId(passwordSubmitButton).click(); } + +export async function startResetPassword(page: Page) { + await page.getByTestId(passwordResetButton).click(); +} + +export async function resetPassword(page: Page, username: string, password: string) { + await startResetPassword(page); + await resetPasswordScreen(page, username, password, password); + await page.getByTestId(passwordSubmitButton).click(); +} diff --git a/acceptance/tests/sink.ts b/acceptance/tests/sink.ts index d3cfa7ddd7..fc13a98dc7 100644 --- a/acceptance/tests/sink.ts +++ b/acceptance/tests/sink.ts @@ -1,6 +1,6 @@ import axios from "axios"; -export async function getCodeFromSink(key: string): Promise { +export async function getOtpFromSink(key: string): Promise { try { const response = await axios.post( process.env.SINK_NOTIFICATION_URL!, @@ -26,3 +26,30 @@ export async function getCodeFromSink(key: string): Promise { throw error; } } + +export async function getCodeFromSink(key: string): Promise { + try { + const response = await axios.post( + process.env.SINK_NOTIFICATION_URL!, + { + recipient: key, + }, + { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, + }, + }, + ); + + if (response.status >= 400) { + const error = `HTTP Error: ${response.status} - ${response.statusText}`; + console.error(error); + throw new Error(error); + } + return response.data.args.code; + } catch (error) { + console.error("Error making request:", error); + throw error; + } +} diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index 1e07b65a50..9b2bfb46d5 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -21,8 +21,6 @@ class User { } async ensure(page: Page) { - await this.remove(); - const body = { username: this.props.email, organization: { @@ -53,7 +51,7 @@ class User { }, }); - if (response.status >= 400 && response.status !== 409) { + if (response.status >= 400) { const error = `HTTP Error: ${response.status} - ${response.statusText}`; console.error(error); throw new Error(error); @@ -65,7 +63,7 @@ class User { } // wait for projection of user - await page.waitForTimeout(3000); + await page.waitForTimeout(2000); } async remove() { @@ -166,7 +164,7 @@ export class PasswordUserWithOTP extends User { }, ); - if (response.status >= 400 && response.status !== 409) { + if (response.status >= 400) { const error = `HTTP Error: ${response.status} - ${response.statusText}`; console.error(error); throw new Error(error); @@ -204,7 +202,6 @@ export class PasskeyUser extends User { } public async ensure(page: Page) { - await this.remove(); const authId = await registerWithPasskey(page, this.getFirstname(), this.getLastname(), this.getUsername()); this.authenticatorId = authId; @@ -212,10 +209,6 @@ export class PasskeyUser extends User { await page.waitForTimeout(2000); } - public async remove() { - await super.remove(); - } - public getAuthenticatorId(): string { return this.authenticatorId; } diff --git a/acceptance/tests/username-passkey.spec.ts b/acceptance/tests/username-passkey.spec.ts index 304a17b16b..7fbf290d99 100644 --- a/acceptance/tests/username-passkey.spec.ts +++ b/acceptance/tests/username-passkey.spec.ts @@ -27,15 +27,6 @@ test("username and passkey login", async ({ user, page }) => { await loginScreenExpect(page, user.getFullName()); }); -test("username and passkey login, if passkey enabled", async ({ page }) => { - // Given passkey is enabled on the organization of the user - // Given the user has only passkey enabled as authentication - // enter username - // passkey popup is directly shown - // user verifies passkey - // user is redirected to app -}); - test("username and passkey login, multiple auth methods", async ({ page }) => { // Given passkey and password is enabled on the organization of the user // Given the user has password and passkey registered diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index 9a9e4b67e3..cf3af995f7 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -42,7 +42,7 @@ test("username and password changed login", async ({ user, page }) => { */ }); -test("password not with desired complexity", async ({ user, page }) => { +test("password change not with desired complexity", async ({ user, page }) => { const changedPw1 = "change"; const changedPw2 = "chang"; await loginWithPassword(page, user.getUsername(), user.getPassword()); diff --git a/acceptance/tests/username-password-otp_email.spec.ts b/acceptance/tests/username-password-otp_email.spec.ts index ca318e9fee..b684fa80c6 100644 --- a/acceptance/tests/username-password-otp_email.spec.ts +++ b/acceptance/tests/username-password-otp_email.spec.ts @@ -2,7 +2,7 @@ import { faker } from "@faker-js/faker"; import { test as base } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; -import { code, codeFromSink, codeResend } from "./code"; +import { code, codeResend, otpFromSink } from "./code"; import { codeScreenExpect } from "./code-screen"; import { loginScreenExpect, loginWithPassword, loginWithPasswordAndEmailOTP } from "./login"; import { OtpType, PasswordUserWithOTP } from "./user"; @@ -61,7 +61,7 @@ test("username, password and email otp login, resend code", async ({ user, page // User is redirected to the app (default redirect url) await loginWithPassword(page, user.getUsername(), user.getPassword()); await codeResend(page); - await codeFromSink(page, user.getUsername()); + await otpFromSink(page, user.getUsername()); await loginScreenExpect(page, user.getFullName()); }); diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index 2cb5c91e77..12cdc6c740 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -2,6 +2,9 @@ import { faker } from "@faker-js/faker"; import { test as base } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; +import { code } from "./code"; +import { codeScreenExpect } from "./code-screen"; +import { loginScreenExpect, loginWithPassword, loginWithPasswordAndPhoneOTP } from "./login"; import { OtpType, PasswordUserWithOTP } from "./user"; // Read from ".env" file. @@ -14,7 +17,7 @@ const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({ firstName: faker.person.firstName(), lastName: faker.person.lastName(), organization: "", - phone: faker.phone.number(), + phone: faker.phone.number({ style: "international" }), password: "Password1!", type: OtpType.sms, }); @@ -32,10 +35,8 @@ test("username, password and sms otp login, enter code manually", async ({ user, // User receives a sms with a verification code // User enters the code into the ui // User is redirected to the app (default redirect url) - /* TODO fix on login, that sms is sent await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone()); await loginScreenExpect(page, user.getFullName()); - */ }); test("username, password and sms otp login, resend code", async ({ user, page }) => { @@ -47,11 +48,9 @@ test("username, password and sms otp login, resend code", async ({ user, page }) // User clicks resend code // User receives a new sms with a verification code // User is redirected to the app (default redirect url) - /* TODO fix on login, that sms is sent -await loginWithPassword(page, user.getUsername(), user.getPassword()); -await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone()); -await loginScreenExpect(page, user.getFullName()); - */ + await loginWithPassword(page, user.getUsername(), user.getPassword()); + await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone()); + await loginScreenExpect(page, user.getFullName()); }); test("username, password and sms otp login, wrong code", async ({ user, page }) => { @@ -62,10 +61,8 @@ test("username, password and sms otp login, wrong code", async ({ user, page }) // User receives a sms with a verification code // User enters a wrong code // Error message - "Invalid code" is shown - /* TODO fix on login, that sms is sent -const c = "wrongcode"; -await loginWithPassword(page, user.getUsername(), user.getPassword()); -await code(page, c); -await codeScreenExpect(page, c); - */ + const c = "wrongcode"; + await loginWithPassword(page, user.getUsername(), user.getPassword()); + await code(page, c); + await codeScreenExpect(page, c); }); diff --git a/acceptance/tests/username-password-set.spec.ts b/acceptance/tests/username-password-set.spec.ts new file mode 100644 index 0000000000..c2a60bd410 --- /dev/null +++ b/acceptance/tests/username-password-set.spec.ts @@ -0,0 +1,50 @@ +import { faker } from "@faker-js/faker"; +import { test as base } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { loginScreenExpect, loginWithPassword, startLogin } from "./login"; +import { loginname } from "./loginname"; +import { resetPassword, startResetPassword } from "./password"; +import { resetPasswordScreen, resetPasswordScreenExpect } from "./password-screen"; +import { PasswordUser } from "./user"; + +// Read from ".env" file. +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); + +const test = base.extend<{ user: PasswordUser }>({ + user: async ({ page }, use) => { + const user = new PasswordUser({ + email: faker.internet.email(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + organization: "", + phone: faker.phone.number(), + password: "Password1!", + }); + await user.ensure(page); + await use(user); + }, +}); + +test("username and password set login", async ({ user, page }) => { + // commented, fix in https://github.com/zitadel/zitadel/pull/8807 + + const changedPw = "ChangedPw1!"; + await startLogin(page); + await loginname(page, user.getUsername()); + await resetPassword(page, user.getUsername(), changedPw); + await loginScreenExpect(page, user.getFullName()); + + await loginWithPassword(page, user.getUsername(), changedPw); + await loginScreenExpect(page, user.getFullName()); +}); + +test("password set not with desired complexity", async ({ user, page }) => { + const changedPw1 = "change"; + const changedPw2 = "chang"; + await startLogin(page); + await loginname(page, user.getUsername()); + await startResetPassword(page); + await resetPasswordScreen(page, user.getUsername(), changedPw1, changedPw2); + await resetPasswordScreenExpect(page, changedPw1, changedPw2, false, false, false, false, true, false); +}); diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 59a0979db0..922b43fa14 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -77,7 +77,7 @@ export function LoginOTP({ if (method === "sms") { challenges = create(RequestChallengesSchema, { - otpSms: { returnCode: true }, + otpSms: {}, }); } diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index 5885ecc6d4..490e5e0608 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -134,6 +134,7 @@ export function PasswordForm({ onClick={() => resetPasswordAndContinue()} type="button" disabled={loading} + data-testid="reset-button" > {t("verify.resetPassword")} diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 65405ed8f0..ca87c85c9f 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -170,11 +170,15 @@ export function SetPasswordForm({ label="Code" autoComplete="one-time-code" error={errors.code?.message as string} + data-testid="code-text-input" />
    -
    @@ -190,6 +194,7 @@ export function SetPasswordForm({ })} label="New Password" error={errors.password?.message as string} + data-testid="password-text-input" />
    @@ -202,6 +207,7 @@ export function SetPasswordForm({ })} label="Confirm Password" error={errors.confirmPassword?.message as string} + data-testid="password-confirm-text-input" />
    From 61bb6ac7566407c818383319025fc0b5896b157b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 22 Nov 2024 08:46:16 +0100 Subject: [PATCH 461/640] change test ids --- acceptance/tests/register-screen.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acceptance/tests/register-screen.ts b/acceptance/tests/register-screen.ts index 414e38793b..d14f5dc970 100644 --- a/acceptance/tests/register-screen.ts +++ b/acceptance/tests/register-screen.ts @@ -5,12 +5,12 @@ const passwordConfirmField = "password-confirm-text-input"; export async function registerUserScreenPassword(page: Page, firstname: string, lastname: string, email: string) { await registerUserScreen(page, firstname, lastname, email); - await page.getByTestId("Password-radio").click(); + await page.getByTestId("password-radio").click(); } export async function registerUserScreenPasskey(page: Page, firstname: string, lastname: string, email: string) { await registerUserScreen(page, firstname, lastname, email); - await page.getByTestId("Passkeys-radio").click(); + await page.getByTestId("passkey-radio").click(); } export async function registerPasswordScreen(page: Page, password1: string, password2: string) { From ff73d8e3ccd2d52ad1aa4aca32f0e2ceb3ddb1fa Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 22 Nov 2024 09:41:44 +0100 Subject: [PATCH 462/640] use before stub --- apps/login/cypress/integration/register.cy.ts | 10 ++++++++++ apps/login/src/components/register-form.tsx | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/login/cypress/integration/register.cy.ts b/apps/login/cypress/integration/register.cy.ts index 902c6a8061..1040b91c14 100644 --- a/apps/login/cypress/integration/register.cy.ts +++ b/apps/login/cypress/integration/register.cy.ts @@ -10,6 +10,16 @@ describe("register", () => { result: [{ id: "256088834543534543" }], }, }); + stub("zitadel.settings.v2.SettingsService", "GetLoginSettings", { + data: { + settings: { + passkeysType: 1, + allowRegister: true, + allowUsernamePassword: true, + defaultRedirectUri: "", + }, + }, + }); stub("zitadel.user.v2.UserService", "AddHumanUser", { data: { userId: "221394658884845598", diff --git a/apps/login/src/components/register-form.tsx b/apps/login/src/components/register-form.tsx index cf3f1631da..f4f1fec610 100644 --- a/apps/login/src/components/register-form.tsx +++ b/apps/login/src/components/register-form.tsx @@ -118,7 +118,6 @@ export function RegisterForm({ const { errors } = formState; const [tosAndPolicyAccepted, setTosAndPolicyAccepted] = useState(false); - console.log(loginSettings); return (
    From 19c310f83bed6cc403414b665668dcddbed8280b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 22 Nov 2024 09:54:08 +0100 Subject: [PATCH 463/640] remove type check --- apps/login/readme.md | 2 +- apps/login/src/app/(login)/password/page.tsx | 2 +- apps/login/src/components/choose-authenticator-to-setup.tsx | 4 ++-- apps/login/src/components/register-form.tsx | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 38eee55b90..a1999e6041 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -116,7 +116,7 @@ If the user has set up an additional **single** second factor, it is redirected **NO MFA, FORCE MFA:** If no MFA method is available, and the settings force MFA, the user is sent to `/mfa/set` which prompts to setup a second factor. -**PROMPT PASSKEY** If the settings do not enforce MFA, we check if passkeys are allowed with `loginSettings?.passkeysType === PasskeysType.ALLOWED` and redirect the user to `/passkey/set` if no passkeys are setup. This step can be skipped. +**PROMPT PASSKEY** If the settings do not enforce MFA, we check if passkeys are allowed with `loginSettings?.passkeysType == PasskeysType.ALLOWED` and redirect the user to `/passkey/set` if no passkeys are setup. This step can be skipped. If none of the previous conditions apply, we continue to sign in. diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 9731e4030e..2064e161ff 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -83,7 +83,7 @@ export default async function Page({ organization={organization} // stick to "organization" as we still want to do user discovery based on the searchParams not the default organization, later the organization is determined by the found user loginSettings={loginSettings} promptPasswordless={ - loginSettings?.passkeysType === PasskeysType.ALLOWED + loginSettings?.passkeysType == PasskeysType.ALLOWED } isAlternative={alt === "true"} /> diff --git a/apps/login/src/components/choose-authenticator-to-setup.tsx b/apps/login/src/components/choose-authenticator-to-setup.tsx index 25ca69e776..9075e3286e 100644 --- a/apps/login/src/components/choose-authenticator-to-setup.tsx +++ b/apps/login/src/components/choose-authenticator-to-setup.tsx @@ -25,7 +25,7 @@ export function ChooseAuthenticatorToSetup({ } else { return ( <> - {loginSettings.passkeysType === PasskeysType.NOT_ALLOWED && + {loginSettings.passkeysType == PasskeysType.NOT_ALLOWED && !loginSettings.allowUsernamePassword && ( {t("noMethodsAvailable")} )} @@ -35,7 +35,7 @@ export function ChooseAuthenticatorToSetup({ loginSettings.allowUsernamePassword && PASSWORD(false, "/password/set?" + params)} {!authMethods.includes(AuthenticationMethodType.PASSKEY) && - loginSettings.passkeysType === PasskeysType.ALLOWED && + loginSettings.passkeysType == PasskeysType.ALLOWED && PASSKEYS(false, "/passkey/set?" + params)}
    diff --git a/apps/login/src/components/register-form.tsx b/apps/login/src/components/register-form.tsx index f4f1fec610..9788ab0e7d 100644 --- a/apps/login/src/components/register-form.tsx +++ b/apps/login/src/components/register-form.tsx @@ -165,7 +165,7 @@ export function RegisterForm({ {/* show chooser if both methods are allowed */} {loginSettings && loginSettings.allowUsernamePassword && - loginSettings.passkeysType === PasskeysType.ALLOWED && ( + loginSettings.passkeysType == PasskeysType.ALLOWED && (
    { const usePasswordToContinue: boolean = loginSettings?.allowUsernamePassword && - loginSettings?.passkeysType === PasskeysType.ALLOWED + loginSettings?.passkeysType == PasskeysType.ALLOWED ? !!!(selected === methods[0]) // choose selection if both available : !!loginSettings?.allowUsernamePassword; // if password is chosen // set password as default if only password is allowed From 837cd4f674a3ea9f921ac592e1aef70aa901947b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 22 Nov 2024 11:25:03 +0100 Subject: [PATCH 464/640] update nextjs to 15 --- apps/login/cypress/integration/register.cy.ts | 8 +- .../zitadel.settings.v2.SettingsService.json | 24 +- apps/login/next.config.mjs | 3 + apps/login/package.json | 18 +- apps/login/src/app/(login)/accounts/page.tsx | 11 +- .../app/(login)/authenticator/set/page.tsx | 11 +- .../(login)/idp/[provider]/failure/page.tsx | 14 +- .../(login)/idp/[provider]/success/page.tsx | 19 +- apps/login/src/app/(login)/idp/page.tsx | 11 +- apps/login/src/app/(login)/invite/page.tsx | 11 +- .../src/app/(login)/invite/success/page.tsx | 11 +- apps/login/src/app/(login)/loginname/page.tsx | 11 +- apps/login/src/app/(login)/mfa/page.tsx | 11 +- apps/login/src/app/(login)/mfa/set/page.tsx | 11 +- .../src/app/(login)/otp/[method]/page.tsx | 15 +- .../src/app/(login)/otp/[method]/set/page.tsx | 15 +- apps/login/src/app/(login)/passkey/page.tsx | 11 +- .../src/app/(login)/passkey/set/page.tsx | 11 +- .../src/app/(login)/password/change/page.tsx | 11 +- apps/login/src/app/(login)/password/page.tsx | 11 +- .../src/app/(login)/password/set/page.tsx | 11 +- apps/login/src/app/(login)/register/page.tsx | 18 +- .../app/(login)/register/password/page.tsx | 23 +- apps/login/src/app/(login)/signedin/page.tsx | 3 +- apps/login/src/app/(login)/u2f/page.tsx | 11 +- apps/login/src/app/(login)/u2f/set/page.tsx | 11 +- apps/login/src/app/(login)/verify/page.tsx | 11 +- apps/login/src/components/register-form.tsx | 1 + apps/login/src/i18n/request.ts | 2 +- apps/login/src/lib/cookies.ts | 24 +- apps/login/src/lib/server/invite.ts | 2 +- apps/login/src/lib/server/loginname.ts | 4 +- apps/login/src/lib/server/passkeys.ts | 4 +- apps/login/src/lib/server/password.ts | 2 +- apps/login/src/lib/server/u2f.ts | 4 +- apps/login/src/lib/zitadel.ts | 101 +-- pnpm-lock.yaml | 842 ++++++++++++------ 37 files changed, 775 insertions(+), 547 deletions(-) diff --git a/apps/login/cypress/integration/register.cy.ts b/apps/login/cypress/integration/register.cy.ts index 1040b91c14..262302c4c3 100644 --- a/apps/login/cypress/integration/register.cy.ts +++ b/apps/login/cypress/integration/register.cy.ts @@ -63,9 +63,11 @@ describe("register", () => { it("should redirect a user who selects passwordless on register to /passkey/set", () => { cy.visit("/register"); - cy.get('input[autocomplete="firstname"]').focus().type("John"); - cy.get('input[autocomplete="lastname"]').focus().type("Doe"); - cy.get('input[autocomplete="email"]').focus().type("john@zitadel.com"); + cy.get('input[data-testid="firstname-text-input"]').focus().type("John"); + cy.get('input[data-testid="lastname-text-input"]').focus().type("Doe"); + cy.get('input[data-testid="email-text-input"]') + .focus() + .type("john@zitadel.com"); cy.get('input[type="checkbox"][value="privacypolicy"]').check(); cy.get('input[type="checkbox"][value="tos"]').check(); cy.get('button[type="submit"]').click(); diff --git a/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json b/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json index 264b483c19..f62b5da077 100644 --- a/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json +++ b/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json @@ -2,7 +2,15 @@ { "service": "zitadel.settings.v2.SettingsService", "method": "GetBrandingSettings", - "out": {} + "out": { + "data": { + "settings": { + "darkTheme": { + "backgroundColor": "#ff0000" + } + } + } + } }, { "service": "zitadel.settings.v2.SettingsService", @@ -46,19 +54,5 @@ } } } - }, - { - "service": "zitadel.settings.v2.SettingsService", - "method": "GetLoginSettings", - "out": { - "data": { - "settings": { - "allowRegister": true, - "passkeysType": 1, - "allowUsernamePassword": true, - "defaultRedirectUri": "" - } - } - } } ] diff --git a/apps/login/next.config.mjs b/apps/login/next.config.mjs index e2906b1fa8..e42b4ef7a1 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -37,6 +37,9 @@ const secureHeaders = [ const nextConfig = { reactStrictMode: true, // Recommended for the `pages` directory, default in `app`. swcMinify: true, + experimental: { + dynamicIO: true, + }, images: { remotePatterns: [ { diff --git a/apps/login/package.json b/apps/login/package.json index 13927b5168..e621cb567a 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "dev": "next dev", + "dev": "next dev --turbopack", "test": "concurrently --timings --kill-others-on-fail 'npm:test:unit' 'npm:test:integration'", "test:watch": "concurrently --kill-others 'npm:test:unit:watch' 'npm:test:integration:watch'", "test:unit": "vitest", @@ -45,13 +45,13 @@ "copy-to-clipboard": "^3.3.3", "deepmerge": "^4.3.1", "moment": "^2.29.4", - "next": "14.2.14", + "next": "15.0.3", "next-intl": "^3.20.0", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", "qrcode.react": "^3.1.0", - "react": "^18.3.1", - "react-dom": "18.3.1", + "react": "19.0.0-rc-66855b96-20241106", + "react-dom": "19.0.0-rc-66855b96-20241106", "react-hook-form": "7.39.5", "swr": "^2.2.0", "tinycolor2": "1.4.2" @@ -62,8 +62,8 @@ "@testing-library/react": "^16.0.1", "@types/ms": "0.7.34", "@types/node": "22.9.0", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", + "@types/react": "npm:types-react@19.0.0-rc.1", + "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", "@types/tinycolor2": "1.4.3", "@types/uuid": "^10.0.0", "@vercel/git-hooks": "1.0.0", @@ -88,5 +88,11 @@ "ts-proto": "^2.2.7", "typescript": "^5.6.3", "zitadel-tailwind-config": "workspace:*" + }, + "pnpm": { + "overrides": { + "@types/react": "npm:types-react@19.0.0-rc.1", + "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1" + } } } diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index d8813fada0..e072d7c2e7 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -20,11 +20,12 @@ async function loadSessions() { } } -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "accounts" }); diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 78a06ab329..e2f093adb9 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -15,11 +15,12 @@ import { import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "authenticator" }); const tError = await getTranslations({ locale, namespace: "error" }); diff --git a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx index 42dcba25fa..8729c1e978 100644 --- a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -12,13 +12,13 @@ const PROVIDER_NAME_MAPPING: { [IdentityProviderType.AZURE_AD]: "Microsoft", }; -export default async function Page({ - searchParams, - params, -}: { - searchParams: Record; - params: { provider: string }; -}) { +export default async function Page( + props: { + searchParams: Promise>; + params: Promise<{ provider: string }>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "idp" }); diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index c7a5f06ca9..eb62bd35bc 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -29,13 +29,14 @@ async function loginFailed(branding?: BrandingSettings) { ); } -export default async function Page({ - searchParams, - params, -}: { - searchParams: Record; - params: { provider: string }; -}) { +export default async function Page( + props: { + searchParams: Promise>; + params: Promise<{ provider: string }>; + } +) { + const params = await props.params; + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "idp" }); const { id, token, authRequestId, organization } = searchParams; @@ -137,12 +138,12 @@ export default async function Page({ if (idpLink) { return ( // TODO: possibily login user now - + (

    {t("linkingSuccess.title")}

    {t("linkingSuccess.description")}
    -
    +
    ) ); } } diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index ee43d5582c..cbf0a1ae78 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -12,11 +12,12 @@ function getIdentityProviders(orgId?: string) { }); } -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "idp" }); diff --git a/apps/login/src/app/(login)/invite/page.tsx b/apps/login/src/app/(login)/invite/page.tsx index 83beb9d9bf..c11131b50a 100644 --- a/apps/login/src/app/(login)/invite/page.tsx +++ b/apps/login/src/app/(login)/invite/page.tsx @@ -9,11 +9,12 @@ import { } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "invite" }); diff --git a/apps/login/src/app/(login)/invite/success/page.tsx b/apps/login/src/app/(login)/invite/success/page.tsx index 2cb564580e..5955a8ecc3 100644 --- a/apps/login/src/app/(login)/invite/success/page.tsx +++ b/apps/login/src/app/(login)/invite/success/page.tsx @@ -7,11 +7,12 @@ import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { getLocale, getTranslations } from "next-intl/server"; import Link from "next/link"; -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "invite" }); diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 68928755d3..89b58f516e 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -19,11 +19,12 @@ function getIdentityProviders(orgId?: string) { }); } -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "loginname" }); diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index f61aa4d988..96c708ba63 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -12,11 +12,12 @@ import { } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "mfa" }); const tError = await getTranslations({ locale, namespace: "error" }); diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index 80ea4638d3..4c1a7b7207 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -32,11 +32,12 @@ function isSessionValid(session: Partial): { return { valid, verifiedAt }; } -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "mfa" }); const tError = await getTranslations({ locale, namespace: "error" }); diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 63bdbf551d..fbeca2fa37 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -6,13 +6,14 @@ import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ - searchParams, - params, -}: { - searchParams: Record; - params: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + params: Promise>; + } +) { + const params = await props.params; + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "otp" }); const tError = await getTranslations({ locale, namespace: "error" }); diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index 9fb279e5f9..2a8b0ea790 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -17,13 +17,14 @@ import { getLocale, getTranslations } from "next-intl/server"; import Link from "next/link"; import { redirect } from "next/navigation"; -export default async function Page({ - searchParams, - params, -}: { - searchParams: Record; - params: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + params: Promise>; + } +) { + const params = await props.params; + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "otp" }); const tError = await getTranslations({ locale, namespace: "error" }); diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index 09605ad382..d6791676f7 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -11,11 +11,12 @@ import { } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "passkey" }); const tError = await getTranslations({ locale, namespace: "error" }); diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx index 45ea1d7803..4e4e4f24f0 100644 --- a/apps/login/src/app/(login)/passkey/set/page.tsx +++ b/apps/login/src/app/(login)/passkey/set/page.tsx @@ -6,11 +6,12 @@ import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "passkey" }); const tError = await getTranslations({ locale, namespace: "error" }); diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx index 55b8c0ad49..a697547f8f 100644 --- a/apps/login/src/app/(login)/password/change/page.tsx +++ b/apps/login/src/app/(login)/password/change/page.tsx @@ -10,11 +10,12 @@ import { } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "password" }); const tError = await getTranslations({ locale, namespace: "error" }); diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 2064e161ff..da7b91fbd7 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -12,11 +12,12 @@ import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "password" }); const tError = await getTranslations({ locale, namespace: "error" }); diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index b7cd7c0d1b..c44f236c38 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -13,11 +13,12 @@ import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "password" }); const tError = await getTranslations({ locale, namespace: "error" }); diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 00adc174b2..497c1ac6e4 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -10,11 +10,12 @@ import { import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "register" }); @@ -39,8 +40,11 @@ export default async function Page({ if (!loginSettings?.allowRegister) { return ( -
    {t("disabled.title")}
    -

    {t("disabled.description")}

    +
    +

    {t("disabled.title")}

    +

    {t("disabled.description")}

    + {JSON.stringify(loginSettings)} +
    ); } diff --git a/apps/login/src/app/(login)/register/password/page.tsx b/apps/login/src/app/(login)/register/password/page.tsx index 66843d45ac..c1d1a895ee 100644 --- a/apps/login/src/app/(login)/register/password/page.tsx +++ b/apps/login/src/app/(login)/register/password/page.tsx @@ -10,11 +10,12 @@ import { import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "register" }); @@ -40,8 +41,10 @@ export default async function Page({ return missingData ? ( -
    {t("missingdata.title")}
    -

    {t("missingdata.description")}

    +
    +

    {t("missingdata.title")}

    +

    {t("missingdata.description")}

    +
    ) : loginSettings?.allowRegister && loginSettings.allowUsernamePassword ? ( @@ -63,8 +66,10 @@ export default async function Page({ ) : ( -
    {t("disabled.title")}
    -

    {t("disabled.description")}

    +
    +

    {t("disabled.title")}

    +

    {t("disabled.description")}

    +
    ); } diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index 3833d1b753..4fc9ac8546 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -46,7 +46,8 @@ async function loadSession(loginName: string, authRequestId?: string) { ); } -export default async function Page({ searchParams }: { searchParams: any }) { +export default async function Page(props: { searchParams: Promise }) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "signedin" }); diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index 36fef6d786..4dce6fdeaa 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -7,11 +7,12 @@ import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "u2f" }); const tError = await getTranslations({ locale, namespace: "error" }); diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index d35bcd4ddb..862b7024d2 100644 --- a/apps/login/src/app/(login)/u2f/set/page.tsx +++ b/apps/login/src/app/(login)/u2f/set/page.tsx @@ -6,11 +6,12 @@ import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ - searchParams, -}: { - searchParams: Record; -}) { +export default async function Page( + props: { + searchParams: Promise>; + } +) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "u2f" }); const tError = await getTranslations({ locale, namespace: "error" }); diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index ed216ea317..c8e586f409 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -12,7 +12,8 @@ import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page({ searchParams }: { searchParams: any }) { +export default async function Page(props: { searchParams: Promise }) { + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "verify" }); const tError = await getTranslations({ locale, namespace: "error" }); @@ -60,7 +61,7 @@ export default async function Page({ searchParams }: { searchParams: any }) { } return ( - + (

    {t("verify.title")}

    {t("verify.description")}

    @@ -92,14 +93,14 @@ export default async function Page({ searchParams }: { searchParams: any }) { /> ) : ( // check if auth methods are set - + />) )}
    -
    +
    ) ); } diff --git a/apps/login/src/components/register-form.tsx b/apps/login/src/components/register-form.tsx index 9788ab0e7d..40dfe3c1ea 100644 --- a/apps/login/src/components/register-form.tsx +++ b/apps/login/src/components/register-form.tsx @@ -50,6 +50,7 @@ export function RegisterForm({ loginSettings, }: Props) { const t = useTranslations("register"); + console.log(loginSettings); const { register, handleSubmit, formState } = useForm({ mode: "onBlur", diff --git a/apps/login/src/i18n/request.ts b/apps/login/src/i18n/request.ts index ed4ba25054..2641c0fa26 100644 --- a/apps/login/src/i18n/request.ts +++ b/apps/login/src/i18n/request.ts @@ -5,7 +5,7 @@ import { cookies } from "next/headers"; export default getRequestConfig(async () => { const fallback = "en"; - const cookiesList = cookies(); + const cookiesList = await cookies(); const locale: string = cookiesList.get(LANGUAGE_COOKIE_NAME)?.value ?? "en"; const userMessages = (await import(`../../locales/${locale}.json`)).default; diff --git a/apps/login/src/lib/cookies.ts b/apps/login/src/lib/cookies.ts index c6afb50797..ebf667b51a 100644 --- a/apps/login/src/lib/cookies.ts +++ b/apps/login/src/lib/cookies.ts @@ -1,7 +1,7 @@ "use server"; import { timestampDate, timestampFromMs } from "@zitadel/client"; -import { cookies } from "next/headers"; +import { cookies, type UnsafeUnwrappedCookies } from "next/headers"; import { LANGUAGE_COOKIE_NAME } from "./i18n"; // TODO: improve this to handle overflow @@ -21,7 +21,7 @@ export type Cookie = { type SessionCookie = Cookie & T; function setSessionHttpOnlyCookie(sessions: SessionCookie[]) { - const cookiesList = cookies(); + const cookiesList = (cookies() as unknown as UnsafeUnwrappedCookies); return cookiesList.set({ name: "sessions", @@ -32,7 +32,7 @@ function setSessionHttpOnlyCookie(sessions: SessionCookie[]) { } export async function setLanguageCookie(language: string) { - const cookiesList = cookies(); + const cookiesList = await cookies(); await cookiesList.set({ name: LANGUAGE_COOKIE_NAME, @@ -46,7 +46,7 @@ export async function addSessionToCookie( session: SessionCookie, cleanup: boolean = false, ): Promise { - const cookiesList = cookies(); + const cookiesList = await cookies(); const stringifiedCookie = cookiesList.get("sessions"); let currentSessions: SessionCookie[] = stringifiedCookie?.value @@ -90,7 +90,7 @@ export async function updateSessionCookie( session: SessionCookie, cleanup: boolean = false, ): Promise { - const cookiesList = cookies(); + const cookiesList = await cookies(); const stringifiedCookie = cookiesList.get("sessions"); const sessions: SessionCookie[] = stringifiedCookie?.value @@ -121,7 +121,7 @@ export async function removeSessionFromCookie( session: SessionCookie, cleanup: boolean = false, ): Promise { - const cookiesList = cookies(); + const cookiesList = await cookies(); const stringifiedCookie = cookiesList.get("sessions"); const sessions: SessionCookie[] = stringifiedCookie?.value @@ -143,7 +143,7 @@ export async function removeSessionFromCookie( } export async function getMostRecentSessionCookie(): Promise { - const cookiesList = cookies(); + const cookiesList = await cookies(); const stringifiedCookie = cookiesList.get("sessions"); if (stringifiedCookie?.value) { @@ -166,7 +166,7 @@ export async function getSessionCookieById({ sessionId: string; organization?: string; }): Promise> { - const cookiesList = cookies(); + const cookiesList = await cookies(); const stringifiedCookie = cookiesList.get("sessions"); if (stringifiedCookie?.value) { @@ -194,7 +194,7 @@ export async function getSessionCookieByLoginName({ loginName?: string; organization?: string; }): Promise> { - const cookiesList = cookies(); + const cookiesList = await cookies(); const stringifiedCookie = cookiesList.get("sessions"); if (stringifiedCookie?.value) { @@ -222,7 +222,7 @@ export async function getSessionCookieByLoginName({ export async function getAllSessionCookieIds( cleanup: boolean = false, ): Promise { - const cookiesList = cookies(); + const cookiesList = await cookies(); const stringifiedCookie = cookiesList.get("sessions"); if (stringifiedCookie?.value) { @@ -253,7 +253,7 @@ export async function getAllSessionCookieIds( export async function getAllSessions( cleanup: boolean = false, ): Promise[]> { - const cookiesList = cookies(); + const cookiesList = await cookies(); const stringifiedCookie = cookiesList.get("sessions"); if (stringifiedCookie?.value) { @@ -287,7 +287,7 @@ export async function getMostRecentCookieWithLoginname({ loginName?: string; organization?: string; }): Promise { - const cookiesList = cookies(); + const cookiesList = await cookies(); const stringifiedCookie = cookiesList.get("sessions"); if (stringifiedCookie?.value) { diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts index 14b9e8ef9e..3c68587898 100644 --- a/apps/login/src/lib/server/invite.ts +++ b/apps/login/src/lib/server/invite.ts @@ -20,7 +20,7 @@ export type RegisterUserResponse = { }; export async function inviteUser(command: InviteUserCommand) { - const host = headers().get("host"); + const host = (await headers()).get("host"); const human = await addHumanUser({ email: command.email, diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index a23664ec46..797d93488c 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -44,7 +44,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { }); if (identityProviders.length === 1) { - const host = headers().get("host"); + const host = (await headers()).get("host"); const identityProviderType = identityProviders[0].type; const provider = idpTypeToSlug(identityProviderType); @@ -81,7 +81,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { }); if (identityProviders.length === 1) { - const host = headers().get("host"); + const host = (await headers()).get("host"); const identityProviderId = identityProviders[0].idpId; const idp = await getIDPByID(identityProviderId); diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 366c54295a..181962cae1 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -37,7 +37,7 @@ export async function registerPasskeyLink( sessionToken: sessionCookie.token, }); - const host = headers().get("host"); + const host = (await headers()).get("host"); if (!host) { throw new Error("Could not get domain"); @@ -73,7 +73,7 @@ export async function verifyPasskey(command: VerifyPasskeyCommand) { // if no name is provided, try to generate one from the user agent let passkeyName = command.passkeyName; if (!!!passkeyName) { - const headersList = headers(); + const headersList = await headers(); const userAgentStructure = { headers: headersList }; const { browser, device, os } = userAgent(userAgentStructure); diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 24ba10faf8..63d9f4319f 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -30,7 +30,7 @@ type ResetPasswordCommand = { }; export async function resetPassword(command: ResetPasswordCommand) { - const host = headers().get("host"); + const host = (await headers()).get("host"); const users = await listUsers({ loginName: command.loginName, diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index eb0bccf5af..a174d0bc56 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -32,7 +32,7 @@ export async function addU2F(command: RegisterU2FCommand) { sessionToken: sessionCookie.token, }); - const domain = headers().get("host"); + const domain = (await headers()).get("host"); if (!domain) { return { error: "Could not get domain" }; @@ -54,7 +54,7 @@ export async function addU2F(command: RegisterU2FCommand) { export async function verifyU2F(command: VerifyU2FCommand) { let passkeyName = command.passkeyName; if (!!!passkeyName) { - const headersList = headers(); + const headersList = await headers(); const userAgentStructure = { headers: headersList }; const { browser, device, os } = userAgent(userAgentStructure); diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 161353ccd5..ad0b48bc4a 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -18,17 +18,11 @@ import { VerifyU2FRegistrationRequest, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { create, fromJson, toJson } from "@zitadel/client"; +import { create } from "@zitadel/client"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; -import { BrandingSettingsSchema } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; -import { LegalAndSupportSettingsSchema } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; -import { - IdentityProviderType, - LoginSettingsSchema, -} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { PasswordComplexitySettingsSchema } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; +import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { NotificationType, @@ -43,7 +37,7 @@ import { User, UserState, } from "@zitadel/proto/zitadel/user/v2/user_pb"; -import { unstable_cache } from "next/cache"; +import { unstable_cacheLife as cacheLife } from "next/cache"; import { PROVIDER_MAPPING } from "./idp"; const SESSION_LIFETIME_S = 3600; // TODO load from oidc settings @@ -66,43 +60,19 @@ export const orgService = createOrganizationServiceClient(transport); export const settingsService = createSettingsServiceClient(transport); export async function getBrandingSettings(organization?: string) { - return unstable_cache( - async () => { - return await settingsService - .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) - .then((resp) => - resp.settings - ? toJson(BrandingSettingsSchema, resp.settings) - : undefined, - ); - }, - ["brandingSettings", organization ?? "default"], - { - revalidate: CACHE_REVALIDATION_INTERVAL_IN_SECONDS, - tags: ["brandingSettings"], - }, - )().then((resp) => - resp ? fromJson(BrandingSettingsSchema, resp) : undefined, - ); + "use cache"; + cacheLife("hours"); + + return settingsService + .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) + .then((resp) => (resp.settings ? resp.settings : undefined)); } export async function getLoginSettings(orgId?: string) { - return unstable_cache( - async () => { - return await settingsService - .getLoginSettings({ ctx: makeReqCtx(orgId) }, {}) - .then((resp) => - resp.settings - ? toJson(LoginSettingsSchema, resp.settings) - : undefined, - ); - }, - ["loginSettings", orgId ?? "default"], - { - revalidate: CACHE_REVALIDATION_INTERVAL_IN_SECONDS, - tags: ["loginSettings"], - }, - )().then((resp) => (resp ? fromJson(LoginSettingsSchema, resp) : undefined)); + "use cache"; + return await settingsService + .getLoginSettings({ ctx: makeReqCtx(orgId) }, {}) + .then((resp) => (resp.settings ? resp.settings : undefined)); } export async function listIDPLinks(userId: string) { @@ -132,51 +102,24 @@ export async function registerTOTP(userId: string) { } export async function getGeneralSettings() { + "use cache"; return settingsService .getGeneralSettings({}, {}) .then((resp) => resp.supportedLanguages); } export async function getLegalAndSupportSettings(organization?: string) { - return unstable_cache( - async () => { - return await settingsService - .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) - .then((resp) => - resp.settings - ? toJson(LegalAndSupportSettingsSchema, resp.settings) - : undefined, - ); - }, - ["legalAndSupportSettings", organization ?? "default"], - { - revalidate: CACHE_REVALIDATION_INTERVAL_IN_SECONDS, - tags: ["legalAndSupportSettings"], - }, - )().then((resp) => - resp ? fromJson(LegalAndSupportSettingsSchema, resp) : undefined, - ); + "use cache"; + return settingsService + .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) + .then((resp) => (resp.settings ? resp.settings : undefined)); } export async function getPasswordComplexitySettings(organization?: string) { - return unstable_cache( - async () => { - return await settingsService - .getPasswordComplexitySettings({ ctx: makeReqCtx(organization) }) - .then((resp) => - resp.settings - ? toJson(PasswordComplexitySettingsSchema, resp.settings) - : undefined, - ); - }, - ["complexitySettings", organization ?? "default"], - { - revalidate: CACHE_REVALIDATION_INTERVAL_IN_SECONDS, - tags: ["complexitySettings"], - }, - )().then((resp) => - resp ? fromJson(PasswordComplexitySettingsSchema, resp) : undefined, - ); + "use cache"; + return settingsService + .getPasswordComplexitySettings({ ctx: makeReqCtx(organization) }) + .then((resp) => (resp.settings ? resp.settings : undefined)); } export async function createSessionFromChecks( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f326d64a99..320e21c122 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,16 +64,16 @@ importers: dependencies: '@headlessui/react': specifier: ^2.1.9 - version: 2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.1.9(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) '@heroicons/react': specifier: 2.1.3 - version: 2.1.3(react@18.3.1) + version: 2.1.3(react@19.0.0-rc-66855b96-20241106) '@tailwindcss/forms': specifier: 0.5.7 version: 0.5.7(tailwindcss@3.4.14) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7))(react@18.3.1) + version: 1.3.1(next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -96,32 +96,32 @@ importers: specifier: ^2.29.4 version: 2.30.1 next: - specifier: 14.2.14 - version: 14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7) + specifier: 15.0.3 + version: 15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) next-intl: specifier: ^3.20.0 - version: 3.20.0(next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7))(react@18.3.1) + version: 3.20.0(next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.2.1(next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) nice-grpc: specifier: 2.0.1 version: 2.0.1 qrcode.react: specifier: ^3.1.0 - version: 3.1.0(react@18.3.1) + version: 3.1.0(react@19.0.0-rc-66855b96-20241106) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: 19.0.0-rc-66855b96-20241106 + version: 19.0.0-rc-66855b96-20241106 react-dom: - specifier: 18.3.1 - version: 18.3.1(react@18.3.1) + specifier: 19.0.0-rc-66855b96-20241106 + version: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) react-hook-form: specifier: 7.39.5 - version: 7.39.5(react@18.3.1) + version: 7.39.5(react@19.0.0-rc-66855b96-20241106) swr: specifier: ^2.2.0 - version: 2.2.5(react@18.3.1) + version: 2.2.5(react@19.0.0-rc-66855b96-20241106) tinycolor2: specifier: 1.4.2 version: 1.4.2 @@ -134,7 +134,7 @@ importers: version: 6.6.3 '@testing-library/react': specifier: ^16.0.1 - version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 16.0.1(@testing-library/dom@10.4.0)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) '@types/ms': specifier: 0.7.34 version: 0.7.34 @@ -142,11 +142,11 @@ importers: specifier: 22.9.0 version: 22.9.0 '@types/react': - specifier: 18.3.12 - version: 18.3.12 + specifier: npm:types-react@19.0.0-rc.1 + version: types-react@19.0.0-rc.1 '@types/react-dom': - specifier: 18.3.1 - version: 18.3.1 + specifier: npm:types-react-dom@19.0.0-rc.1 + version: types-react-dom@19.0.0-rc.1 '@types/tinycolor2': specifier: 1.4.3 version: 1.4.3 @@ -412,79 +412,79 @@ packages: engines: {node: '>=6.9.0'} '@bufbuild/buf-darwin-arm64@1.46.0': - resolution: {integrity: sha512-lSmTKyRhg+71acXp9QeX/wm+vjkf0J3n38wph7KOwMfCEeK4A2AkqsGOkoXSiaIvidA2pRU9RJRQYfryzCA9Pg==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.46.0.tgz} + resolution: {integrity: sha512-lSmTKyRhg+71acXp9QeX/wm+vjkf0J3n38wph7KOwMfCEeK4A2AkqsGOkoXSiaIvidA2pRU9RJRQYfryzCA9Pg==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@bufbuild/buf-darwin-arm64@1.47.2': - resolution: {integrity: sha512-74WerFn06y+azgVfsnzhfbI5wla/OLPDnIvaNJBWHaqya/3bfascJkDylW2GVNHmwG1K/cscpmcc/RJPaO7ntQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.47.2.tgz} + resolution: {integrity: sha512-74WerFn06y+azgVfsnzhfbI5wla/OLPDnIvaNJBWHaqya/3bfascJkDylW2GVNHmwG1K/cscpmcc/RJPaO7ntQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@bufbuild/buf-darwin-x64@1.46.0': - resolution: {integrity: sha512-Oa9XTLJshsEjzowyt2mH9XrXW38DRFdz7ml+IYKXVQPotNLr04ix7QES7A1eOBJtxLwuTiri4ScXuBLQGNX8+A==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.46.0.tgz} + resolution: {integrity: sha512-Oa9XTLJshsEjzowyt2mH9XrXW38DRFdz7ml+IYKXVQPotNLr04ix7QES7A1eOBJtxLwuTiri4ScXuBLQGNX8+A==} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@bufbuild/buf-darwin-x64@1.47.2': - resolution: {integrity: sha512-adAiOacOQe8Ym/YXPCEiq9mrPeKRmDtF2TgqPWTcDy6mF7TqR7hMJINkEEuMd1EeACmXnzMOnXlm9ICtvdYgPg==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.47.2.tgz} + resolution: {integrity: sha512-adAiOacOQe8Ym/YXPCEiq9mrPeKRmDtF2TgqPWTcDy6mF7TqR7hMJINkEEuMd1EeACmXnzMOnXlm9ICtvdYgPg==} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@bufbuild/buf-linux-aarch64@1.46.0': - resolution: {integrity: sha512-CbxbLH5sQCRjEKVEcWJySvCKyAPAUhX0vCTifT/eQyZ70FUsqCJKJ6+dKl6Ajk0CgUHqf8jkU/wX/+aQFYXyaA==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.46.0.tgz} + resolution: {integrity: sha512-CbxbLH5sQCRjEKVEcWJySvCKyAPAUhX0vCTifT/eQyZ70FUsqCJKJ6+dKl6Ajk0CgUHqf8jkU/wX/+aQFYXyaA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@bufbuild/buf-linux-aarch64@1.47.2': - resolution: {integrity: sha512-52vY+Owffr5diw2PyfQJqH+Fld6zW6NhNZak4zojvc2MjZKubWM0TfNyM9jXz2YrwyB+cyxkabE60nBI80m37w==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.47.2.tgz} + resolution: {integrity: sha512-52vY+Owffr5diw2PyfQJqH+Fld6zW6NhNZak4zojvc2MjZKubWM0TfNyM9jXz2YrwyB+cyxkabE60nBI80m37w==} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@bufbuild/buf-linux-armv7@1.47.2': - resolution: {integrity: sha512-g9KtpObDeHZ/VG/0b5ZCieOao7L/WYZ0fPqFSs4N07D3APgEDhJG6vLyUcDgJMDgyLcgkNjNz0+XdYQb/tXyQw==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-armv7/-/buf-linux-armv7-1.47.2.tgz} + resolution: {integrity: sha512-g9KtpObDeHZ/VG/0b5ZCieOao7L/WYZ0fPqFSs4N07D3APgEDhJG6vLyUcDgJMDgyLcgkNjNz0+XdYQb/tXyQw==} engines: {node: '>=12'} cpu: [arm] os: [linux] '@bufbuild/buf-linux-x64@1.46.0': - resolution: {integrity: sha512-bMqp+Q+16KPbuwX34/OLDeiimnwt5sfvHqyeMeRz4LLwLshbmM3m+8dGCSHZRo3Lr+4gW1PfunrfaEmcGqPHLQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.46.0.tgz} + resolution: {integrity: sha512-bMqp+Q+16KPbuwX34/OLDeiimnwt5sfvHqyeMeRz4LLwLshbmM3m+8dGCSHZRo3Lr+4gW1PfunrfaEmcGqPHLQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] '@bufbuild/buf-linux-x64@1.47.2': - resolution: {integrity: sha512-MODCK2BzD1Mgoyr+5Sp8xA8qMNdytj8hYheyhA5NnCGTkQf8sfqAjpBSAAmKk6Zar8HOlVXML6tzE/ioDFFGwQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.47.2.tgz} + resolution: {integrity: sha512-MODCK2BzD1Mgoyr+5Sp8xA8qMNdytj8hYheyhA5NnCGTkQf8sfqAjpBSAAmKk6Zar8HOlVXML6tzE/ioDFFGwQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] '@bufbuild/buf-win32-arm64@1.46.0': - resolution: {integrity: sha512-geVYXp1PWJiAAFpwhgP8Cnct0+Rdr89BF/WZoIh5WwFGYITGiu5Hb1Ui9DTrEYwDzahPCyPxgIVwzzW6kPWSag==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.46.0.tgz} + resolution: {integrity: sha512-geVYXp1PWJiAAFpwhgP8Cnct0+Rdr89BF/WZoIh5WwFGYITGiu5Hb1Ui9DTrEYwDzahPCyPxgIVwzzW6kPWSag==} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@bufbuild/buf-win32-arm64@1.47.2': - resolution: {integrity: sha512-563YKYWJl3LrCY3G3+zuhb8HwOs6DzWslwGPFkKV2hwHyWyvd1DR1JjiLvw9zX64IKNctQ0HempSqc3kcboaqQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.47.2.tgz} + resolution: {integrity: sha512-563YKYWJl3LrCY3G3+zuhb8HwOs6DzWslwGPFkKV2hwHyWyvd1DR1JjiLvw9zX64IKNctQ0HempSqc3kcboaqQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@bufbuild/buf-win32-x64@1.46.0': - resolution: {integrity: sha512-6nsxkzj5a1L41NOJFKjli8j6GB/NkPHLIr0T/b27Y3GfprVYQawOComYD5HfojvBLuAiE2cD/kEQIWKK1YRcng==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.46.0.tgz} + resolution: {integrity: sha512-6nsxkzj5a1L41NOJFKjli8j6GB/NkPHLIr0T/b27Y3GfprVYQawOComYD5HfojvBLuAiE2cD/kEQIWKK1YRcng==} engines: {node: '>=12'} cpu: [x64] os: [win32] '@bufbuild/buf-win32-x64@1.47.2': - resolution: {integrity: sha512-Sqcdv7La2xBDh3bTdEYb2f4UTMMqCcYe/D0RELhvQ5wDn6I35V3/2YT1OF5fRuf0BZLCo0OdO37S9L47uHSz2g==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.47.2.tgz} + resolution: {integrity: sha512-Sqcdv7La2xBDh3bTdEYb2f4UTMMqCcYe/D0RELhvQ5wDn6I35V3/2YT1OF5fRuf0BZLCo0OdO37S9L47uHSz2g==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -495,7 +495,7 @@ packages: hasBin: true '@bufbuild/buf@1.47.2': - resolution: {integrity: sha512-glY5kCAoO4+a7HvDb+BLOdoHSdCk4mdXdkp53H8JFz7maOnkxCiHHXgRX+taFyEu25N8ybn7NjZFrZSdRwq2sA==, tarball: https://registry.npmjs.org/@bufbuild/buf/-/buf-1.47.2.tgz} + resolution: {integrity: sha512-glY5kCAoO4+a7HvDb+BLOdoHSdCk4mdXdkp53H8JFz7maOnkxCiHHXgRX+taFyEu25N8ybn7NjZFrZSdRwq2sA==} engines: {node: '>=12'} hasBin: true @@ -503,7 +503,7 @@ packages: resolution: {integrity: sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ==} '@bufbuild/protobuf@2.2.2': - resolution: {integrity: sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==, tarball: https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.2.tgz} + resolution: {integrity: sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==} '@bufbuild/protocompile@0.0.1': resolution: {integrity: sha512-cOTMtjcWLcbjF17dPYgeMtVC5jZyS0bSjz3jy8kDPjOgjgSYMD2u2It7w8aCc2z23hTPIKl/2SNdMnz0Jzu3Xg==} @@ -566,24 +566,24 @@ packages: resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==, tarball: https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz} + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} '@connectrpc/connect-node@2.0.0': - resolution: {integrity: sha512-DoI5T+SUvlS/8QBsxt2iDoUg15dSxqhckegrgZpWOtADtmGohBIVbx1UjtWmjLBrP4RdD0FeBw+XyRUSbpKnJQ==, tarball: https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-2.0.0.tgz} + resolution: {integrity: sha512-DoI5T+SUvlS/8QBsxt2iDoUg15dSxqhckegrgZpWOtADtmGohBIVbx1UjtWmjLBrP4RdD0FeBw+XyRUSbpKnJQ==} engines: {node: '>=18.14.1'} peerDependencies: '@bufbuild/protobuf': ^2.2.0 '@connectrpc/connect': 2.0.0 '@connectrpc/connect-web@2.0.0': - resolution: {integrity: sha512-oeCxqHXLXlWJdmcvp9L3scgAuK+FjNSn+twyhUxc8yvDbTumnt5Io+LnBzSYxAdUdYqTw5yHfTSCJ4hj0QID0g==, tarball: https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-2.0.0.tgz} + resolution: {integrity: sha512-oeCxqHXLXlWJdmcvp9L3scgAuK+FjNSn+twyhUxc8yvDbTumnt5Io+LnBzSYxAdUdYqTw5yHfTSCJ4hj0QID0g==} peerDependencies: '@bufbuild/protobuf': ^2.2.0 '@connectrpc/connect': 2.0.0 '@connectrpc/connect@2.0.0': - resolution: {integrity: sha512-Usm8jgaaULANJU8vVnhWssSA6nrZ4DJEAbkNtXSoZay2YD5fDyMukCxu8NEhCvFzfHvrhxhcjttvgpyhOM7xAQ==, tarball: https://registry.npmjs.org/@connectrpc/connect/-/connect-2.0.0.tgz} + resolution: {integrity: sha512-Usm8jgaaULANJU8vVnhWssSA6nrZ4DJEAbkNtXSoZay2YD5fDyMukCxu8NEhCvFzfHvrhxhcjttvgpyhOM7xAQ==} peerDependencies: '@bufbuild/protobuf': ^2.2.0 @@ -594,284 +594,287 @@ packages: '@cypress/xvfb@1.2.4': resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} + '@emnapi/runtime@1.3.1': + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz} + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] '@esbuild/aix-ppc64@0.24.0': - resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz} + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz} + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] '@esbuild/android-arm64@0.24.0': - resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz} + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} engines: {node: '>=18'} cpu: [arm64] os: [android] '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz} + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] '@esbuild/android-arm@0.24.0': - resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz} + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} engines: {node: '>=18'} cpu: [arm] os: [android] '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz} + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] '@esbuild/android-x64@0.24.0': - resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz} + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} engines: {node: '>=18'} cpu: [x64] os: [android] '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz} + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@esbuild/darwin-arm64@0.24.0': - resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz} + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz} + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@esbuild/darwin-x64@0.24.0': - resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz} + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz} + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-arm64@0.24.0': - resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz} + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] '@esbuild/freebsd-x64@0.24.0': - resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz} + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz} + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@esbuild/linux-arm64@0.24.0': - resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz} + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz} + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] '@esbuild/linux-arm@0.24.0': - resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz} + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} engines: {node: '>=18'} cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz} + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] '@esbuild/linux-ia32@0.24.0': - resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz} + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz} + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] '@esbuild/linux-loong64@0.24.0': - resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz} + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} engines: {node: '>=18'} cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz} + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] '@esbuild/linux-mips64el@0.24.0': - resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz} + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz} + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] '@esbuild/linux-ppc64@0.24.0': - resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz} + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz} + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] '@esbuild/linux-riscv64@0.24.0': - resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz} + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz} + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] '@esbuild/linux-s390x@0.24.0': - resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz} + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} engines: {node: '>=18'} cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz} + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] '@esbuild/linux-x64@0.24.0': - resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz} + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} engines: {node: '>=18'} cpu: [x64] os: [linux] '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] '@esbuild/netbsd-x64@0.24.0': - resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz} + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] '@esbuild/openbsd-arm64@0.24.0': - resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz} + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] '@esbuild/openbsd-x64@0.24.0': - resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz} + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz} + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] '@esbuild/sunos-x64@0.24.0': - resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz} + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz} + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@esbuild/win32-arm64@0.24.0': - resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz} + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz} + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] '@esbuild/win32-ia32@0.24.0': - resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz} + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz} + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] '@esbuild/win32-x64@0.24.0': - resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz} + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -970,6 +973,111 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1005,62 +1113,56 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true - '@next/env@14.2.14': - resolution: {integrity: sha512-/0hWQfiaD5//LvGNgc8PjvyqV50vGK0cADYzaoOOGN8fxzBn3iAiaq3S0tCRnFBldq0LVveLcxCTi41ZoYgAgg==} + '@next/env@15.0.3': + resolution: {integrity: sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==} '@next/eslint-plugin-next@14.2.7': resolution: {integrity: sha512-+7xh142AdhZGjY9/L0iFo7mqRBMJHe+q+uOL+hto1Lfo9DeWCGcR6no4StlFbVSVcA6fQLKEX6y6qhMsSKbgNQ==} - '@next/swc-darwin-arm64@14.2.14': - resolution: {integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==, tarball: https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.14.tgz} + '@next/swc-darwin-arm64@15.0.3': + resolution: {integrity: sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.14': - resolution: {integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==, tarball: https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.14.tgz} + '@next/swc-darwin-x64@15.0.3': + resolution: {integrity: sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.14': - resolution: {integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==, tarball: https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.14.tgz} + '@next/swc-linux-arm64-gnu@15.0.3': + resolution: {integrity: sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.14': - resolution: {integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==, tarball: https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.14.tgz} + '@next/swc-linux-arm64-musl@15.0.3': + resolution: {integrity: sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.14': - resolution: {integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==, tarball: https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.14.tgz} + '@next/swc-linux-x64-gnu@15.0.3': + resolution: {integrity: sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.14': - resolution: {integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==, tarball: https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.14.tgz} + '@next/swc-linux-x64-musl@15.0.3': + resolution: {integrity: sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.14': - resolution: {integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==, tarball: https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.14.tgz} + '@next/swc-win32-arm64-msvc@15.0.3': + resolution: {integrity: sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.14': - resolution: {integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==, tarball: https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.14.tgz} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@next/swc-win32-x64-msvc@14.2.14': - resolution: {integrity: sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg==, tarball: https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.14.tgz} + '@next/swc-win32-x64-msvc@15.0.3': + resolution: {integrity: sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1082,89 +1184,89 @@ packages: engines: {node: '>=12.4.0'} '@parcel/watcher-android-arm64@2.5.0': - resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==, tarball: https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz} + resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [android] '@parcel/watcher-darwin-arm64@2.5.0': - resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==, tarball: https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz} + resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [darwin] '@parcel/watcher-darwin-x64@2.5.0': - resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==, tarball: https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz} + resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [darwin] '@parcel/watcher-freebsd-x64@2.5.0': - resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==, tarball: https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz} + resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [freebsd] '@parcel/watcher-linux-arm-glibc@2.5.0': - resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz} + resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] '@parcel/watcher-linux-arm-musl@2.5.0': - resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz} + resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] '@parcel/watcher-linux-arm64-glibc@2.5.0': - resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz} + resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] '@parcel/watcher-linux-arm64-musl@2.5.0': - resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz} + resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] '@parcel/watcher-linux-x64-glibc@2.5.0': - resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz} + resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] '@parcel/watcher-linux-x64-musl@2.5.0': - resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz} + resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] '@parcel/watcher-win32-arm64@2.5.0': - resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==, tarball: https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz} + resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [win32] '@parcel/watcher-win32-ia32@2.5.0': - resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==, tarball: https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz} + resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==} engines: {node: '>= 10.0.0'} cpu: [ia32] os: [win32] '@parcel/watcher-win32-x64@2.5.0': - resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==, tarball: https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz} + resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [win32] '@parcel/watcher@2.5.0': - resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==, tarball: https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz} + resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==} engines: {node: '>= 10.0.0'} '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, tarball: https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz} + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} '@playwright/test@1.48.2': @@ -1234,92 +1336,92 @@ packages: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 '@rollup/rollup-android-arm-eabi@4.25.0': - resolution: {integrity: sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.25.0.tgz} + resolution: {integrity: sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==} cpu: [arm] os: [android] '@rollup/rollup-android-arm64@4.25.0': - resolution: {integrity: sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.25.0.tgz} + resolution: {integrity: sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==} cpu: [arm64] os: [android] '@rollup/rollup-darwin-arm64@4.25.0': - resolution: {integrity: sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.25.0.tgz} + resolution: {integrity: sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==} cpu: [arm64] os: [darwin] '@rollup/rollup-darwin-x64@4.25.0': - resolution: {integrity: sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.25.0.tgz} + resolution: {integrity: sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==} cpu: [x64] os: [darwin] '@rollup/rollup-freebsd-arm64@4.25.0': - resolution: {integrity: sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.25.0.tgz} + resolution: {integrity: sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==} cpu: [arm64] os: [freebsd] '@rollup/rollup-freebsd-x64@4.25.0': - resolution: {integrity: sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.25.0.tgz} + resolution: {integrity: sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==} cpu: [x64] os: [freebsd] '@rollup/rollup-linux-arm-gnueabihf@4.25.0': - resolution: {integrity: sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.25.0.tgz} + resolution: {integrity: sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==} cpu: [arm] os: [linux] '@rollup/rollup-linux-arm-musleabihf@4.25.0': - resolution: {integrity: sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.25.0.tgz} + resolution: {integrity: sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==} cpu: [arm] os: [linux] '@rollup/rollup-linux-arm64-gnu@4.25.0': - resolution: {integrity: sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.25.0.tgz} + resolution: {integrity: sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==} cpu: [arm64] os: [linux] '@rollup/rollup-linux-arm64-musl@4.25.0': - resolution: {integrity: sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.25.0.tgz} + resolution: {integrity: sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==} cpu: [arm64] os: [linux] '@rollup/rollup-linux-powerpc64le-gnu@4.25.0': - resolution: {integrity: sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.25.0.tgz} + resolution: {integrity: sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==} cpu: [ppc64] os: [linux] '@rollup/rollup-linux-riscv64-gnu@4.25.0': - resolution: {integrity: sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.25.0.tgz} + resolution: {integrity: sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==} cpu: [riscv64] os: [linux] '@rollup/rollup-linux-s390x-gnu@4.25.0': - resolution: {integrity: sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.25.0.tgz} + resolution: {integrity: sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==} cpu: [s390x] os: [linux] '@rollup/rollup-linux-x64-gnu@4.25.0': - resolution: {integrity: sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.25.0.tgz} + resolution: {integrity: sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==} cpu: [x64] os: [linux] '@rollup/rollup-linux-x64-musl@4.25.0': - resolution: {integrity: sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.25.0.tgz} + resolution: {integrity: sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==} cpu: [x64] os: [linux] '@rollup/rollup-win32-arm64-msvc@4.25.0': - resolution: {integrity: sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.25.0.tgz} + resolution: {integrity: sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==} cpu: [arm64] os: [win32] '@rollup/rollup-win32-ia32-msvc@4.25.0': - resolution: {integrity: sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.25.0.tgz} + resolution: {integrity: sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==} cpu: [ia32] os: [win32] '@rollup/rollup-win32-x64-msvc@4.25.0': - resolution: {integrity: sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.25.0.tgz} + resolution: {integrity: sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==} cpu: [x64] os: [win32] @@ -1342,6 +1444,9 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + '@swc/helpers@0.5.13': + resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} + '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} @@ -1365,7 +1470,7 @@ packages: resolution: {integrity: sha512-1giLc4dzgEKLMx5pgKjL6HlG5fjZMgCjzlKAlpr7yoUtetVPELgER1NtephAI910nMwfPTHNyWKSFmJdHkz2Cw==} '@testing-library/dom@10.4.0': - resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==, tarball: https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz} + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} '@testing-library/jest-dom@6.6.3': @@ -1388,7 +1493,7 @@ packages: optional: true '@types/aria-query@5.0.4': - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==, tarball: https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz} + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1420,9 +1525,6 @@ packages: '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - '@types/react-dom@18.3.1': - resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} - '@types/react@18.3.12': resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} @@ -1439,7 +1541,7 @@ packages: resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==, tarball: https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz} + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} '@typescript-eslint/parser@7.18.0': resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} @@ -1580,7 +1682,7 @@ packages: engines: {node: '>=8'} ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==, tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz} + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} ansi-styles@6.2.1: @@ -1793,9 +1895,6 @@ packages: caniuse-lite@1.0.30001654: resolution: {integrity: sha512-wLJc602fW0OdrUR+PqsBUH3dgrjDcT+mWs/Kw86zPvgjiqOiI2TXMkBFK4KihYzZclmJxrFwgYhZDSEogFai/g==} - caniuse-lite@1.0.30001666: - resolution: {integrity: sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==} - caniuse-lite@1.0.30001680: resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==} @@ -1899,10 +1998,17 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -2111,7 +2217,7 @@ packages: engines: {node: '>=6.0.0'} dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==, tarball: https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz} + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} @@ -2517,12 +2623,12 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz} + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz} + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] @@ -2770,6 +2876,9 @@ packages: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + is-async-function@2.0.0: resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} engines: {node: '>= 0.4'} @@ -3132,7 +3241,7 @@ packages: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==, tarball: https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz} + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true magic-string@0.30.12: @@ -3269,21 +3378,24 @@ packages: react: '*' react-dom: '*' - next@14.2.14: - resolution: {integrity: sha512-Q1coZG17MW0Ly5x76shJ4dkC23woLAhhnDnw+DfTc7EpZSGuWrlsZ3bZaO8t6u1Yu8FVfhkqJE+U8GC7E0GLPQ==} - engines: {node: '>=18.17.0'} + next@15.0.3: + resolution: {integrity: sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 '@playwright/test': ^1.41.2 - react: ^18.2.0 - react-dom: ^18.2.0 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-66855b96-20241106 + react-dom: ^18.2.0 || 19.0.0-rc-66855b96-20241106 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': optional: true '@playwright/test': optional: true + babel-plugin-react-compiler: + optional: true sass: optional: true @@ -3698,7 +3810,7 @@ packages: engines: {node: '>=6'} pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==, tarball: https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz} + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} process@0.11.10: @@ -3748,10 +3860,10 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + react-dom@19.0.0-rc-66855b96-20241106: + resolution: {integrity: sha512-D25vdaytZ1wFIRiwNU98NPQ/upS2P8Co4/oNoa02PzHbh8deWdepjm5qwZM/46OdSiGv4WSWwxP55RO9obqJEQ==} peerDependencies: - react: ^18.3.1 + react: 19.0.0-rc-66855b96-20241106 react-hook-form@7.39.5: resolution: {integrity: sha512-OE0HKyz5IPc6svN2wd+e+evidZrw4O4WZWAWYzQVZuHi+hYnHFSLnxOq0ddjbdmaLIsLHut/ab7j72y2QT3+KA==} @@ -3763,14 +3875,14 @@ packages: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==, tarball: https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz} + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + react@19.0.0-rc-66855b96-20241106: + resolution: {integrity: sha512-klH7xkT71SxRCx4hb1hly5FJB21Hz0ACyxbXYAECEqssUjtJeFUAaI2U1DgJAzkGEnvEm3DkxuBchMC/9K4ipg==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -3890,8 +4002,8 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.25.0-rc-66855b96-20241106: + resolution: {integrity: sha512-HQXp/Mnp/MMRSXMQF7urNFla+gmtXW/Gr1KliuR0iboTit4KvZRY8KYaq5ccCTAOJiUqQh2rE2F3wgUekmgdlA==} semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} @@ -3916,6 +4028,10 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} @@ -3949,6 +4065,9 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} @@ -4089,13 +4208,13 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - styled-jsx@5.1.1: - resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} peerDependencies: '@babel/core': '*' babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' peerDependenciesMeta: '@babel/core': optional: true @@ -4297,32 +4416,32 @@ packages: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} turbo-darwin-64@2.2.3: - resolution: {integrity: sha512-Rcm10CuMKQGcdIBS3R/9PMeuYnv6beYIHqfZFeKWVYEWH69sauj4INs83zKMTUiZJ3/hWGZ4jet9AOwhsssLyg==, tarball: https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.2.3.tgz} + resolution: {integrity: sha512-Rcm10CuMKQGcdIBS3R/9PMeuYnv6beYIHqfZFeKWVYEWH69sauj4INs83zKMTUiZJ3/hWGZ4jet9AOwhsssLyg==} cpu: [x64] os: [darwin] turbo-darwin-arm64@2.2.3: - resolution: {integrity: sha512-+EIMHkuLFqUdJYsA3roj66t9+9IciCajgj+DVek+QezEdOJKcRxlvDOS2BUaeN8kEzVSsNiAGnoysFWYw4K0HA==, tarball: https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.2.3.tgz} + resolution: {integrity: sha512-+EIMHkuLFqUdJYsA3roj66t9+9IciCajgj+DVek+QezEdOJKcRxlvDOS2BUaeN8kEzVSsNiAGnoysFWYw4K0HA==} cpu: [arm64] os: [darwin] turbo-linux-64@2.2.3: - resolution: {integrity: sha512-UBhJCYnqtaeOBQLmLo8BAisWbc9v9daL9G8upLR+XGj6vuN/Nz6qUAhverN4Pyej1g4Nt1BhROnj6GLOPYyqxQ==, tarball: https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.2.3.tgz} + resolution: {integrity: sha512-UBhJCYnqtaeOBQLmLo8BAisWbc9v9daL9G8upLR+XGj6vuN/Nz6qUAhverN4Pyej1g4Nt1BhROnj6GLOPYyqxQ==} cpu: [x64] os: [linux] turbo-linux-arm64@2.2.3: - resolution: {integrity: sha512-hJYT9dN06XCQ3jBka/EWvvAETnHRs3xuO/rb5bESmDfG+d9yQjeTMlhRXKrr4eyIMt6cLDt1LBfyi+6CQ+VAwQ==, tarball: https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.2.3.tgz} + resolution: {integrity: sha512-hJYT9dN06XCQ3jBka/EWvvAETnHRs3xuO/rb5bESmDfG+d9yQjeTMlhRXKrr4eyIMt6cLDt1LBfyi+6CQ+VAwQ==} cpu: [arm64] os: [linux] turbo-windows-64@2.2.3: - resolution: {integrity: sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==, tarball: https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.2.3.tgz} + resolution: {integrity: sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==} cpu: [x64] os: [win32] turbo-windows-arm64@2.2.3: - resolution: {integrity: sha512-fnNrYBCqn6zgKPKLHu4sOkihBI/+0oYFr075duRxqUZ+1aLWTAGfHZLgjVeLh3zR37CVzuerGIPWAEkNhkWEIw==, tarball: https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.2.3.tgz} + resolution: {integrity: sha512-fnNrYBCqn6zgKPKLHu4sOkihBI/+0oYFr075duRxqUZ+1aLWTAGfHZLgjVeLh3zR37CVzuerGIPWAEkNhkWEIw==} cpu: [arm64] os: [win32] @@ -4361,6 +4480,12 @@ packages: resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} + types-react-dom@19.0.0-rc.1: + resolution: {integrity: sha512-VSLZJl8VXCD0fAWp7DUTFUDCcZ8DVXOQmjhJMD03odgeFmu14ZQJHCXeETm3BEAhJqfgJaFkLnGkQv88sRx0fQ==} + + types-react@19.0.0-rc.1: + resolution: {integrity: sha512-RshndUfqTW6K3STLPis8BtAYCGOkMbtvYsi90gmVNDZBXUyUc5juf2PE9LfS/JmOlUIRO8cWTS/1MTnmhjDqyQ==} + typescript@5.6.3: resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} @@ -5024,6 +5149,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@emnapi/runtime@1.3.1': + dependencies: + tslib: 2.7.0 + optional: true + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -5197,18 +5327,18 @@ snapshots: '@floating-ui/core': 1.6.8 '@floating-ui/utils': 0.2.8 - '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@floating-ui/react-dom@2.1.2(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: '@floating-ui/dom': 1.6.11 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) - '@floating-ui/react@0.26.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@floating-ui/react@0.26.24(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) '@floating-ui/utils': 0.2.8 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) tabbable: 6.2.0 '@floating-ui/utils@0.2.8': {} @@ -5256,18 +5386,18 @@ snapshots: dependencies: '@hapi/hoek': 9.3.0 - '@headlessui/react@2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@headlessui/react@2.1.9(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: - '@floating-ui/react': 0.26.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/focus': 3.18.3(react@18.3.1) - '@react-aria/interactions': 3.22.3(react@18.3.1) - '@tanstack/react-virtual': 3.10.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@floating-ui/react': 0.26.24(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@react-aria/focus': 3.18.3(react@19.0.0-rc-66855b96-20241106) + '@react-aria/interactions': 3.22.3(react@19.0.0-rc-66855b96-20241106) + '@tanstack/react-virtual': 3.10.6(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) - '@heroicons/react@2.1.3(react@18.3.1)': + '@heroicons/react@2.1.3(react@19.0.0-rc-66855b96-20241106)': dependencies: - react: 18.3.1 + react: 19.0.0-rc-66855b96-20241106 '@humanwhocodes/config-array@0.13.0': dependencies: @@ -5281,6 +5411,81 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.3.1 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -5340,37 +5545,34 @@ snapshots: - encoding - supports-color - '@next/env@14.2.14': {} + '@next/env@15.0.3': {} '@next/eslint-plugin-next@14.2.7': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@14.2.14': + '@next/swc-darwin-arm64@15.0.3': optional: true - '@next/swc-darwin-x64@14.2.14': + '@next/swc-darwin-x64@15.0.3': optional: true - '@next/swc-linux-arm64-gnu@14.2.14': + '@next/swc-linux-arm64-gnu@15.0.3': optional: true - '@next/swc-linux-arm64-musl@14.2.14': + '@next/swc-linux-arm64-musl@15.0.3': optional: true - '@next/swc-linux-x64-gnu@14.2.14': + '@next/swc-linux-x64-gnu@15.0.3': optional: true - '@next/swc-linux-x64-musl@14.2.14': + '@next/swc-linux-x64-musl@15.0.3': optional: true - '@next/swc-win32-arm64-msvc@14.2.14': + '@next/swc-win32-arm64-msvc@15.0.3': optional: true - '@next/swc-win32-ia32-msvc@14.2.14': - optional: true - - '@next/swc-win32-x64-msvc@14.2.14': + '@next/swc-win32-x64-msvc@15.0.3': optional: true '@nodelib/fs.scandir@2.1.5': @@ -5478,45 +5680,45 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@react-aria/focus@3.18.3(react@18.3.1)': + '@react-aria/focus@3.18.3(react@19.0.0-rc-66855b96-20241106)': dependencies: - '@react-aria/interactions': 3.22.3(react@18.3.1) - '@react-aria/utils': 3.25.3(react@18.3.1) - '@react-types/shared': 3.25.0(react@18.3.1) + '@react-aria/interactions': 3.22.3(react@19.0.0-rc-66855b96-20241106) + '@react-aria/utils': 3.25.3(react@19.0.0-rc-66855b96-20241106) + '@react-types/shared': 3.25.0(react@19.0.0-rc-66855b96-20241106) '@swc/helpers': 0.5.5 clsx: 2.1.1 - react: 18.3.1 + react: 19.0.0-rc-66855b96-20241106 - '@react-aria/interactions@3.22.3(react@18.3.1)': + '@react-aria/interactions@3.22.3(react@19.0.0-rc-66855b96-20241106)': dependencies: - '@react-aria/ssr': 3.9.6(react@18.3.1) - '@react-aria/utils': 3.25.3(react@18.3.1) - '@react-types/shared': 3.25.0(react@18.3.1) + '@react-aria/ssr': 3.9.6(react@19.0.0-rc-66855b96-20241106) + '@react-aria/utils': 3.25.3(react@19.0.0-rc-66855b96-20241106) + '@react-types/shared': 3.25.0(react@19.0.0-rc-66855b96-20241106) '@swc/helpers': 0.5.5 - react: 18.3.1 + react: 19.0.0-rc-66855b96-20241106 - '@react-aria/ssr@3.9.6(react@18.3.1)': + '@react-aria/ssr@3.9.6(react@19.0.0-rc-66855b96-20241106)': dependencies: '@swc/helpers': 0.5.5 - react: 18.3.1 + react: 19.0.0-rc-66855b96-20241106 - '@react-aria/utils@3.25.3(react@18.3.1)': + '@react-aria/utils@3.25.3(react@19.0.0-rc-66855b96-20241106)': dependencies: - '@react-aria/ssr': 3.9.6(react@18.3.1) - '@react-stately/utils': 3.10.4(react@18.3.1) - '@react-types/shared': 3.25.0(react@18.3.1) + '@react-aria/ssr': 3.9.6(react@19.0.0-rc-66855b96-20241106) + '@react-stately/utils': 3.10.4(react@19.0.0-rc-66855b96-20241106) + '@react-types/shared': 3.25.0(react@19.0.0-rc-66855b96-20241106) '@swc/helpers': 0.5.5 clsx: 2.1.1 - react: 18.3.1 + react: 19.0.0-rc-66855b96-20241106 - '@react-stately/utils@3.10.4(react@18.3.1)': + '@react-stately/utils@3.10.4(react@19.0.0-rc-66855b96-20241106)': dependencies: '@swc/helpers': 0.5.5 - react: 18.3.1 + react: 19.0.0-rc-66855b96-20241106 - '@react-types/shared@3.25.0(react@18.3.1)': + '@react-types/shared@3.25.0(react@19.0.0-rc-66855b96-20241106)': dependencies: - react: 18.3.1 + react: 19.0.0-rc-66855b96-20241106 '@rollup/rollup-android-arm-eabi@4.25.0': optional: true @@ -5586,6 +5788,10 @@ snapshots: '@swc/counter@0.1.3': {} + '@swc/helpers@0.5.13': + dependencies: + tslib: 2.7.0 + '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 @@ -5601,11 +5807,11 @@ snapshots: mini-svg-data-uri: 1.4.4 tailwindcss: 3.4.14 - '@tanstack/react-virtual@3.10.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@tanstack/react-virtual@3.10.6(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: '@tanstack/virtual-core': 3.10.6 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) '@tanstack/virtual-core@3.10.6': {} @@ -5630,15 +5836,15 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)': dependencies: '@babel/runtime': 7.25.6 '@testing-library/dom': 10.4.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) optionalDependencies: - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 + '@types/react': types-react@19.0.0-rc.1 + '@types/react-dom': types-react-dom@19.0.0-rc.1 '@types/aria-query@5.0.4': {} @@ -5677,10 +5883,6 @@ snapshots: '@types/prop-types@15.7.12': {} - '@types/react-dom@18.3.1': - dependencies: - '@types/react': 18.3.12 - '@types/react@18.3.12': dependencies: '@types/prop-types': 15.7.12 @@ -5741,12 +5943,12 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/analytics@1.3.1(next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7))(react@18.3.1)': + '@vercel/analytics@1.3.1(next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7) - react: 18.3.1 + next: 15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) + react: 19.0.0-rc-66855b96-20241106 '@vercel/git-hooks@1.0.0': {} @@ -6085,8 +6287,6 @@ snapshots: caniuse-lite@1.0.30001654: {} - caniuse-lite@1.0.30001666: {} - caniuse-lite@1.0.30001680: {} case-anything@2.1.13: {} @@ -6185,8 +6385,20 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + optional: true + color-support@1.1.3: {} + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + colorette@2.0.20: {} combined-stream@1.0.8: @@ -6694,7 +6906,7 @@ snapshots: debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 @@ -6707,7 +6919,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: @@ -6728,7 +6940,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -7329,6 +7541,9 @@ snapshots: call-bind: 1.0.7 get-intrinsic: 1.2.4 + is-arrayish@0.3.2: + optional: true + is-async-function@2.0.0: dependencies: has-tostringtag: 1.0.2 @@ -7792,43 +8007,43 @@ snapshots: negotiator@0.6.3: {} - next-intl@3.20.0(next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7))(react@18.3.1): + next-intl@3.20.0(next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next: 14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7) - react: 18.3.1 - use-intl: 3.20.0(react@18.3.1) + next: 15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) + react: 19.0.0-rc-66855b96-20241106 + use-intl: 3.20.0(react@19.0.0-rc-66855b96-20241106) - next-themes@0.2.1(next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-themes@0.2.1(next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106): dependencies: - next: 14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + next: 15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) - next@14.2.14(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.7): + next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7): dependencies: - '@next/env': 14.2.14 - '@swc/helpers': 0.5.5 + '@next/env': 15.0.3 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.13 busboy: 1.6.0 - caniuse-lite: 1.0.30001666 - graceful-fs: 4.2.11 + caniuse-lite: 1.0.30001680 postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(@babel/core@7.26.0)(react@18.3.1) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.0.0-rc-66855b96-20241106) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.14 - '@next/swc-darwin-x64': 14.2.14 - '@next/swc-linux-arm64-gnu': 14.2.14 - '@next/swc-linux-arm64-musl': 14.2.14 - '@next/swc-linux-x64-gnu': 14.2.14 - '@next/swc-linux-x64-musl': 14.2.14 - '@next/swc-win32-arm64-msvc': 14.2.14 - '@next/swc-win32-ia32-msvc': 14.2.14 - '@next/swc-win32-x64-msvc': 14.2.14 + '@next/swc-darwin-arm64': 15.0.3 + '@next/swc-darwin-x64': 15.0.3 + '@next/swc-linux-arm64-gnu': 15.0.3 + '@next/swc-linux-arm64-musl': 15.0.3 + '@next/swc-linux-x64-gnu': 15.0.3 + '@next/swc-linux-x64-musl': 15.0.3 + '@next/swc-win32-arm64-msvc': 15.0.3 + '@next/swc-win32-x64-msvc': 15.0.3 '@playwright/test': 1.48.2 sass: 1.80.7 + sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -8183,9 +8398,9 @@ snapshots: punycode@2.3.1: {} - qrcode.react@3.1.0(react@18.3.1): + qrcode.react@3.1.0(react@19.0.0-rc-66855b96-20241106): dependencies: - react: 18.3.1 + react: 19.0.0-rc-66855b96-20241106 qs@6.13.0: dependencies: @@ -8193,15 +8408,14 @@ snapshots: queue-microtask@1.2.3: {} - react-dom@18.3.1(react@18.3.1): + react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106): dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 + react: 19.0.0-rc-66855b96-20241106 + scheduler: 0.25.0-rc-66855b96-20241106 - react-hook-form@7.39.5(react@18.3.1): + react-hook-form@7.39.5(react@19.0.0-rc-66855b96-20241106): dependencies: - react: 18.3.1 + react: 19.0.0-rc-66855b96-20241106 react-is@16.13.1: {} @@ -8209,9 +8423,7 @@ snapshots: react-refresh@0.14.2: {} - react@18.3.1: - dependencies: - loose-envify: 1.4.0 + react@19.0.0-rc-66855b96-20241106: {} read-cache@1.0.0: dependencies: @@ -8365,9 +8577,7 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 + scheduler@0.25.0-rc-66855b96-20241106: {} semver@6.3.1: {} @@ -8393,6 +8603,33 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.6.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + optional: true + shebang-command@1.2.0: dependencies: shebang-regex: 1.0.0 @@ -8420,6 +8657,11 @@ snapshots: signal-exit@4.1.0: {} + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + optional: true + simple-update-notifier@2.0.0: dependencies: semver: 7.6.3 @@ -8594,10 +8836,10 @@ snapshots: strip-json-comments@3.1.1: {} - styled-jsx@5.1.1(@babel/core@7.26.0)(react@18.3.1): + styled-jsx@5.1.6(@babel/core@7.26.0)(react@19.0.0-rc-66855b96-20241106): dependencies: client-only: 0.0.1 - react: 18.3.1 + react: 19.0.0-rc-66855b96-20241106 optionalDependencies: '@babel/core': 7.26.0 @@ -8625,11 +8867,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - swr@2.2.5(react@18.3.1): + swr@2.2.5(react@19.0.0-rc-66855b96-20241106): dependencies: client-only: 0.0.1 - react: 18.3.1 - use-sync-external-store: 1.2.2(react@18.3.1) + react: 19.0.0-rc-66855b96-20241106 + use-sync-external-store: 1.2.2(react@19.0.0-rc-66855b96-20241106) symbol-tree@3.2.4: {} @@ -8878,6 +9120,14 @@ snapshots: is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 + types-react-dom@19.0.0-rc.1: + dependencies: + '@types/react': 18.3.12 + + types-react@19.0.0-rc.1: + dependencies: + csstype: 3.1.3 + typescript@5.6.3: {} unbox-primitive@1.0.2: @@ -8915,15 +9165,15 @@ snapshots: dependencies: punycode: 2.3.1 - use-intl@3.20.0(react@18.3.1): + use-intl@3.20.0(react@19.0.0-rc-66855b96-20241106): dependencies: '@formatjs/fast-memoize': 2.2.0 intl-messageformat: 10.6.0 - react: 18.3.1 + react: 19.0.0-rc-66855b96-20241106 - use-sync-external-store@1.2.2(react@18.3.1): + use-sync-external-store@1.2.2(react@19.0.0-rc-66855b96-20241106): dependencies: - react: 18.3.1 + react: 19.0.0-rc-66855b96-20241106 util-deprecate@1.0.2: {} From fe615a220a2843916667a8a132da6e77683ba9fe Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 22 Nov 2024 11:27:37 +0100 Subject: [PATCH 465/640] lint --- apps/login/src/app/(login)/accounts/page.tsx | 8 +++----- .../src/app/(login)/authenticator/set/page.tsx | 8 +++----- .../app/(login)/idp/[provider]/failure/page.tsx | 10 ++++------ .../app/(login)/idp/[provider]/success/page.tsx | 14 ++++++-------- apps/login/src/app/(login)/idp/page.tsx | 8 +++----- apps/login/src/app/(login)/invite/page.tsx | 8 +++----- apps/login/src/app/(login)/invite/success/page.tsx | 8 +++----- apps/login/src/app/(login)/loginname/page.tsx | 8 +++----- apps/login/src/app/(login)/mfa/page.tsx | 8 +++----- apps/login/src/app/(login)/mfa/set/page.tsx | 8 +++----- apps/login/src/app/(login)/otp/[method]/page.tsx | 10 ++++------ .../src/app/(login)/otp/[method]/set/page.tsx | 10 ++++------ apps/login/src/app/(login)/passkey/page.tsx | 8 +++----- apps/login/src/app/(login)/passkey/set/page.tsx | 8 +++----- .../login/src/app/(login)/password/change/page.tsx | 8 +++----- apps/login/src/app/(login)/password/page.tsx | 8 +++----- apps/login/src/app/(login)/password/set/page.tsx | 8 +++----- apps/login/src/app/(login)/register/page.tsx | 8 +++----- .../src/app/(login)/register/password/page.tsx | 8 +++----- apps/login/src/app/(login)/u2f/page.tsx | 8 +++----- apps/login/src/app/(login)/u2f/set/page.tsx | 8 +++----- apps/login/src/app/(login)/verify/page.tsx | 8 ++++---- apps/login/src/lib/cookies.ts | 2 +- 23 files changed, 74 insertions(+), 116 deletions(-) diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index e072d7c2e7..c3944a3768 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -20,11 +20,9 @@ async function loadSessions() { } } -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "accounts" }); diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index e2f093adb9..1c4422327f 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -15,11 +15,9 @@ import { import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "authenticator" }); diff --git a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx index 8729c1e978..a8bc6d9cb5 100644 --- a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -12,12 +12,10 @@ const PROVIDER_NAME_MAPPING: { [IdentityProviderType.AZURE_AD]: "Microsoft", }; -export default async function Page( - props: { - searchParams: Promise>; - params: Promise<{ provider: string }>; - } -) { +export default async function Page(props: { + searchParams: Promise>; + params: Promise<{ provider: string }>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "idp" }); diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index eb62bd35bc..48a035f34e 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -29,12 +29,10 @@ async function loginFailed(branding?: BrandingSettings) { ); } -export default async function Page( - props: { - searchParams: Promise>; - params: Promise<{ provider: string }>; - } -) { +export default async function Page(props: { + searchParams: Promise>; + params: Promise<{ provider: string }>; +}) { const params = await props.params; const searchParams = await props.searchParams; const locale = getLocale(); @@ -138,12 +136,12 @@ export default async function Page( if (idpLink) { return ( // TODO: possibily login user now - ( +

    {t("linkingSuccess.title")}

    {t("linkingSuccess.description")}
    -
    ) +
    ); } } diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index cbf0a1ae78..d11bd8c20a 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -12,11 +12,9 @@ function getIdentityProviders(orgId?: string) { }); } -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "idp" }); diff --git a/apps/login/src/app/(login)/invite/page.tsx b/apps/login/src/app/(login)/invite/page.tsx index c11131b50a..18c60eb993 100644 --- a/apps/login/src/app/(login)/invite/page.tsx +++ b/apps/login/src/app/(login)/invite/page.tsx @@ -9,11 +9,9 @@ import { } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "invite" }); diff --git a/apps/login/src/app/(login)/invite/success/page.tsx b/apps/login/src/app/(login)/invite/success/page.tsx index 5955a8ecc3..96c4984159 100644 --- a/apps/login/src/app/(login)/invite/success/page.tsx +++ b/apps/login/src/app/(login)/invite/success/page.tsx @@ -7,11 +7,9 @@ import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { getLocale, getTranslations } from "next-intl/server"; import Link from "next/link"; -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "invite" }); diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 89b58f516e..55a6682ce1 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -19,11 +19,9 @@ function getIdentityProviders(orgId?: string) { }); } -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "loginname" }); diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index 96c708ba63..071806db04 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -12,11 +12,9 @@ import { } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "mfa" }); diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index 4c1a7b7207..dce54618dc 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -32,11 +32,9 @@ function isSessionValid(session: Partial): { return { valid, verifiedAt }; } -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "mfa" }); diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index fbeca2fa37..9c91b3edd2 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -6,12 +6,10 @@ import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page( - props: { - searchParams: Promise>; - params: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; + params: Promise>; +}) { const params = await props.params; const searchParams = await props.searchParams; const locale = getLocale(); diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index 2a8b0ea790..e64b4debe0 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -17,12 +17,10 @@ import { getLocale, getTranslations } from "next-intl/server"; import Link from "next/link"; import { redirect } from "next/navigation"; -export default async function Page( - props: { - searchParams: Promise>; - params: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; + params: Promise>; +}) { const params = await props.params; const searchParams = await props.searchParams; const locale = getLocale(); diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index d6791676f7..0804f3ce2e 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -11,11 +11,9 @@ import { } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "passkey" }); diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx index 4e4e4f24f0..26a2de2428 100644 --- a/apps/login/src/app/(login)/passkey/set/page.tsx +++ b/apps/login/src/app/(login)/passkey/set/page.tsx @@ -6,11 +6,9 @@ import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "passkey" }); diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx index a697547f8f..1ba48a589d 100644 --- a/apps/login/src/app/(login)/password/change/page.tsx +++ b/apps/login/src/app/(login)/password/change/page.tsx @@ -10,11 +10,9 @@ import { } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "password" }); diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index da7b91fbd7..a2c10c3238 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -12,11 +12,9 @@ import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "password" }); diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index c44f236c38..d60ac7023c 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -13,11 +13,9 @@ import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "password" }); diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 497c1ac6e4..113e33be74 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -10,11 +10,9 @@ import { import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "register" }); diff --git a/apps/login/src/app/(login)/register/password/page.tsx b/apps/login/src/app/(login)/register/password/page.tsx index c1d1a895ee..48e454c312 100644 --- a/apps/login/src/app/(login)/register/password/page.tsx +++ b/apps/login/src/app/(login)/register/password/page.tsx @@ -10,11 +10,9 @@ import { import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "register" }); diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index 4dce6fdeaa..e4dd2bd8d2 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -7,11 +7,9 @@ import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "u2f" }); diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index 862b7024d2..f9f3daeaba 100644 --- a/apps/login/src/app/(login)/u2f/set/page.tsx +++ b/apps/login/src/app/(login)/u2f/set/page.tsx @@ -6,11 +6,9 @@ import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -export default async function Page( - props: { - searchParams: Promise>; - } -) { +export default async function Page(props: { + searchParams: Promise>; +}) { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "u2f" }); diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index c8e586f409..fc11f45921 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -61,7 +61,7 @@ export default async function Page(props: { searchParams: Promise }) { } return ( - ( +

    {t("verify.title")}

    {t("verify.description")}

    @@ -93,14 +93,14 @@ export default async function Page(props: { searchParams: Promise }) { /> ) : ( // check if auth methods are set - () + /> )}
    -
    ) +
    ); } diff --git a/apps/login/src/lib/cookies.ts b/apps/login/src/lib/cookies.ts index ebf667b51a..cd7457e8b1 100644 --- a/apps/login/src/lib/cookies.ts +++ b/apps/login/src/lib/cookies.ts @@ -21,7 +21,7 @@ export type Cookie = { type SessionCookie = Cookie & T; function setSessionHttpOnlyCookie(sessions: SessionCookie[]) { - const cookiesList = (cookies() as unknown as UnsafeUnwrappedCookies); + const cookiesList = cookies() as unknown as UnsafeUnwrappedCookies; return cookiesList.set({ name: "sessions", From ee993afbc9bde010b1f1d9f54e9d5a59244aafcd Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 22 Nov 2024 11:32:14 +0100 Subject: [PATCH 466/640] babel --- apps/login/.eslintrc.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/.eslintrc.cjs b/apps/login/.eslintrc.cjs index f5383dd47a..09e34c8066 100755 --- a/apps/login/.eslintrc.cjs +++ b/apps/login/.eslintrc.cjs @@ -1,5 +1,5 @@ module.exports = { - extends: ["next/core-web-vitals"], + extends: ["next/babel", "next/core-web-vitals"], ignorePatterns: ["external/**/*.ts"], rules: { "@next/next/no-html-link-for-pages": "off", From 2cb726776e3d5c48acb727501fb96e4e2879fe34 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 22 Nov 2024 11:35:02 +0100 Subject: [PATCH 467/640] canary version --- apps/login/next-env.d.ts | 2 +- apps/login/package.json | 2 +- pnpm-lock.yaml | 106 +++++++++++++++++++-------------------- 3 files changed, 55 insertions(+), 55 deletions(-) diff --git a/apps/login/next-env.d.ts b/apps/login/next-env.d.ts index 40c3d68096..1b3be0840f 100755 --- a/apps/login/next-env.d.ts +++ b/apps/login/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/login/package.json b/apps/login/package.json index e621cb567a..e04e430609 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -45,7 +45,7 @@ "copy-to-clipboard": "^3.3.3", "deepmerge": "^4.3.1", "moment": "^2.29.4", - "next": "15.0.3", + "next": "15.0.4-canary.23", "next-intl": "^3.20.0", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 320e21c122..e586abc576 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,7 +73,7 @@ importers: version: 0.5.7(tailwindcss@3.4.14) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106) + version: 1.3.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -96,14 +96,14 @@ importers: specifier: ^2.29.4 version: 2.30.1 next: - specifier: 15.0.3 - version: 15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) + specifier: 15.0.4-canary.23 + version: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) next-intl: specifier: ^3.20.0 - version: 3.20.0(next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106) + version: 3.20.0(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + version: 0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) nice-grpc: specifier: 2.0.1 version: 2.0.1 @@ -1113,56 +1113,56 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true - '@next/env@15.0.3': - resolution: {integrity: sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==} + '@next/env@15.0.4-canary.23': + resolution: {integrity: sha512-NfBMRPa10yaEzQ693kGEsgHL58Y27jSbGCDbyXy14dx3z6UeQZQfEVRAwJ4iG1V6gND9+CzzugtiXvJZfSlC9A==} '@next/eslint-plugin-next@14.2.7': resolution: {integrity: sha512-+7xh142AdhZGjY9/L0iFo7mqRBMJHe+q+uOL+hto1Lfo9DeWCGcR6no4StlFbVSVcA6fQLKEX6y6qhMsSKbgNQ==} - '@next/swc-darwin-arm64@15.0.3': - resolution: {integrity: sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==} + '@next/swc-darwin-arm64@15.0.4-canary.23': + resolution: {integrity: sha512-sX3MaDUiFiMT14KSx5mJz6B+IH9k7+buNniNrDP7iz4YG28jssm9e8uHbiWXsbn9jnkQUJu8PHoUOLhgjZgtsQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.0.3': - resolution: {integrity: sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==} + '@next/swc-darwin-x64@15.0.4-canary.23': + resolution: {integrity: sha512-KJRSDVvEPuvjRKe9IY3YMAv9KMOmB/U5+7g0c3OTT/50x1KL0XOlgnc+Af2GdZKIrkKiAdTFG54AHaSD584yHg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.0.3': - resolution: {integrity: sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==} + '@next/swc-linux-arm64-gnu@15.0.4-canary.23': + resolution: {integrity: sha512-0EqeqGdlG0MPDYGE/cPtTvBLtBiWDAd7fSRgRhIga6CkuaRVFKuTeRrsjTa0v+51C2OawjQp2N3ww1zBLuBhcg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.0.3': - resolution: {integrity: sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==} + '@next/swc-linux-arm64-musl@15.0.4-canary.23': + resolution: {integrity: sha512-O06Gw8HU0z9f1b4TiGb0u1o87hgLa0yEW1odyLPE1d3+JKwhkh4L1Ug9uLpeqEUnxCoIrwVomEUyQBPGNQtq0Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.0.3': - resolution: {integrity: sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==} + '@next/swc-linux-x64-gnu@15.0.4-canary.23': + resolution: {integrity: sha512-BvERc3hri6eyUHnasZgwcRCdR8WpfCdKKe/M12Q+ZAkTeJeVkLXNakznaZbBWdlCc77F/NeHz/OWoQWUTpKm3g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.0.3': - resolution: {integrity: sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==} + '@next/swc-linux-x64-musl@15.0.4-canary.23': + resolution: {integrity: sha512-FF5LNTdra/tHxdHjRR3lb+UxFgRVT+v3EMruueQg6BpOqpciodyCkkYQFrx2DitpADojQ6bBBFBDs6KIb8jB5w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.0.3': - resolution: {integrity: sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==} + '@next/swc-win32-arm64-msvc@15.0.4-canary.23': + resolution: {integrity: sha512-XnHD7fqQYZR1XCCuAf8+yAdkMpzAFz2pWmny2K6g5C7BalrwNuxWLsM5LycW1PTMzSqkzLJeXCG6AZu099u7/w==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.0.3': - resolution: {integrity: sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==} + '@next/swc-win32-x64-msvc@15.0.4-canary.23': + resolution: {integrity: sha512-HGoW8LjYxbUhkND+vJ/21dWQ7sdv4SIUQDv2r/FpcdHFMzb5M/jgQVqcMFkqg2ibH65ZAcVBM0ICcUnTLlX7PQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -3378,16 +3378,16 @@ packages: react: '*' react-dom: '*' - next@15.0.3: - resolution: {integrity: sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==} + next@15.0.4-canary.23: + resolution: {integrity: sha512-xCjjBx4csWdG4MP9tKV/C25OIDbN0o+zovMC5zd4yvE4lrd43Y5tt+w171IGUueb6VbPLTSlDaXvqOtrxKJXzQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 '@playwright/test': ^1.41.2 babel-plugin-react-compiler: '*' - react: ^18.2.0 || 19.0.0-rc-66855b96-20241106 - react-dom: ^18.2.0 || 19.0.0-rc-66855b96-20241106 + react: ^18.2.0 || 19.0.0-rc-380f5d67-20241113 + react-dom: ^18.2.0 || 19.0.0-rc-380f5d67-20241113 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': @@ -5545,34 +5545,34 @@ snapshots: - encoding - supports-color - '@next/env@15.0.3': {} + '@next/env@15.0.4-canary.23': {} '@next/eslint-plugin-next@14.2.7': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@15.0.3': + '@next/swc-darwin-arm64@15.0.4-canary.23': optional: true - '@next/swc-darwin-x64@15.0.3': + '@next/swc-darwin-x64@15.0.4-canary.23': optional: true - '@next/swc-linux-arm64-gnu@15.0.3': + '@next/swc-linux-arm64-gnu@15.0.4-canary.23': optional: true - '@next/swc-linux-arm64-musl@15.0.3': + '@next/swc-linux-arm64-musl@15.0.4-canary.23': optional: true - '@next/swc-linux-x64-gnu@15.0.3': + '@next/swc-linux-x64-gnu@15.0.4-canary.23': optional: true - '@next/swc-linux-x64-musl@15.0.3': + '@next/swc-linux-x64-musl@15.0.4-canary.23': optional: true - '@next/swc-win32-arm64-msvc@15.0.3': + '@next/swc-win32-arm64-msvc@15.0.4-canary.23': optional: true - '@next/swc-win32-x64-msvc@15.0.3': + '@next/swc-win32-x64-msvc@15.0.4-canary.23': optional: true '@nodelib/fs.scandir@2.1.5': @@ -5713,7 +5713,7 @@ snapshots: '@react-stately/utils@3.10.4(react@19.0.0-rc-66855b96-20241106)': dependencies: - '@swc/helpers': 0.5.5 + '@swc/helpers': 0.5.13 react: 19.0.0-rc-66855b96-20241106 '@react-types/shared@3.25.0(react@19.0.0-rc-66855b96-20241106)': @@ -5943,11 +5943,11 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/analytics@1.3.1(next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106)': + '@vercel/analytics@1.3.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) + next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) react: 19.0.0-rc-66855b96-20241106 '@vercel/git-hooks@1.0.0': {} @@ -8007,23 +8007,23 @@ snapshots: negotiator@0.6.3: {} - next-intl@3.20.0(next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106): + next-intl@3.20.0(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next: 15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) + next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) react: 19.0.0-rc-66855b96-20241106 use-intl: 3.20.0(react@19.0.0-rc-66855b96-20241106) - next-themes@0.2.1(next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106): + next-themes@0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106): dependencies: - next: 15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) + next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) react: 19.0.0-rc-66855b96-20241106 react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) - next@15.0.3(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7): + next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7): dependencies: - '@next/env': 15.0.3 + '@next/env': 15.0.4-canary.23 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.13 busboy: 1.6.0 @@ -8033,14 +8033,14 @@ snapshots: react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.0.0-rc-66855b96-20241106) optionalDependencies: - '@next/swc-darwin-arm64': 15.0.3 - '@next/swc-darwin-x64': 15.0.3 - '@next/swc-linux-arm64-gnu': 15.0.3 - '@next/swc-linux-arm64-musl': 15.0.3 - '@next/swc-linux-x64-gnu': 15.0.3 - '@next/swc-linux-x64-musl': 15.0.3 - '@next/swc-win32-arm64-msvc': 15.0.3 - '@next/swc-win32-x64-msvc': 15.0.3 + '@next/swc-darwin-arm64': 15.0.4-canary.23 + '@next/swc-darwin-x64': 15.0.4-canary.23 + '@next/swc-linux-arm64-gnu': 15.0.4-canary.23 + '@next/swc-linux-arm64-musl': 15.0.4-canary.23 + '@next/swc-linux-x64-gnu': 15.0.4-canary.23 + '@next/swc-linux-x64-musl': 15.0.4-canary.23 + '@next/swc-win32-arm64-msvc': 15.0.4-canary.23 + '@next/swc-win32-x64-msvc': 15.0.4-canary.23 '@playwright/test': 1.48.2 sass: 1.80.7 sharp: 0.33.5 From 58dbcdc3aa3835102bc35f85d3b7f30a68e785fb Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 22 Nov 2024 11:40:21 +0100 Subject: [PATCH 468/640] rm cache life in layout --- apps/login/src/app/(login)/layout.tsx | 2 -- apps/login/src/lib/zitadel.ts | 4 ---- apps/login/tailwind.config.mjs | 2 +- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/login/src/app/(login)/layout.tsx b/apps/login/src/app/(login)/layout.tsx index 75820d7990..9105c45eb9 100644 --- a/apps/login/src/app/(login)/layout.tsx +++ b/apps/login/src/app/(login)/layout.tsx @@ -14,8 +14,6 @@ const lato = Lato({ subsets: ["latin"], }); -export const revalidate = 60; // revalidate every minute - export default async function RootLayout({ children, }: { diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index ad0b48bc4a..7c5d63c4e5 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -41,10 +41,6 @@ import { unstable_cacheLife as cacheLife } from "next/cache"; import { PROVIDER_MAPPING } from "./idp"; const SESSION_LIFETIME_S = 3600; // TODO load from oidc settings -const CACHE_REVALIDATION_INTERVAL_IN_SECONDS = process.env - .CACHE_REVALIDATION_INTERVAL_IN_SECONDS - ? Number(process.env.CACHE_REVALIDATION_INTERVAL_IN_SECONDS) - : 3600; const transport = createServerTransport( process.env.ZITADEL_SERVICE_USER_TOKEN!, diff --git a/apps/login/tailwind.config.mjs b/apps/login/tailwind.config.mjs index 705398ceb6..0241b0c449 100644 --- a/apps/login/tailwind.config.mjs +++ b/apps/login/tailwind.config.mjs @@ -35,7 +35,7 @@ types.forEach((type) => { }); /** @type {import('tailwindcss').Config} */ -module.exports = { +export default { presets: [sharedConfig], darkMode: "class", content: ["./src/**/*.{js,ts,jsx,tsx}"], From 7796f590fd03387e9b0e456326ecc3131a1c8c3f Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 22 Nov 2024 15:34:31 +0100 Subject: [PATCH 469/640] add suspense --- apps/login/package.json | 2 +- apps/login/src/app/(login)/layout.tsx | 40 +++---- .../src/components/language-provider.tsx | 18 +++ apps/login/src/lib/zitadel.ts | 8 ++ pnpm-lock.yaml | 104 ++++++++++-------- 5 files changed, 102 insertions(+), 70 deletions(-) create mode 100644 apps/login/src/components/language-provider.tsx diff --git a/apps/login/package.json b/apps/login/package.json index e04e430609..3dc0946609 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -46,7 +46,7 @@ "deepmerge": "^4.3.1", "moment": "^2.29.4", "next": "15.0.4-canary.23", - "next-intl": "^3.20.0", + "next-intl": "^3.25.1", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", "qrcode.react": "^3.1.0", diff --git a/apps/login/src/app/(login)/layout.tsx b/apps/login/src/app/(login)/layout.tsx index 9105c45eb9..1975b7c5e5 100644 --- a/apps/login/src/app/(login)/layout.tsx +++ b/apps/login/src/app/(login)/layout.tsx @@ -1,13 +1,12 @@ import "@/styles/globals.scss"; +import { LanguageProvider } from "@/components/language-provider"; import { LanguageSwitcher } from "@/components/language-switcher"; import { Theme } from "@/components/theme"; import { ThemeProvider } from "@/components/theme-provider"; import { Analytics } from "@vercel/analytics/react"; -import { NextIntlClientProvider } from "next-intl"; -import { getLocale, getMessages } from "next-intl/server"; import { Lato } from "next/font/google"; -import { ReactNode } from "react"; +import { ReactNode, Suspense } from "react"; const lato = Lato({ weight: ["400", "700", "900"], @@ -19,33 +18,28 @@ export default async function RootLayout({ }: { children: ReactNode; }) { - const locale = await getLocale(); - const messages = await getMessages(); - return ( - + - -
    -
    - {children} -
    - - + Loading...
    }> + +
    +
    + {children} +
    + + +
    -
    - - + + + diff --git a/apps/login/src/components/language-provider.tsx b/apps/login/src/components/language-provider.tsx new file mode 100644 index 0000000000..438eee9293 --- /dev/null +++ b/apps/login/src/components/language-provider.tsx @@ -0,0 +1,18 @@ +"use cache"; + +import { NextIntlClientProvider } from "next-intl"; +import { getLocale, getMessages } from "next-intl/server"; +import { unstable_cacheLife as cacheLife } from "next/cache"; +import { ReactNode } from "react"; + +export async function LanguageProvider({ children }: { children: ReactNode }) { + cacheLife("hours"); + + const locale = await getLocale(); + const messages = await getMessages(); + return ( + + {children} + + ); +} diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 7c5d63c4e5..0092f4ad44 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -66,6 +66,8 @@ export async function getBrandingSettings(organization?: string) { export async function getLoginSettings(orgId?: string) { "use cache"; + cacheLife("hours"); + return await settingsService .getLoginSettings({ ctx: makeReqCtx(orgId) }, {}) .then((resp) => (resp.settings ? resp.settings : undefined)); @@ -99,6 +101,8 @@ export async function registerTOTP(userId: string) { export async function getGeneralSettings() { "use cache"; + cacheLife("hours"); + return settingsService .getGeneralSettings({}, {}) .then((resp) => resp.supportedLanguages); @@ -106,6 +110,8 @@ export async function getGeneralSettings() { export async function getLegalAndSupportSettings(organization?: string) { "use cache"; + cacheLife("hours"); + return settingsService .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) .then((resp) => (resp.settings ? resp.settings : undefined)); @@ -113,6 +119,8 @@ export async function getLegalAndSupportSettings(organization?: string) { export async function getPasswordComplexitySettings(organization?: string) { "use cache"; + cacheLife("hours"); + return settingsService .getPasswordComplexitySettings({ ctx: makeReqCtx(organization) }) .then((resp) => (resp.settings ? resp.settings : undefined)); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e586abc576..9a9d40dbf9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,8 +99,8 @@ importers: specifier: 15.0.4-canary.23 version: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) next-intl: - specifier: ^3.20.0 - version: 3.20.0(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106) + specifier: ^3.25.1 + version: 3.25.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106) next-themes: specifier: ^0.2.1 version: 0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) @@ -918,21 +918,24 @@ packages: '@floating-ui/utils@0.2.8': resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} - '@formatjs/ecma402-abstract@2.1.0': - resolution: {integrity: sha512-SE2V2PE03K9U/YQZ3nxEOysRkQ/CfSwLHR789Uk9N0PTiWT6I+17UTDI97zYEwC1mbnjefqmtjbL8nunjPwGjw==} + '@formatjs/ecma402-abstract@2.2.4': + resolution: {integrity: sha512-lFyiQDVvSbQOpU+WFd//ILolGj4UgA/qXrKeZxdV14uKiAUiPAtX6XAn7WBCRi7Mx6I7EybM9E5yYn4BIpZWYg==} - '@formatjs/fast-memoize@2.2.0': - resolution: {integrity: sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==} + '@formatjs/fast-memoize@2.2.3': + resolution: {integrity: sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==} - '@formatjs/icu-messageformat-parser@2.7.9': - resolution: {integrity: sha512-9Z5buDRMsTbplXknvRlDmnpWhZrayNVcVvkH0+SSz8Ll4XD/7Tcn8m1IjxM3iBJSwQbxwxb7/g0Fkx3d4j2osw==} + '@formatjs/icu-messageformat-parser@2.9.4': + resolution: {integrity: sha512-Tbvp5a9IWuxUcpWNIW6GlMQYEc4rwNHR259uUFoKWNN1jM9obf9Ul0e+7r7MvFOBNcN+13K7NuKCKqQiAn1QEg==} - '@formatjs/icu-skeleton-parser@1.8.3': - resolution: {integrity: sha512-TsKAP013ayZFbWWR2KWy+f9QVZh0yDFTPK3yE4OqU2gnzafvmKTodRtJLVpfZmpXWJ5y7BWD1AsyT14mcbLzig==} + '@formatjs/icu-skeleton-parser@1.8.8': + resolution: {integrity: sha512-vHwK3piXwamFcx5YQdCdJxUQ1WdTl6ANclt5xba5zLGDv5Bsur7qz8AD7BevaKxITwpgDeU0u8My3AIibW9ywA==} '@formatjs/intl-localematcher@0.5.4': resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==} + '@formatjs/intl-localematcher@0.5.8': + resolution: {integrity: sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==} + '@grpc/grpc-js@1.11.1': resolution: {integrity: sha512-gyt/WayZrVPH2w/UTLansS7F9Nwld472JxxaETamrM8HNlsa+jSLNyKAZmhxI2Me4c3mQHFiS1wWHDY1g1Kthw==} engines: {node: '>=12.10.0'} @@ -2865,8 +2868,8 @@ packages: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} - intl-messageformat@10.6.0: - resolution: {integrity: sha512-AYKl/DY1nl75pJU8EK681JOVL40uQTNJe3yEMXKfydDFoz+5hNrM/PqjchueSMKGKCZKBVgeexqZwy3uC2B36Q==} + intl-messageformat@10.7.7: + resolution: {integrity: sha512-F134jIoeYMro/3I0h08D0Yt4N9o9pjddU/4IIxMMURqbAtI2wu70X8hvG1V48W49zXHXv3RKSF/po+0fDfsGjA==} is-arguments@1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} @@ -3361,15 +3364,15 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} - next-intl@3.20.0: - resolution: {integrity: sha512-0bCZcc38HfAZk/T+PNNcnJZknC+caS5rBK+WYRd1HsOL5O6puEu2H3kya8oT9s8piHjrTf7P0UHeahOFleOnrw==} + next-intl@3.25.1: + resolution: {integrity: sha512-Z2dJWn5f/b1sb8EmuJcuDhbQTIp4RG1KBFAILgRt/y27W0ifU7Ll/os3liphUY4InyRH89uShTAk7ItAlpr0uA==} peerDependencies: - next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 next-themes@0.2.1: resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} @@ -4393,6 +4396,9 @@ packages: tslib@2.7.0: resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsup@8.3.5: resolution: {integrity: sha512-Tunf6r6m6tnZsG9GYWndg0z8dEV7fD733VBFzFJ5Vcm1FtlXB8xBD/rtrBi2a3YKEV7hHtxiZtW5EAVADoe1pA==} engines: {node: '>=18'} @@ -4531,10 +4537,10 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - use-intl@3.20.0: - resolution: {integrity: sha512-5WQs6yZVWI9K7vw3134P0bhKNp4mi8NbmqKOCuhD9nQUMTKdmpBXwjk62+axwvEbj4XrZxj4X93mQMLXU5ZsCg==} + use-intl@3.25.1: + resolution: {integrity: sha512-Xeyl0+BjlBf6fJr2h5W/CESZ2IQAH7jzXYK4c/ao+qR26jNPW3FXBLjg7eLRxdeI6QaLcYGLtH3WYhC9I0+6Yg==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 use-sync-external-store@1.2.2: resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} @@ -5343,31 +5349,35 @@ snapshots: '@floating-ui/utils@0.2.8': {} - '@formatjs/ecma402-abstract@2.1.0': + '@formatjs/ecma402-abstract@2.2.4': dependencies: - '@formatjs/fast-memoize': 2.2.0 - '@formatjs/intl-localematcher': 0.5.4 - tslib: 2.7.0 + '@formatjs/fast-memoize': 2.2.3 + '@formatjs/intl-localematcher': 0.5.8 + tslib: 2.8.1 - '@formatjs/fast-memoize@2.2.0': + '@formatjs/fast-memoize@2.2.3': dependencies: - tslib: 2.7.0 + tslib: 2.8.1 - '@formatjs/icu-messageformat-parser@2.7.9': + '@formatjs/icu-messageformat-parser@2.9.4': dependencies: - '@formatjs/ecma402-abstract': 2.1.0 - '@formatjs/icu-skeleton-parser': 1.8.3 - tslib: 2.7.0 + '@formatjs/ecma402-abstract': 2.2.4 + '@formatjs/icu-skeleton-parser': 1.8.8 + tslib: 2.8.1 - '@formatjs/icu-skeleton-parser@1.8.3': + '@formatjs/icu-skeleton-parser@1.8.8': dependencies: - '@formatjs/ecma402-abstract': 2.1.0 - tslib: 2.7.0 + '@formatjs/ecma402-abstract': 2.2.4 + tslib: 2.8.1 '@formatjs/intl-localematcher@0.5.4': dependencies: tslib: 2.7.0 + '@formatjs/intl-localematcher@0.5.8': + dependencies: + tslib: 2.8.1 + '@grpc/grpc-js@1.11.1': dependencies: '@grpc/proto-loader': 0.7.13 @@ -7524,12 +7534,12 @@ snapshots: hasown: 2.0.2 side-channel: 1.0.6 - intl-messageformat@10.6.0: + intl-messageformat@10.7.7: dependencies: - '@formatjs/ecma402-abstract': 2.1.0 - '@formatjs/fast-memoize': 2.2.0 - '@formatjs/icu-messageformat-parser': 2.7.9 - tslib: 2.7.0 + '@formatjs/ecma402-abstract': 2.2.4 + '@formatjs/fast-memoize': 2.2.3 + '@formatjs/icu-messageformat-parser': 2.9.4 + tslib: 2.8.1 is-arguments@1.1.1: dependencies: @@ -8005,15 +8015,15 @@ snapshots: natural-compare@1.4.0: {} - negotiator@0.6.3: {} + negotiator@1.0.0: {} - next-intl@3.20.0(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106): + next-intl@3.25.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106): dependencies: '@formatjs/intl-localematcher': 0.5.4 - negotiator: 0.6.3 + negotiator: 1.0.0 next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) react: 19.0.0-rc-66855b96-20241106 - use-intl: 3.20.0(react@19.0.0-rc-66855b96-20241106) + use-intl: 3.25.1(react@19.0.0-rc-66855b96-20241106) next-themes@0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106): dependencies: @@ -9020,6 +9030,8 @@ snapshots: tslib@2.7.0: {} + tslib@2.8.1: {} + tsup@8.3.5(jiti@1.21.6)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.5.0): dependencies: bundle-require: 5.0.0(esbuild@0.24.0) @@ -9165,10 +9177,10 @@ snapshots: dependencies: punycode: 2.3.1 - use-intl@3.20.0(react@19.0.0-rc-66855b96-20241106): + use-intl@3.25.1(react@19.0.0-rc-66855b96-20241106): dependencies: - '@formatjs/fast-memoize': 2.2.0 - intl-messageformat: 10.6.0 + '@formatjs/fast-memoize': 2.2.3 + intl-messageformat: 10.7.7 react: 19.0.0-rc-66855b96-20241106 use-sync-external-store@1.2.2(react@19.0.0-rc-66855b96-20241106): From e964c7c0620ce51b310f7b8449e06a20d6cc633b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 25 Nov 2024 09:19:58 +0100 Subject: [PATCH 470/640] rm caching of i18n --- apps/login/src/components/language-provider.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/login/src/components/language-provider.tsx b/apps/login/src/components/language-provider.tsx index 438eee9293..524704b600 100644 --- a/apps/login/src/components/language-provider.tsx +++ b/apps/login/src/components/language-provider.tsx @@ -1,13 +1,8 @@ -"use cache"; - import { NextIntlClientProvider } from "next-intl"; import { getLocale, getMessages } from "next-intl/server"; -import { unstable_cacheLife as cacheLife } from "next/cache"; import { ReactNode } from "react"; export async function LanguageProvider({ children }: { children: ReactNode }) { - cacheLife("hours"); - const locale = await getLocale(); const messages = await getMessages(); return ( From b48c25f7cdd986b0c05557dc6503e94007d918fd Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 25 Nov 2024 09:35:36 +0100 Subject: [PATCH 471/640] lint --- apps/login/.eslintrc.cjs | 2 +- packages/eslint-config-zitadel/index.js | 7 + packages/eslint-config-zitadel/package.json | 5 +- pnpm-lock.yaml | 219 +++++++++++++++++++- 4 files changed, 221 insertions(+), 12 deletions(-) diff --git a/apps/login/.eslintrc.cjs b/apps/login/.eslintrc.cjs index 09e34c8066..f5383dd47a 100755 --- a/apps/login/.eslintrc.cjs +++ b/apps/login/.eslintrc.cjs @@ -1,5 +1,5 @@ module.exports = { - extends: ["next/babel", "next/core-web-vitals"], + extends: ["next/core-web-vitals"], ignorePatterns: ["external/**/*.ts"], rules: { "@next/next/no-html-link-for-pages": "off", diff --git a/packages/eslint-config-zitadel/index.js b/packages/eslint-config-zitadel/index.js index 6024e8d635..6a53b2a5e6 100644 --- a/packages/eslint-config-zitadel/index.js +++ b/packages/eslint-config-zitadel/index.js @@ -1,6 +1,13 @@ module.exports = { + parser: "@babel/eslint-parser", extends: ["next", "turbo", "prettier"], rules: { "@next/next/no-html-link-for-pages": "off", }, + parserOptions: { + requireConfigFile: false, + babelOptions: { + presets: ["next/babel"], + }, + }, }; diff --git a/packages/eslint-config-zitadel/package.json b/packages/eslint-config-zitadel/package.json index 261744ce00..b733284b2b 100644 --- a/packages/eslint-config-zitadel/package.json +++ b/packages/eslint-config-zitadel/package.json @@ -7,10 +7,11 @@ "access": "public" }, "dependencies": { - "eslint-config-next": "^14.2.3", "@typescript-eslint/parser": "^7.9.0", + "eslint-config-next": "^14.2.18", "eslint-config-prettier": "^9.1.0", + "eslint-config-turbo": "^2.0.9", "eslint-plugin-react": "^7.34.1", - "eslint-config-turbo": "^2.0.9" + "@babel/eslint-parser": "^7.25.9" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a9d40dbf9..c1f59c0c03 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -222,12 +222,15 @@ importers: packages/eslint-config-zitadel: dependencies: + '@babel/eslint-parser': + specifier: ^7.25.9 + version: 7.25.9(@babel/core@7.26.0)(eslint@8.57.1) '@typescript-eslint/parser': specifier: ^7.9.0 version: 7.18.0(eslint@8.57.1)(typescript@5.6.3) eslint-config-next: - specifier: ^14.2.3 - version: 14.2.7(eslint@8.57.1)(typescript@5.6.3) + specifier: ^14.2.18 + version: 14.2.18(eslint@8.57.1)(typescript@5.6.3) eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@8.57.1) @@ -336,6 +339,13 @@ packages: resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} engines: {node: '>=6.9.0'} + '@babel/eslint-parser@7.25.9': + resolution: {integrity: sha512-5UXfgpK0j0Xr/xIdgdLEhOFxaDZ0bRPWJJchRpqOSur/3rZoPbqqki5mm0p4NE2cs28krBEiSM2MB7//afRSQQ==} + engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + peerDependencies: + '@babel/core': ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 + '@babel/generator@7.26.2': resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} engines: {node: '>=6.9.0'} @@ -885,10 +895,20 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.11.1': resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/eslintrc@2.1.4': resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1119,8 +1139,8 @@ packages: '@next/env@15.0.4-canary.23': resolution: {integrity: sha512-NfBMRPa10yaEzQ693kGEsgHL58Y27jSbGCDbyXy14dx3z6UeQZQfEVRAwJ4iG1V6gND9+CzzugtiXvJZfSlC9A==} - '@next/eslint-plugin-next@14.2.7': - resolution: {integrity: sha512-+7xh142AdhZGjY9/L0iFo7mqRBMJHe+q+uOL+hto1Lfo9DeWCGcR6no4StlFbVSVcA6fQLKEX6y6qhMsSKbgNQ==} + '@next/eslint-plugin-next@14.2.18': + resolution: {integrity: sha512-KyYTbZ3GQwWOjX3Vi1YcQbekyGP0gdammb7pbmmi25HBUCINzDReyrzCMOJIeZisK1Q3U6DT5Rlc4nm2/pQeXA==} '@next/swc-darwin-arm64@15.0.4-canary.23': resolution: {integrity: sha512-sX3MaDUiFiMT14KSx5mJz6B+IH9k7+buNniNrDP7iz4YG28jssm9e8uHbiWXsbn9jnkQUJu8PHoUOLhgjZgtsQ==} @@ -1170,6 +1190,9 @@ packages: cpu: [x64] os: [win32] + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1546,6 +1569,17 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@typescript-eslint/eslint-plugin@8.15.0': + resolution: {integrity: sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^7.9.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/parser@7.18.0': resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} engines: {node: ^18.18.0 || >=20.0.0} @@ -1560,10 +1594,28 @@ packages: resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@8.15.0': + resolution: {integrity: sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.15.0': + resolution: {integrity: sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/types@7.18.0': resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/types@8.15.0': + resolution: {integrity: sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@7.18.0': resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} engines: {node: ^18.18.0 || >=20.0.0} @@ -1573,10 +1625,33 @@ packages: typescript: optional: true + '@typescript-eslint/typescript-estree@8.15.0': + resolution: {integrity: sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@8.15.0': + resolution: {integrity: sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/visitor-keys@7.18.0': resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/visitor-keys@8.15.0': + resolution: {integrity: sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -2340,8 +2415,8 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-next@14.2.7: - resolution: {integrity: sha512-ppmy+QdQ7qkuCHGDlPjWaoSbJvjGpWSBD4zEW8f1eWlxYXYpZK7QzBOer1EcHKT3uKhlY1JjUus9g7Kvv712rw==} + eslint-config-next@14.2.18: + resolution: {integrity: sha512-SuDRcpJY5VHBkhz5DijJ4iA4bVnBA0n48Rb+YSJSCDr+h7kKAcb1mZHusLbW+WA8LDB6edSolomXA55eG3eOVA==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 typescript: '>=3.3.1' @@ -2430,14 +2505,26 @@ packages: peerDependencies: eslint: '>6.6.0' + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2461,6 +2548,10 @@ packages: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -4364,6 +4455,12 @@ packages: peerDependencies: typescript: '>=4.2.0' + ts-api-utils@1.4.1: + resolution: {integrity: sha512-5RU2/lxTA3YUZxju61HO2U6EoZLvBLtmV2mbTvqyu4a/7s7RmJPT+1YekhMVsQhznRWk/czIwDUg+V8Q9ZuG4w==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + ts-error@1.0.6: resolution: {integrity: sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==} @@ -4810,6 +4907,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/eslint-parser@7.25.9(@babel/core@7.26.0)(eslint@8.57.1)': + dependencies: + '@babel/core': 7.26.0 + '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 + eslint: 8.57.1 + eslint-visitor-keys: 2.1.0 + semver: 6.3.1 + '@babel/generator@7.26.2': dependencies: '@babel/parser': 7.26.2 @@ -5306,8 +5411,15 @@ snapshots: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.11.1': {} + '@eslint-community/regexpp@4.12.1': {} + '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 @@ -5557,7 +5669,7 @@ snapshots: '@next/env@15.0.4-canary.23': {} - '@next/eslint-plugin-next@14.2.7': + '@next/eslint-plugin-next@14.2.18': dependencies: glob: 10.3.10 @@ -5585,6 +5697,10 @@ snapshots: '@next/swc-win32-x64-msvc@15.0.4-canary.23': optional: true + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + dependencies: + eslint-scope: 5.1.1 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -5911,6 +6027,24 @@ snapshots: '@types/node': 22.9.0 optional: true + '@typescript-eslint/eslint-plugin@8.15.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.15.0 + '@typescript-eslint/type-utils': 8.15.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/utils': 8.15.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.15.0 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.4.1(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3)': dependencies: '@typescript-eslint/scope-manager': 7.18.0 @@ -5929,8 +6063,27 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/scope-manager@8.15.0': + dependencies: + '@typescript-eslint/types': 8.15.0 + '@typescript-eslint/visitor-keys': 8.15.0 + + '@typescript-eslint/type-utils@8.15.0(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3) + '@typescript-eslint/utils': 8.15.0(eslint@8.57.1)(typescript@5.6.3) + debug: 4.3.7(supports-color@5.5.0) + eslint: 8.57.1 + ts-api-utils: 1.4.1(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@7.18.0': {} + '@typescript-eslint/types@8.15.0': {} + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.6.3)': dependencies: '@typescript-eslint/types': 7.18.0 @@ -5946,11 +6099,43 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.15.0(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 8.15.0 + '@typescript-eslint/visitor-keys': 8.15.0 + debug: 4.3.7(supports-color@5.5.0) + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.4.1(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.15.0(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.15.0 + '@typescript-eslint/types': 8.15.0 + '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3) + eslint: 8.57.1 + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@7.18.0': dependencies: '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 + '@typescript-eslint/visitor-keys@8.15.0': + dependencies: + '@typescript-eslint/types': 8.15.0 + eslint-visitor-keys: 4.2.0 + '@ungap/structured-clone@1.2.0': {} '@vercel/analytics@1.3.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106)': @@ -6874,10 +7059,11 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@14.2.7(eslint@8.57.1)(typescript@5.6.3): + eslint-config-next@14.2.18(eslint@8.57.1)(typescript@5.6.3): dependencies: - '@next/eslint-plugin-next': 14.2.7 + '@next/eslint-plugin-next': 14.2.18 '@rushstack/eslint-patch': 1.10.4 + '@typescript-eslint/eslint-plugin': 8.15.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 @@ -7018,13 +7204,22 @@ snapshots: dotenv: 16.0.3 eslint: 8.57.1 + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + eslint-scope@7.2.2: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 + eslint-visitor-keys@2.1.0: {} + eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@4.2.0: {} + eslint@8.57.1: dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) @@ -7084,6 +7279,8 @@ snapshots: dependencies: estraverse: 5.3.0 + estraverse@4.3.0: {} + estraverse@5.3.0: {} estree-walker@3.0.3: @@ -8998,6 +9195,10 @@ snapshots: dependencies: typescript: 5.6.3 + ts-api-utils@1.4.1(typescript@5.6.3): + dependencies: + typescript: 5.6.3 + ts-error@1.0.6: {} ts-interface-checker@0.1.13: {} From 76e9fea4e45d51769ce416c70e7259edbf96d910 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 25 Nov 2024 10:59:28 +0100 Subject: [PATCH 472/640] scss, module, nextjs, hydration fixes --- apps/login/next.config.mjs | 1 - apps/login/package.json | 6 ------ apps/login/src/components/input.tsx | 2 ++ apps/login/src/styles/globals.scss | 2 +- apps/login/tailwind.config.mjs | 2 +- package.json | 4 +++- .../zitadel-tailwind-config/tailwind.config.mjs | 14 +++++--------- pnpm-lock.yaml | 6 ++++-- 8 files changed, 16 insertions(+), 21 deletions(-) diff --git a/apps/login/next.config.mjs b/apps/login/next.config.mjs index e42b4ef7a1..a6e1d1a6d8 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -36,7 +36,6 @@ const secureHeaders = [ const nextConfig = { reactStrictMode: true, // Recommended for the `pages` directory, default in `app`. - swcMinify: true, experimental: { dynamicIO: true, }, diff --git a/apps/login/package.json b/apps/login/package.json index 3dc0946609..f8f3b89a3f 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -88,11 +88,5 @@ "ts-proto": "^2.2.7", "typescript": "^5.6.3", "zitadel-tailwind-config": "workspace:*" - }, - "pnpm": { - "overrides": { - "@types/react": "npm:types-react@19.0.0-rc.1", - "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1" - } } } diff --git a/apps/login/src/components/input.tsx b/apps/login/src/components/input.tsx index f1c88a2dee..0cb9e58467 100644 --- a/apps/login/src/components/input.tsx +++ b/apps/login/src/components/input.tsx @@ -1,4 +1,5 @@ "use client"; + import { CheckCircleIcon } from "@heroicons/react/24/solid"; import { clsx } from "clsx"; import { @@ -64,6 +65,7 @@ export const TextInput = forwardRef( {label} {required && "*"} ({ "dark-vc-border-gradient": `radial-gradient(at left top, ${theme( - "colors.gray.800" + "colors.gray.800", )}, 50px, ${theme("colors.gray.800")} 50%)`, "vc-border-gradient": `radial-gradient(at left top, ${theme( - "colors.gray.200" + "colors.gray.200", )}, 50px, ${theme("colors.gray.300")} 50%)`, }), keyframes: ({ theme }) => ({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c1f59c0c03..e35075ac47 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,8 @@ settings: overrides: '@typescript-eslint/parser': ^7.9.0 + '@types/react': npm:types-react@19.0.0-rc.1 + '@types/react-dom': npm:types-react-dom@19.0.0-rc.1 importers: @@ -1508,8 +1510,8 @@ packages: engines: {node: '>=18'} peerDependencies: '@testing-library/dom': ^10.0.0 - '@types/react': ^18.0.0 - '@types/react-dom': ^18.0.0 + '@types/react': npm:types-react@19.0.0-rc.1 + '@types/react-dom': npm:types-react-dom@19.0.0-rc.1 react: ^18.0.0 react-dom: ^18.0.0 peerDependenciesMeta: From 74eae3aed3eb532491d16c6c07003e814ce22e27 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 25 Nov 2024 13:59:30 +0100 Subject: [PATCH 473/640] only cache in prod --- apps/login/src/lib/zitadel.ts | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 0092f4ad44..9b8d37979c 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -56,8 +56,10 @@ export const orgService = createOrganizationServiceClient(transport); export const settingsService = createSettingsServiceClient(transport); export async function getBrandingSettings(organization?: string) { - "use cache"; - cacheLife("hours"); + if (process.env.DEBUG !== "true") { + ("use cache"); + cacheLife("hours"); + } return settingsService .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) @@ -65,8 +67,10 @@ export async function getBrandingSettings(organization?: string) { } export async function getLoginSettings(orgId?: string) { - "use cache"; - cacheLife("hours"); + if (process.env.DEBUG !== "true") { + ("use cache"); + cacheLife("hours"); + } return await settingsService .getLoginSettings({ ctx: makeReqCtx(orgId) }, {}) @@ -100,8 +104,10 @@ export async function registerTOTP(userId: string) { } export async function getGeneralSettings() { - "use cache"; - cacheLife("hours"); + if (process.env.DEBUG !== "true") { + ("use cache"); + cacheLife("hours"); + } return settingsService .getGeneralSettings({}, {}) @@ -109,8 +115,10 @@ export async function getGeneralSettings() { } export async function getLegalAndSupportSettings(organization?: string) { - "use cache"; - cacheLife("hours"); + if (process.env.DEBUG !== "true") { + ("use cache"); + cacheLife("hours"); + } return settingsService .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) @@ -118,8 +126,10 @@ export async function getLegalAndSupportSettings(organization?: string) { } export async function getPasswordComplexitySettings(organization?: string) { - "use cache"; - cacheLife("hours"); + if (process.env.DEBUG !== "true") { + ("use cache"); + cacheLife("hours"); + } return settingsService .getPasswordComplexitySettings({ ctx: makeReqCtx(organization) }) From 8d5882fdb0e62c11e2e51f383e3c254e56ef012d Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 26 Nov 2024 10:13:30 +0100 Subject: [PATCH 474/640] use cache under condition --- apps/login/src/lib/zitadel.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 9b8d37979c..1a1907144a 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -57,7 +57,8 @@ export const settingsService = createSettingsServiceClient(transport); export async function getBrandingSettings(organization?: string) { if (process.env.DEBUG !== "true") { - ("use cache"); + // @ts-ignore + `${"use cache"}`; cacheLife("hours"); } @@ -68,7 +69,8 @@ export async function getBrandingSettings(organization?: string) { export async function getLoginSettings(orgId?: string) { if (process.env.DEBUG !== "true") { - ("use cache"); + // @ts-ignore + `${"use cache"}`; cacheLife("hours"); } @@ -105,7 +107,8 @@ export async function registerTOTP(userId: string) { export async function getGeneralSettings() { if (process.env.DEBUG !== "true") { - ("use cache"); + // @ts-ignore + `${"use cache"}`; cacheLife("hours"); } @@ -116,7 +119,8 @@ export async function getGeneralSettings() { export async function getLegalAndSupportSettings(organization?: string) { if (process.env.DEBUG !== "true") { - ("use cache"); + // @ts-ignore + `${"use cache"}`; cacheLife("hours"); } @@ -127,7 +131,8 @@ export async function getLegalAndSupportSettings(organization?: string) { export async function getPasswordComplexitySettings(organization?: string) { if (process.env.DEBUG !== "true") { - ("use cache"); + // @ts-ignore + `${"use cache"}`; cacheLife("hours"); } From 0ab2a100a8c9e76e0ea2db7497f5f498c5eeab80 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 26 Nov 2024 10:22:59 +0100 Subject: [PATCH 475/640] wrap use cache --- apps/login/src/lib/zitadel.ts | 60 ++++++++++++++++------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 1a1907144a..a76f772f6b 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -52,31 +52,33 @@ export const userService = createUserServiceClient(transport); export const oidcService = createOIDCServiceClient(transport); export const idpService = createIdpServiceClient(transport); export const orgService = createOrganizationServiceClient(transport); - export const settingsService = createSettingsServiceClient(transport); -export async function getBrandingSettings(organization?: string) { - if (process.env.DEBUG !== "true") { - // @ts-ignore - `${"use cache"}`; - cacheLife("hours"); - } +async function cacheWrapper(callback: Promise) { + "use cache"; + cacheLife("hours"); - return settingsService + return callback; +} + +export async function getBrandingSettings(organization?: string) { + const useCache = process.env.DEBUG !== "true"; + + const callback = settingsService .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) .then((resp) => (resp.settings ? resp.settings : undefined)); + + return useCache ? cacheWrapper(callback) : callback; } export async function getLoginSettings(orgId?: string) { - if (process.env.DEBUG !== "true") { - // @ts-ignore - `${"use cache"}`; - cacheLife("hours"); - } + const useCache = process.env.DEBUG !== "true"; - return await settingsService + const callback = settingsService .getLoginSettings({ ctx: makeReqCtx(orgId) }, {}) .then((resp) => (resp.settings ? resp.settings : undefined)); + + return useCache ? cacheWrapper(callback) : callback; } export async function listIDPLinks(userId: string) { @@ -106,39 +108,33 @@ export async function registerTOTP(userId: string) { } export async function getGeneralSettings() { - if (process.env.DEBUG !== "true") { - // @ts-ignore - `${"use cache"}`; - cacheLife("hours"); - } + const useCache = process.env.DEBUG !== "true"; - return settingsService + const callback = settingsService .getGeneralSettings({}, {}) .then((resp) => resp.supportedLanguages); + + return useCache ? cacheWrapper(callback) : callback; } export async function getLegalAndSupportSettings(organization?: string) { - if (process.env.DEBUG !== "true") { - // @ts-ignore - `${"use cache"}`; - cacheLife("hours"); - } + const useCache = process.env.DEBUG !== "true"; - return settingsService + const callback = settingsService .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) .then((resp) => (resp.settings ? resp.settings : undefined)); + + return useCache ? cacheWrapper(callback) : callback; } export async function getPasswordComplexitySettings(organization?: string) { - if (process.env.DEBUG !== "true") { - // @ts-ignore - `${"use cache"}`; - cacheLife("hours"); - } + const useCache = process.env.DEBUG !== "true"; - return settingsService + const callback = settingsService .getPasswordComplexitySettings({ ctx: makeReqCtx(organization) }) .then((resp) => (resp.settings ? resp.settings : undefined)); + + return useCache ? cacheWrapper(callback) : callback; } export async function createSessionFromChecks( From e9f5d97352bd212504bf5c6a890c57f325e22eed Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 26 Nov 2024 10:29:32 +0100 Subject: [PATCH 476/640] cleanup wrapper, suspense --- apps/login/src/app/(login)/layout.tsx | 9 ++++++++- apps/login/src/lib/zitadel.ts | 14 +++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/login/src/app/(login)/layout.tsx b/apps/login/src/app/(login)/layout.tsx index 1975b7c5e5..c7c681f05f 100644 --- a/apps/login/src/app/(login)/layout.tsx +++ b/apps/login/src/app/(login)/layout.tsx @@ -1,5 +1,6 @@ import "@/styles/globals.scss"; +import { Alert, AlertType } from "@/components/alert"; import { LanguageProvider } from "@/components/language-provider"; import { LanguageSwitcher } from "@/components/language-switcher"; import { Theme } from "@/components/theme"; @@ -23,7 +24,13 @@ export default async function RootLayout({ - Loading...
    }> + + Loading... +
    + } + >
    ) { +const useCache = process.env.DEBUG !== "true"; + +async function cacheWrapper(callback: Promise) { "use cache"; cacheLife("hours"); @@ -62,8 +64,6 @@ async function cacheWrapper(callback: Promise) { } export async function getBrandingSettings(organization?: string) { - const useCache = process.env.DEBUG !== "true"; - const callback = settingsService .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) .then((resp) => (resp.settings ? resp.settings : undefined)); @@ -72,8 +72,6 @@ export async function getBrandingSettings(organization?: string) { } export async function getLoginSettings(orgId?: string) { - const useCache = process.env.DEBUG !== "true"; - const callback = settingsService .getLoginSettings({ ctx: makeReqCtx(orgId) }, {}) .then((resp) => (resp.settings ? resp.settings : undefined)); @@ -108,8 +106,6 @@ export async function registerTOTP(userId: string) { } export async function getGeneralSettings() { - const useCache = process.env.DEBUG !== "true"; - const callback = settingsService .getGeneralSettings({}, {}) .then((resp) => resp.supportedLanguages); @@ -118,8 +114,6 @@ export async function getGeneralSettings() { } export async function getLegalAndSupportSettings(organization?: string) { - const useCache = process.env.DEBUG !== "true"; - const callback = settingsService .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) .then((resp) => (resp.settings ? resp.settings : undefined)); @@ -128,8 +122,6 @@ export async function getLegalAndSupportSettings(organization?: string) { } export async function getPasswordComplexitySettings(organization?: string) { - const useCache = process.env.DEBUG !== "true"; - const callback = settingsService .getPasswordComplexitySettings({ ctx: makeReqCtx(organization) }) .then((resp) => (resp.settings ? resp.settings : undefined)); From fc371da2571d09f3cdeeb501a1e91971a9389fae Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 26 Nov 2024 11:06:22 +0100 Subject: [PATCH 477/640] redirect outside try catch, await cookie --- apps/login/src/components/session-item.tsx | 22 ++++++++++++--- apps/login/src/components/username-form.tsx | 6 ++++- apps/login/src/lib/cookies.ts | 6 ++--- apps/login/src/lib/server/loginname.ts | 30 +++++++++++---------- 4 files changed, 43 insertions(+), 21 deletions(-) diff --git a/apps/login/src/components/session-item.tsx b/apps/login/src/components/session-item.tsx index 5f9b734827..1f864d4929 100644 --- a/apps/login/src/components/session-item.tsx +++ b/apps/login/src/components/session-item.tsx @@ -6,6 +6,7 @@ import { XCircleIcon } from "@heroicons/react/24/outline"; import { Timestamp, timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import moment from "moment"; +import { redirect } from "next/navigation"; import { useState } from "react"; import { Avatar } from "./avatar"; @@ -57,18 +58,33 @@ export function SessionItem({ return (
    - - + ); diff --git a/apps/login/src/components/language-provider.tsx b/apps/login/src/components/language-provider.tsx index 524704b600..21a53093bb 100644 --- a/apps/login/src/components/language-provider.tsx +++ b/apps/login/src/components/language-provider.tsx @@ -1,10 +1,10 @@ import { NextIntlClientProvider } from "next-intl"; -import { getLocale, getMessages } from "next-intl/server"; +import { getMessages } from "next-intl/server"; import { ReactNode } from "react"; export async function LanguageProvider({ children }: { children: ReactNode }) { - const locale = await getLocale(); const messages = await getMessages(); + return ( {children} diff --git a/apps/login/src/components/skeleton.tsx b/apps/login/src/components/skeleton.tsx new file mode 100644 index 0000000000..548953d278 --- /dev/null +++ b/apps/login/src/components/skeleton.tsx @@ -0,0 +1,9 @@ +import { ReactNode } from "react"; + +export function Skeleton({ children }: { children?: ReactNode }) { + return ( +
    + {children} +
    + ); +} diff --git a/apps/login/src/styles/globals.scss b/apps/login/src/styles/globals.scss index 2a2e80f2c6..cfce853bc7 100755 --- a/apps/login/src/styles/globals.scss +++ b/apps/login/src/styles/globals.scss @@ -24,3 +24,42 @@ html { .form-checkbox:checked { background-image: url("/checkbox.svg"); } + +.skeleton { + --accents-2: var(--theme-light-background-400); + --accents-1: var(--theme-light-background-500); + + background-image: linear-gradient( + 270deg, + var(--accents-1), + var(--accents-2), + var(--accents-2), + var(--accents-1) + ); + background-size: 400% 100%; + animation: skeleton_loading 8s ease-in-out infinite; +} + +.dark .skeleton { + --accents-2: var(--theme-dark-background-400); + --accents-1: var(--theme-dark-background-500); + + background-image: linear-gradient( + 270deg, + var(--accents-1), + var(--accents-2), + var(--accents-2), + var(--accents-1) + ); + background-size: 400% 100%; + animation: skeleton_loading 8s ease-in-out infinite; +} + +@keyframes skeleton_loading { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} From 7d29e541b4a2615213511d3b7bc496ed97302f45 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 26 Nov 2024 14:48:12 +0100 Subject: [PATCH 483/640] cleanup --- apps/login/src/app/(login)/register/page.tsx | 1 - apps/login/src/components/register-form.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 113e33be74..2cd1c35346 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -41,7 +41,6 @@ export default async function Page(props: {

    {t("disabled.title")}

    {t("disabled.description")}

    - {JSON.stringify(loginSettings)}
    ); diff --git a/apps/login/src/components/register-form.tsx b/apps/login/src/components/register-form.tsx index 6905aa12ba..c336b836c4 100644 --- a/apps/login/src/components/register-form.tsx +++ b/apps/login/src/components/register-form.tsx @@ -50,7 +50,6 @@ export function RegisterForm({ loginSettings, }: Props) { const t = useTranslations("register"); - console.log(loginSettings); const { register, handleSubmit, formState } = useForm({ mode: "onBlur", From 9573cdba04a710e72ae7d77b37ef8e2b32e64115 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 27 Nov 2024 11:02:34 +0100 Subject: [PATCH 484/640] feat: implement session lifetimes --- apps/login/src/lib/server/cookie.ts | 7 ++- apps/login/src/lib/server/otp.ts | 70 ++++++++++++++------------- apps/login/src/lib/server/password.ts | 19 +++++--- apps/login/src/lib/server/register.ts | 7 ++- apps/login/src/lib/server/session.ts | 36 ++++++++++---- apps/login/src/lib/zitadel.ts | 16 +++--- packages/zitadel-client/src/index.ts | 2 +- 7 files changed, 93 insertions(+), 64 deletions(-) diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 17be3844aa..ab1513ac69 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -7,7 +7,7 @@ import { getSession, setSession, } from "@/lib/zitadel"; -import { timestampMs } from "@zitadel/client"; +import { Duration, timestampMs } from "@zitadel/client"; import { Challenges, RequestChallenges, @@ -30,6 +30,7 @@ export async function createSessionAndUpdateCookie( checks: Checks, challenges: RequestChallenges | undefined, authRequestId: string | undefined, + lifetime?: Duration, ): Promise { const createdSession = await createSessionFromChecks(checks, challenges); @@ -82,10 +83,12 @@ export async function createSessionForIdpAndUpdateCookie( idpIntentToken?: string | undefined; }, authRequestId: string | undefined, + lifetime?: Duration, ): Promise { const createdSession = await createSessionForUserIdAndIdpIntent( userId, idpIntent, + lifetime, ); if (createdSession) { @@ -140,12 +143,14 @@ export async function setSessionAndUpdateCookie( checks?: Checks, challenges?: RequestChallenges, authRequestId?: string, + lifetime?: Duration, ) { return setSession( recentCookie.id, recentCookie.token, challenges, checks, + lifetime, ).then((updatedSession) => { if (updatedSession) { const sessionCookie: CustomCookieData = { diff --git a/apps/login/src/lib/server/otp.ts b/apps/login/src/lib/server/otp.ts index 07aea40be2..b91d8eac7c 100644 --- a/apps/login/src/lib/server/otp.ts +++ b/apps/login/src/lib/server/otp.ts @@ -12,6 +12,7 @@ import { getSessionCookieById, getSessionCookieByLoginName, } from "../cookies"; +import { getLoginSettings } from "../zitadel"; export type SetOTPCommand = { loginName?: string; @@ -23,49 +24,52 @@ export type SetOTPCommand = { }; export async function setOTP(command: SetOTPCommand) { - const recentPromise = command.sessionId - ? getSessionCookieById({ sessionId: command.sessionId }).catch((error) => { - return Promise.reject(error); - }) + const recentSession = command.sessionId + ? await getSessionCookieById({ sessionId: command.sessionId }).catch( + (error) => { + return Promise.reject(error); + }, + ) : command.loginName - ? getSessionCookieByLoginName({ + ? await getSessionCookieByLoginName({ loginName: command.loginName, organization: command.organization, }).catch((error) => { return Promise.reject(error); }) - : getMostRecentSessionCookie().catch((error) => { + : await getMostRecentSessionCookie().catch((error) => { return Promise.reject(error); }); - return recentPromise.then((recent) => { - const checks = create(ChecksSchema, {}); + 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, - }); - } - - return setSessionAndUpdateCookie( - recent, - checks, - undefined, - command.authRequestId, - ).then((session) => { - return { - sessionId: session.id, - factors: session.factors, - challenges: session.challenges, - }; + 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(command.organization); + + return setSessionAndUpdateCookie( + recentSession, + checks, + undefined, + command.authRequestId, + loginSettings?.secondFactorCheckLifetime, + ).then((session) => { + return { + sessionId: session.id, + factors: session.factors, + challenges: session.challenges, + }; }); } diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 526c8c044a..59afca50a5 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -17,6 +17,7 @@ import { Checks, ChecksSchema, } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { User, UserState } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; @@ -66,6 +67,8 @@ export async function sendPassword(command: UpdateSessionCommand) { let session; let user: User; + let loginSettings: LoginSettings | undefined; + if (!sessionCookie) { const users = await listUsers({ loginName: command.loginName, @@ -80,10 +83,13 @@ export async function sendPassword(command: UpdateSessionCommand) { password: { password: command.checks.password?.password }, }); + loginSettings = await getLoginSettings(command.organization); + session = await createSessionAndUpdateCookie( checks, undefined, command.authRequestId, + loginSettings?.passwordCheckLifetime, ); } @@ -95,6 +101,7 @@ export async function sendPassword(command: UpdateSessionCommand) { command.checks, undefined, command.authRequestId, + loginSettings?.passwordCheckLifetime, ); if (!session?.factors?.user?.id) { @@ -110,6 +117,12 @@ export async function sendPassword(command: UpdateSessionCommand) { user = userResponse.user; } + if (!loginSettings) { + loginSettings = await getLoginSettings( + command.organization ?? session.factors?.user?.organizationId, + ); + } + if (!session?.factors?.user?.id || !sessionCookie) { return { error: "Could not create session for user" }; } @@ -241,9 +254,6 @@ export async function sendPassword(command: UpdateSessionCommand) { // return router.push(`/passkey/set?` + params); // } else if (command.authRequestId && session.id) { - const loginSettings = await getLoginSettings( - command.organization ?? session.factors?.user?.organizationId, - ); const nextUrl = await getNextUrl( { sessionId: session.id, @@ -257,9 +267,6 @@ export async function sendPassword(command: UpdateSessionCommand) { return { redirect: nextUrl }; } - const loginSettings = await getLoginSettings( - command.organization ?? session.factors?.user?.organizationId, - ); const url = await getNextUrl( { loginName: session.factors.user.loginName, diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index d045f4c8bf..01ddb0d8c8 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -37,6 +37,8 @@ export async function registerUser(command: RegisterUserCommand) { return { error: "Could not create user" }; } + const loginSettings = await getLoginSettings(command.organization); + let checkPayload: any = { user: { search: { case: "userId", value: human.userId } }, }; @@ -54,6 +56,7 @@ export async function registerUser(command: RegisterUserCommand) { checks, undefined, command.authRequestId, + command.password ? loginSettings?.passwordCheckLifetime : undefined, ); if (!session || !session.factors?.user) { @@ -72,10 +75,6 @@ export async function registerUser(command: RegisterUserCommand) { return { redirect: "/passkey/set?" + params }; } else { - const loginSettings = await getLoginSettings( - session.factors.user.organizationId, - ); - const url = await getNextUrl( command.authRequestId && session.id ? { diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index d204e4beef..060be989a1 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -7,8 +7,10 @@ import { import { deleteSession, getLoginSettings, + getUserByID, listAuthenticationMethodTypes, } from "@/lib/zitadel"; +import { Duration } from "@zitadel/client"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; @@ -38,20 +40,26 @@ export async function createNewSessionForIdp(options: CreateNewSessionCommand) { if (!userId || !idpIntent) { throw new Error("No userId or loginName provided"); } + + const user = await getUserByID(userId); + + if (!user) { + return { error: "Could not find user" }; + } + + const loginSettings = await getLoginSettings(user.details?.resourceOwner); + const session = await createSessionForIdpAndUpdateCookie( userId, idpIntent, authRequestId, + loginSettings?.externalLoginCheckLifetime, ); if (!session || !session.factors?.user) { return { error: "Could not create session" }; } - const loginSettings = await getLoginSettings( - session.factors.user.organizationId, - ); - const url = await getNextUrl( authRequestId && session.id ? { @@ -110,6 +118,7 @@ export type UpdateSessionCommand = { checks?: Checks; authRequestId?: string; challenges?: RequestChallenges; + lifetime?: Duration; }; export async function updateSession(options: UpdateSessionCommand) { @@ -121,17 +130,17 @@ export async function updateSession(options: UpdateSessionCommand) { authRequestId, challenges, } = options; - const sessionPromise = sessionId - ? getSessionCookieById({ sessionId }).catch((error) => { + const recentSession = sessionId + ? await getSessionCookieById({ sessionId }).catch((error) => { return Promise.reject(error); }) : loginName - ? getSessionCookieByLoginName({ loginName, organization }).catch( + ? await getSessionCookieByLoginName({ loginName, organization }).catch( (error) => { return Promise.reject(error); }, ) - : getMostRecentSessionCookie().catch((error) => { + : await getMostRecentSessionCookie().catch((error) => { return Promise.reject(error); }); @@ -148,13 +157,20 @@ export async function updateSession(options: UpdateSessionCommand) { challenges.webAuthN.domain = hostname; } - const recent = await sessionPromise; + const loginSettings = await getLoginSettings(organization); + + const lifetime = checks?.webAuthN + ? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey + : checks?.otpEmail || checks?.otpSms + ? loginSettings?.secondFactorCheckLifetime + : undefined; const session = await setSessionAndUpdateCookie( - recent, + recentSession, checks, challenges, authRequestId, + lifetime, ); // if password, check if user has MFA methods diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index aeda88c386..7a63fc55a7 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -18,7 +18,7 @@ import { VerifyU2FRegistrationRequest, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { create } from "@zitadel/client"; +import { create, Duration } from "@zitadel/client"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; @@ -132,15 +132,13 @@ export async function getPasswordComplexitySettings(organization?: string) { export async function createSessionFromChecks( checks: Checks, challenges: RequestChallenges | undefined, + lifetime?: Duration, ) { return sessionService.createSession( { checks: checks, challenges, - lifetime: { - seconds: BigInt(SESSION_LIFETIME_S), - nanos: 0, - }, + lifetime, }, {}, ); @@ -152,6 +150,7 @@ export async function createSessionForUserIdAndIdpIntent( idpIntentId?: string | undefined; idpIntentToken?: string | undefined; }, + lifetime?: Duration, ) { return sessionService.createSession({ checks: { @@ -163,10 +162,7 @@ export async function createSessionForUserIdAndIdpIntent( }, idpIntent, }, - // lifetime: { - // seconds: 300, - // nanos: 0, - // }, + lifetime, }); } @@ -175,6 +171,7 @@ export async function setSession( sessionToken: string, challenges: RequestChallenges | undefined, checks?: Checks, + lifetime?: Duration, ) { return sessionService.setSession( { @@ -183,6 +180,7 @@ export async function setSession( challenges, checks: checks ? checks : {}, metadata: {}, + lifetime, }, {}, ); diff --git a/packages/zitadel-client/src/index.ts b/packages/zitadel-client/src/index.ts index dd677643fc..7cf14163bf 100644 --- a/packages/zitadel-client/src/index.ts +++ b/packages/zitadel-client/src/index.ts @@ -4,4 +4,4 @@ export { NewAuthorizationBearerInterceptor } from "./interceptors"; // TODO: Move this to `./protobuf.ts` and export it from there export { create, fromJson, toJson } from "@bufbuild/protobuf"; export { TimestampSchema, timestampDate, timestampFromDate, timestampFromMs, timestampMs } from "@bufbuild/protobuf/wkt"; -export type { Timestamp } from "@bufbuild/protobuf/wkt"; +export type { Duration, Timestamp } from "@bufbuild/protobuf/wkt"; From 5381084d941ad2ad0c9b2269ce6d95588595095c Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 11:40:17 +0100 Subject: [PATCH 485/640] test: test acceptance against cloud --- .github/workflows/test.yml | 45 ++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ce61199e7f..8662fc03b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,28 @@ name: Quality -on: pull_request +on: + pull_request: + schedule: + # All 5 minutes + - cron: '*/5 * * * *' jobs: + matrix: + # If the workflow is triggered by a schedule event, only the acceptance tests run against QA and Prod. + name: Preparte Matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.prepare-matrix.outputs.matrix }} + steps: + - name: Prepare Matrix + id: prepare-matrix + run: | + if [ -z "${{ github.event.schedule }}" ]; then + echo "::set-output name=matrix::['test:acceptance:qa', 'test:acceptance:prod']" + else + echo "::set-output name=matrix::['format --check', 'lint', 'test:unit', 'test:integration', 'test:acceptance']" + fi + quality: name: Ensure Quality @@ -16,12 +36,7 @@ jobs: strategy: fail-fast: false matrix: - command: - - format --check - - lint - - test:unit - - test:integration - - test:acceptance + command: ${{fromJson(needs.prepare-matrix.outputs.matrix)}} steps: - name: Checkout Repo @@ -55,7 +70,7 @@ jobs: # We can cache the Playwright binary independently from the pnpm cache, because we install it separately. # After pnpm install --frozen-lockfile, we can get the version so we only have to download the binary once per version. - run: echo "PLAYWRIGHT_VERSION=$(npx playwright --version | cut -d ' ' -f 2)" >> $GITHUB_ENV - if: ${{ matrix.command == 'test:acceptance' }} + if: ${{ startsWith(matrix.command, 'test:acceptance') }} - uses: actions/cache@v4.0.2 name: Setup Playwright binary cache @@ -65,11 +80,11 @@ jobs: key: ${{ runner.os }}-playwright-binary-${{ env.PLAYWRIGHT_VERSION }} restore-keys: | ${{ runner.os }}-playwright-binary- - if: ${{ matrix.command == 'test:acceptance' }} + if: ${{ startsWith(matrix.command, 'test:acceptance') }} - name: Install Playwright Browsers run: pnpm exec playwright install --with-deps - if: ${{ matrix.command == 'test:acceptance' && steps.playwright-cache.outputs.cache-hit != 'true' }} + if: ${{ startsWith(matrix.command, 'test:acceptance') && steps.playwright-cache.outputs.cache-hit != 'true' }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -83,6 +98,14 @@ jobs: run: pnpm build if: ${{ matrix.command == 'test:acceptance' }} + - name: Create Production Build + run: pnpm build + if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' }} + env: + ZITADEL_API_URL: ${{ secrets.E2E_QA_ZITADEL_API_URL }} + ZITADEL_SERVICE_USER_ID: ${{ secrets.E2E_QA_ZITADEL_SERVICE_USER_ID }} + ZITADEL_SERVICE_USER_TOKEN: ${{ secrets.E2E_QA_ZITADEL_SERVICE_USER_TOKEN }} + - name: Check id: check - run: pnpm ${{ matrix.command }} + run: pnpm ${{ contains(matrix.command, 'test:acceptance') && 'test:acceptance' || matrix.command }} From 520c1dc7c072dea1b66f8f4484641200e0e56ee2 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 27 Nov 2024 11:47:49 +0100 Subject: [PATCH 486/640] fix: host passkey login --- apps/login/readme.md | 1 - apps/login/src/lib/server/session.ts | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index a1999e6041..2aa09da2e4 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -393,4 +393,3 @@ In future, self service options to jump to are shown below, like: - loginSettings.disableLoginWithPhone - loginSettings.allowExternalIdp - this will be deprecated with the new login as it can be determined by the available IDPs - loginSettings.forceMfaLocalOnly -- loginSettings lifetimes - all besides Multifactor Init Check can be implemented. for the Init Check, an external storage or a timestamp has to be implemented which keeps track of the last verification diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 060be989a1..9726ce84e4 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -14,6 +14,7 @@ import { Duration } from "@zitadel/client"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { headers } from "next/headers"; import { redirect } from "next/navigation"; import { getNextUrl } from "../client"; import { @@ -144,8 +145,7 @@ export async function updateSession(options: UpdateSessionCommand) { return Promise.reject(error); }); - // TODO remove ports from host header for URL with port - const host = "localhost"; + const host = (await headers()).get("host"); if ( host && @@ -154,6 +154,7 @@ export async function updateSession(options: UpdateSessionCommand) { !challenges.webAuthN.domain ) { const [hostname, port] = host.split(":"); + challenges.webAuthN.domain = hostname; } From b87b6e254abb918737e9afbe40019824bea84651 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 27 Nov 2024 11:54:42 +0100 Subject: [PATCH 487/640] rm SESSION_LIFETIME_S --- apps/login/src/lib/zitadel.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 7a63fc55a7..be410551fb 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -40,8 +40,6 @@ import { import { unstable_cacheLife as cacheLife } from "next/cache"; import { PROVIDER_MAPPING } from "./idp"; -const SESSION_LIFETIME_S = 3600; // TODO load from oidc settings - const transport = createServerTransport( process.env.ZITADEL_SERVICE_USER_TOKEN!, { baseUrl: process.env.ZITADEL_API_URL! }, From 80fd39ff3e9d0653c5f107768773513a384f973c Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 12:14:38 +0100 Subject: [PATCH 488/640] quotes --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8662fc03b0..ee2643e056 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,9 +18,9 @@ jobs: id: prepare-matrix run: | if [ -z "${{ github.event.schedule }}" ]; then - echo "::set-output name=matrix::['test:acceptance:qa', 'test:acceptance:prod']" + echo '::set-output name=matrix::["test:acceptance:qa", "test:acceptance:prod"]' else - echo "::set-output name=matrix::['format --check', 'lint', 'test:unit', 'test:integration', 'test:acceptance']" + echo '::set-output name=matrix::["format --check", "lint", "test:unit", "test:integration", "test:acceptance"]' fi quality: @@ -36,7 +36,7 @@ jobs: strategy: fail-fast: false matrix: - command: ${{fromJson(needs.prepare-matrix.outputs.matrix)}} + command: ${{ fromJson( needs.prepare-matrix.outputs.matrix ) }} steps: - name: Checkout Repo From 6a03c58ee0eb982190380dff87d40cbc285df690 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 12:22:20 +0100 Subject: [PATCH 489/640] GITHUB_OUTPUT --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ee2643e056..bb40d74d00 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,9 +18,9 @@ jobs: id: prepare-matrix run: | if [ -z "${{ github.event.schedule }}" ]; then - echo '::set-output name=matrix::["test:acceptance:qa", "test:acceptance:prod"]' + echo 'matrix=["test:acceptance:qa", "test:acceptance:prod"]' >> $GITHUB_OUTPUT else - echo '::set-output name=matrix::["format --check", "lint", "test:unit", "test:integration", "test:acceptance"]' + echo 'matrix=["format --check", "lint", "test:unit", "test:integration", "test:acceptance"]' >> $GITHUB_OUTPUT fi quality: From 1f1310b59f5ddcf694fe882449530eaeac37b7cd Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 12:23:03 +0100 Subject: [PATCH 490/640] minutely --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bb40d74d00..79644724c1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,8 +3,8 @@ name: Quality on: pull_request: schedule: - # All 5 minutes - - cron: '*/5 * * * *' + # All 1 minutes + - cron: '*/1 * * * *' jobs: matrix: From 16a1bcba208726aec3e6920bf57db3a7320a4a29 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 12:23:22 +0100 Subject: [PATCH 491/640] minutely --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 79644724c1..a700c0b7b8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ on: pull_request: schedule: # All 1 minutes - - cron: '*/1 * * * *' + - cron: '* * * * *' jobs: matrix: From 0bae8d916adab477f7ee9577bfd4b0499d9af0e5 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 12:28:41 +0100 Subject: [PATCH 492/640] json --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a700c0b7b8..8ec0abb66b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,9 +18,9 @@ jobs: id: prepare-matrix run: | if [ -z "${{ github.event.schedule }}" ]; then - echo 'matrix=["test:acceptance:qa", "test:acceptance:prod"]' >> $GITHUB_OUTPUT + echo "matrix=$(echo '[\"test:acceptance:qa\", \"test:acceptance:prod\"]' | jq -R .)" >> $GITHUB_OUTPUT else - echo 'matrix=["format --check", "lint", "test:unit", "test:integration", "test:acceptance"]' >> $GITHUB_OUTPUT + echo "matrix=$(echo '[\"format --check\", \"lint\", \"test:unit\", \"test:integration\", \"test:acceptance\"]' | jq -R .)" >> $GITHUB_OUTPUT fi quality: From 64b2dc46100899df120398a879c6a285f226d00b Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 12:38:19 +0100 Subject: [PATCH 493/640] dispatch --- .github/workflows/test.yml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8ec0abb66b..9729cdf0a0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,6 +5,20 @@ on: schedule: # All 1 minutes - cron: '* * * * *' + workflow_dispatch: + inputs: + zitadel-api-url: + description: 'ZITADEL API URL' + required: true + default: ${{ secrets.E2E_QA_ZITADEL_API_URL }} + zitadel-service-user-id: + description: 'ZITADEL SERVICE USER ID' + required: true + default: ${{ secrets.E2E_QA_ZITADEL_SERVICE_USER_ID }} + zitadel-service-user-token: + description: 'ZITADEL SERVICE USER TOKEN' + required: true + default: ${{ secrets.E2E_QA_ZITADEL_SERVICE_USER_TOKEN }} jobs: matrix: @@ -19,6 +33,8 @@ jobs: run: | if [ -z "${{ github.event.schedule }}" ]; then echo "matrix=$(echo '[\"test:acceptance:qa\", \"test:acceptance:prod\"]' | jq -R .)" >> $GITHUB_OUTPUT + else if [ -z "${{ github.event.inputs.zitadel-api-url }}" ]; then + echo "matrix=$(echo '[\"test:acceptance:custom\"]' | jq -R .)" >> $GITHUB_OUTPUT else echo "matrix=$(echo '[\"format --check\", \"lint\", \"test:unit\", \"test:integration\", \"test:acceptance\"]' | jq -R .)" >> $GITHUB_OUTPUT fi @@ -100,11 +116,11 @@ jobs: - name: Create Production Build run: pnpm build - if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' }} + if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' || matrix.command == 'test:acceptance:custom' }} env: - ZITADEL_API_URL: ${{ secrets.E2E_QA_ZITADEL_API_URL }} - ZITADEL_SERVICE_USER_ID: ${{ secrets.E2E_QA_ZITADEL_SERVICE_USER_ID }} - ZITADEL_SERVICE_USER_TOKEN: ${{ secrets.E2E_QA_ZITADEL_SERVICE_USER_TOKEN }} + ZITADEL_API_URL: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-api-url || secrets.E2E_QA_ZITADEL_API_URL }} + ZITADEL_SERVICE_USER_ID: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-service-user-id || secrets.E2E_QA_ZITADEL_SERVICE_USER_ID }} + ZITADEL_SERVICE_USER_TOKEN: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-service-user-token || secrets.E2E_QA_ZITADEL_SERVICE_USER_TOKEN }} - name: Check id: check From c95f5cddbce1f9085140281b2948bb40fcfc0d8a Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 12:45:54 +0100 Subject: [PATCH 494/640] inputs --- .github/workflows/test.yml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9729cdf0a0..9c680eef7b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,18 +7,18 @@ on: - cron: '* * * * *' workflow_dispatch: inputs: + zitadel-env: + description: 'ZITADEL ENVIRONMENT' + required: false zitadel-api-url: description: 'ZITADEL API URL' - required: true - default: ${{ secrets.E2E_QA_ZITADEL_API_URL }} + required: false zitadel-service-user-id: description: 'ZITADEL SERVICE USER ID' - required: true - default: ${{ secrets.E2E_QA_ZITADEL_SERVICE_USER_ID }} + required: false zitadel-service-user-token: description: 'ZITADEL SERVICE USER TOKEN' - required: true - default: ${{ secrets.E2E_QA_ZITADEL_SERVICE_USER_TOKEN }} + required: false jobs: matrix: @@ -33,6 +33,8 @@ jobs: run: | if [ -z "${{ github.event.schedule }}" ]; then echo "matrix=$(echo '[\"test:acceptance:qa\", \"test:acceptance:prod\"]' | jq -R .)" >> $GITHUB_OUTPUT + else if [ -z "${{ github.event.inputs.zitadel-env }}" ]; then + echo "matrix=$(echo '[\"test:acceptance:${{ github.event.inputs.zitadel-env }}\"]' | jq -R .)" >> $GITHUB_OUTPUT else if [ -z "${{ github.event.inputs.zitadel-api-url }}" ]; then echo "matrix=$(echo '[\"test:acceptance:custom\"]' | jq -R .)" >> $GITHUB_OUTPUT else @@ -118,9 +120,9 @@ jobs: run: pnpm build if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' || matrix.command == 'test:acceptance:custom' }} env: - ZITADEL_API_URL: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-api-url || secrets.E2E_QA_ZITADEL_API_URL }} - ZITADEL_SERVICE_USER_ID: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-service-user-id || secrets.E2E_QA_ZITADEL_SERVICE_USER_ID }} - ZITADEL_SERVICE_USER_TOKEN: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-service-user-token || secrets.E2E_QA_ZITADEL_SERVICE_USER_TOKEN }} + ZITADEL_API_URL: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-api-url || matrix.command == 'test:acceptance:qa' && secrets.E2E_QA_ZITADEL_API_URL || secrets.E2E_PROD_ZITADEL_API_URL }} + ZITADEL_SERVICE_USER_ID: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-service-user-id || matrix.command == 'test:acceptance:qa' && secrets.E2E_QA_ZITADEL_SERVICE_USER_ID || secrets.E2E_PROD_ZITADEL_SERVICE_USER_ID}} + ZITADEL_SERVICE_USER_TOKEN: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-service-user-token || matrix.command == 'test:acceptance:qa' && secrets.E2E_QA_ZITADEL_SERVICE_USER_TOKEN || secrets.E2E_PROD_ZITADEL_SERVICE_USER_TOKEN}} - name: Check id: check From f39e97288c32986409131f38da56a85ba749def2 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 12:48:59 +0100 Subject: [PATCH 495/640] elif --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9c680eef7b..2791d2e279 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,9 +33,9 @@ jobs: run: | if [ -z "${{ github.event.schedule }}" ]; then echo "matrix=$(echo '[\"test:acceptance:qa\", \"test:acceptance:prod\"]' | jq -R .)" >> $GITHUB_OUTPUT - else if [ -z "${{ github.event.inputs.zitadel-env }}" ]; then + elif [ -z "${{ github.event.inputs.zitadel-env }}" ]; then echo "matrix=$(echo '[\"test:acceptance:${{ github.event.inputs.zitadel-env }}\"]' | jq -R .)" >> $GITHUB_OUTPUT - else if [ -z "${{ github.event.inputs.zitadel-api-url }}" ]; then + elif [ -z "${{ github.event.inputs.zitadel-api-url }}" ]; then echo "matrix=$(echo '[\"test:acceptance:custom\"]' | jq -R .)" >> $GITHUB_OUTPUT else echo "matrix=$(echo '[\"format --check\", \"lint\", \"test:unit\", \"test:integration\", \"test:acceptance\"]' | jq -R .)" >> $GITHUB_OUTPUT From 1fb6dec1fe85b69bad61d7b6f24c96423e36d5ac Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 12:50:57 +0100 Subject: [PATCH 496/640] enum --- .github/workflows/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2791d2e279..ad08f02fef 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,12 @@ on: inputs: zitadel-env: description: 'ZITADEL ENVIRONMENT' - required: false + required: true + type: choice + options: + - 'qa' + - 'prod' + - 'custom' zitadel-api-url: description: 'ZITADEL API URL' required: false From 95a2cd79ff6e0e07ddbb25eda7f11110acb7f30d Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 12:52:54 +0100 Subject: [PATCH 497/640] json --- .github/workflows/test.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad08f02fef..e9157f2ca0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,13 +37,13 @@ jobs: id: prepare-matrix run: | if [ -z "${{ github.event.schedule }}" ]; then - echo "matrix=$(echo '[\"test:acceptance:qa\", \"test:acceptance:prod\"]' | jq -R .)" >> $GITHUB_OUTPUT - elif [ -z "${{ github.event.inputs.zitadel-env }}" ]; then - echo "matrix=$(echo '[\"test:acceptance:${{ github.event.inputs.zitadel-env }}\"]' | jq -R .)" >> $GITHUB_OUTPUT - elif [ -z "${{ github.event.inputs.zitadel-api-url }}" ]; then - echo "matrix=$(echo '[\"test:acceptance:custom\"]' | jq -R .)" >> $GITHUB_OUTPUT + echo 'matrix=["test:acceptance:qa", "test:acceptance:prod"]' >> $GITHUB_OUTPUT + elif [ -n "${{ github.event.inputs.zitadel-env }}" ]; then + echo "matrix=[\"test:acceptance:${{ github.event.inputs.zitadel-env }}\"]" >> $GITHUB_OUTPUT + elif [ -n "${{ github.event.inputs.zitadel-api-url }}" ]; then + echo 'matrix=["test:acceptance:custom"]' >> $GITHUB_OUTPUT else - echo "matrix=$(echo '[\"format --check\", \"lint\", \"test:unit\", \"test:integration\", \"test:acceptance\"]' | jq -R .)" >> $GITHUB_OUTPUT + echo 'matrix=["format --check", "lint", "test:unit", "test:integration", "test:acceptance"]' >> $GITHUB_OUTPUT fi quality: From be6e63e87ad10663d26aa3d17c984e4350e6793d Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 12:55:33 +0100 Subject: [PATCH 498/640] json --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e9157f2ca0..c6fae8e7b6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,7 +39,7 @@ jobs: if [ -z "${{ github.event.schedule }}" ]; then echo 'matrix=["test:acceptance:qa", "test:acceptance:prod"]' >> $GITHUB_OUTPUT elif [ -n "${{ github.event.inputs.zitadel-env }}" ]; then - echo "matrix=[\"test:acceptance:${{ github.event.inputs.zitadel-env }}\"]" >> $GITHUB_OUTPUT + echo 'matrix=["test:acceptance:${{ github.event.inputs.zitadel-env }}"]' >> $GITHUB_OUTPUT elif [ -n "${{ github.event.inputs.zitadel-api-url }}" ]; then echo 'matrix=["test:acceptance:custom"]' >> $GITHUB_OUTPUT else From 565041f365255d79b36276ccd40e94b88840c7d8 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 12:56:59 +0100 Subject: [PATCH 499/640] debug --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c6fae8e7b6..b3d33f871f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,6 +45,8 @@ jobs: else echo 'matrix=["format --check", "lint", "test:unit", "test:integration", "test:acceptance"]' >> $GITHUB_OUTPUT fi + - name: Show Matrix + run: echo ${{ steps.prepare-matrix.outputs.matrix }} | jq quality: name: Ensure Quality From def3174f74dd052bc2b90ccfecdbcc488469a0c1 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 12:58:02 +0100 Subject: [PATCH 500/640] debug --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b3d33f871f..be37488622 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,7 @@ on: jobs: matrix: # If the workflow is triggered by a schedule event, only the acceptance tests run against QA and Prod. - name: Preparte Matrix + name: Prepare Matrix runs-on: ubuntu-latest outputs: matrix: ${{ steps.prepare-matrix.outputs.matrix }} @@ -46,7 +46,7 @@ jobs: echo 'matrix=["format --check", "lint", "test:unit", "test:integration", "test:acceptance"]' >> $GITHUB_OUTPUT fi - name: Show Matrix - run: echo ${{ steps.prepare-matrix.outputs.matrix }} | jq + run: echo '${{ steps.prepare-matrix.outputs.matrix }}' | jq quality: name: Ensure Quality From e12d3c7015b7afab0fc8cf6e6c86a8c2bf1783f6 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 13:04:21 +0100 Subject: [PATCH 501/640] quote --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index be37488622..d8b9761a45 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,7 @@ on: jobs: matrix: # If the workflow is triggered by a schedule event, only the acceptance tests run against QA and Prod. - name: Prepare Matrix + name: Preparte Matrix runs-on: ubuntu-latest outputs: matrix: ${{ steps.prepare-matrix.outputs.matrix }} @@ -46,7 +46,7 @@ jobs: echo 'matrix=["format --check", "lint", "test:unit", "test:integration", "test:acceptance"]' >> $GITHUB_OUTPUT fi - name: Show Matrix - run: echo '${{ steps.prepare-matrix.outputs.matrix }}' | jq + run: echo ${{ steps.prepare-matrix.outputs.matrix }} | jq quality: name: Ensure Quality @@ -61,7 +61,7 @@ jobs: strategy: fail-fast: false matrix: - command: ${{ fromJson( needs.prepare-matrix.outputs.matrix ) }} + command: "${{ fromJson( needs.prepare-matrix.outputs.matrix ) }}" steps: - name: Checkout Repo From c3868977ef2f61ee3e7a7e82bff0b7b0c7419001 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 13:09:17 +0100 Subject: [PATCH 502/640] quote --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d8b9761a45..64878ddccf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,7 +46,7 @@ jobs: echo 'matrix=["format --check", "lint", "test:unit", "test:integration", "test:acceptance"]' >> $GITHUB_OUTPUT fi - name: Show Matrix - run: echo ${{ steps.prepare-matrix.outputs.matrix }} | jq + run: echo '${{ steps.prepare-matrix.outputs.matrix }}' | jq quality: name: Ensure Quality @@ -61,7 +61,7 @@ jobs: strategy: fail-fast: false matrix: - command: "${{ fromJson( needs.prepare-matrix.outputs.matrix ) }}" + command: '${{ fromJson( needs.prepare-matrix.outputs.matrix ) }}' steps: - name: Checkout Repo From d4898943f4102e994a26435462661686697d3dc3 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 13:10:34 +0100 Subject: [PATCH 503/640] quote --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 64878ddccf..37186b8c40 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,7 +46,7 @@ jobs: echo 'matrix=["format --check", "lint", "test:unit", "test:integration", "test:acceptance"]' >> $GITHUB_OUTPUT fi - name: Show Matrix - run: echo '${{ steps.prepare-matrix.outputs.matrix }}' | jq + run: echo '${{ fromJson( steps.prepare-matrix.outputs.matrix ) }}' quality: name: Ensure Quality From 74db49e7d2bf7354f38f0e8f02a6be7f27edbbf2 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 13:11:34 +0100 Subject: [PATCH 504/640] quote --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 37186b8c40..83fdd00b1d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,7 +46,7 @@ jobs: echo 'matrix=["format --check", "lint", "test:unit", "test:integration", "test:acceptance"]' >> $GITHUB_OUTPUT fi - name: Show Matrix - run: echo '${{ fromJson( steps.prepare-matrix.outputs.matrix ) }}' + run: echo '${{ steps.prepare-matrix.outputs.matrix }}' quality: name: Ensure Quality @@ -61,7 +61,7 @@ jobs: strategy: fail-fast: false matrix: - command: '${{ fromJson( needs.prepare-matrix.outputs.matrix ) }}' + command: ${{ needs.prepare-matrix.outputs.matrix }} steps: - name: Checkout Repo From 71d9f99f746b5c263930a5f3c65005b85ef37820 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 13:14:07 +0100 Subject: [PATCH 505/640] id --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 83fdd00b1d..ece95547fe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,9 +26,9 @@ on: required: false jobs: - matrix: + prepare-matrix: # If the workflow is triggered by a schedule event, only the acceptance tests run against QA and Prod. - name: Preparte Matrix + name: Prepare Matrix runs-on: ubuntu-latest outputs: matrix: ${{ steps.prepare-matrix.outputs.matrix }} @@ -61,7 +61,7 @@ jobs: strategy: fail-fast: false matrix: - command: ${{ needs.prepare-matrix.outputs.matrix }} + command: ${{ fromJson( needs.prepare-matrix.outputs.matrix ) }} steps: - name: Checkout Repo From fab9206e7ebafe66fa8bbb745f23ba2f5c7b0c29 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 13:20:19 +0100 Subject: [PATCH 506/640] debug --- .github/workflows/test.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ece95547fe..921e84d694 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,6 +48,16 @@ jobs: - name: Show Matrix run: echo '${{ steps.prepare-matrix.outputs.matrix }}' + debug: + name: Debug matrix + runs-on: ubuntu-latest + steps: + - name: Debug matrix + run: echo ${{ needs.prepare-matrix.outputs.matrix }} + - name: Debug fromJson + run: echo ${{ fromJson(needs.prepare-matrix.outputs.matrix) }} + + quality: name: Ensure Quality From ef7fd863a57b5eb9d7a03aad51535f0d659eb7aa Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 13:25:06 +0100 Subject: [PATCH 507/640] no dashes --- .github/workflows/test.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 921e84d694..802d10bb2e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,15 +26,15 @@ on: required: false jobs: - prepare-matrix: + matrix: # If the workflow is triggered by a schedule event, only the acceptance tests run against QA and Prod. - name: Prepare Matrix + name: Matrix runs-on: ubuntu-latest outputs: - matrix: ${{ steps.prepare-matrix.outputs.matrix }} + matrix: ${{ steps.matrix.outputs.matrix }} steps: - - name: Prepare Matrix - id: prepare-matrix + - name: Matrix + id: matrix run: | if [ -z "${{ github.event.schedule }}" ]; then echo 'matrix=["test:acceptance:qa", "test:acceptance:prod"]' >> $GITHUB_OUTPUT @@ -46,16 +46,16 @@ jobs: echo 'matrix=["format --check", "lint", "test:unit", "test:integration", "test:acceptance"]' >> $GITHUB_OUTPUT fi - name: Show Matrix - run: echo '${{ steps.prepare-matrix.outputs.matrix }}' + run: echo '${{ steps.matrix.outputs.matrix }}' debug: name: Debug matrix runs-on: ubuntu-latest steps: - name: Debug matrix - run: echo ${{ needs.prepare-matrix.outputs.matrix }} + run: echo ${{ needs.matrix.outputs.matrix }} - name: Debug fromJson - run: echo ${{ fromJson(needs.prepare-matrix.outputs.matrix) }} + run: echo ${{ fromJson(needs.matrix.outputs.matrix) }} quality: @@ -71,7 +71,7 @@ jobs: strategy: fail-fast: false matrix: - command: ${{ fromJson( needs.prepare-matrix.outputs.matrix ) }} + command: ${{ fromJson( needs.matrix.outputs.matrix ) }} steps: - name: Checkout Repo From cf47029e8984a85721bfc9e2a40ad3d55f3f60d4 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 13:26:14 +0100 Subject: [PATCH 508/640] needs matrix --- .github/workflows/test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 802d10bb2e..76189473e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,6 +51,8 @@ jobs: debug: name: Debug matrix runs-on: ubuntu-latest + needs: + - matrix steps: - name: Debug matrix run: echo ${{ needs.matrix.outputs.matrix }} @@ -68,6 +70,9 @@ jobs: permissions: contents: "read" + needs: + - matrix + strategy: fail-fast: false matrix: From b1f3d72256195eeb216dd4f3beaa37bb8ef8b97d Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 13:31:50 +0100 Subject: [PATCH 509/640] -n --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 76189473e5..84175b3ca5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,7 +36,7 @@ jobs: - name: Matrix id: matrix run: | - if [ -z "${{ github.event.schedule }}" ]; then + if [ -n "${{ github.event.schedule }}" ]; then echo 'matrix=["test:acceptance:qa", "test:acceptance:prod"]' >> $GITHUB_OUTPUT elif [ -n "${{ github.event.inputs.zitadel-env }}" ]; then echo 'matrix=["test:acceptance:${{ github.event.inputs.zitadel-env }}"]' >> $GITHUB_OUTPUT From 857dd9101de5abb12ec192f7d1e1762ec6889a12 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 13:58:58 +0100 Subject: [PATCH 510/640] acceptance --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 84175b3ca5..b55f565bac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -142,9 +142,9 @@ jobs: run: pnpm build if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' || matrix.command == 'test:acceptance:custom' }} env: - ZITADEL_API_URL: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-api-url || matrix.command == 'test:acceptance:qa' && secrets.E2E_QA_ZITADEL_API_URL || secrets.E2E_PROD_ZITADEL_API_URL }} - ZITADEL_SERVICE_USER_ID: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-service-user-id || matrix.command == 'test:acceptance:qa' && secrets.E2E_QA_ZITADEL_SERVICE_USER_ID || secrets.E2E_PROD_ZITADEL_SERVICE_USER_ID}} - ZITADEL_SERVICE_USER_TOKEN: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-service-user-token || matrix.command == 'test:acceptance:qa' && secrets.E2E_QA_ZITADEL_SERVICE_USER_TOKEN || secrets.E2E_PROD_ZITADEL_SERVICE_USER_TOKEN}} + ZITADEL_API_URL: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-api-url || matrix.command == 'test:acceptance:qa' && secrets.ACCEPTANCE_QA_ZITADEL_API_URL || secrets.ACCEPTANCE_PROD_ZITADEL_API_URL }} + ZITADEL_SERVICE_USER_ID: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-service-user-id || matrix.command == 'test:acceptance:qa' && secrets.ACCEPTANCE_QA_ZITADEL_SERVICE_USER_ID || secrets.ACCEPTANCE_PROD_ZITADEL_SERVICE_USER_ID}} + ZITADEL_SERVICE_USER_TOKEN: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-service-user-token || matrix.command == 'test:acceptance:qa' && secrets.ACCEPTANCE_QA_ZITADEL_SERVICE_USER_TOKEN || secrets.ACCEPTANCE_PROD_ZITADEL_SERVICE_USER_TOKEN}} - name: Check id: check From 6600ca214797ce701468a996018e823421667057 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 14:32:21 +0100 Subject: [PATCH 511/640] cleanup --- .github/workflows/test.yml | 50 ++++++++------------------------------ 1 file changed, 10 insertions(+), 40 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b55f565bac..a385305fd0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,27 +3,17 @@ name: Quality on: pull_request: schedule: - # All 1 minutes - - cron: '* * * * *' + # Every morning at 6:00 AM CET + - cron: '0 4 * * *' workflow_dispatch: inputs: - zitadel-env: - description: 'ZITADEL ENVIRONMENT' + target-env: + description: 'Zitadel target environment to run the acceptance tests against.' required: true type: choice options: - 'qa' - 'prod' - - 'custom' - zitadel-api-url: - description: 'ZITADEL API URL' - required: false - zitadel-service-user-id: - description: 'ZITADEL SERVICE USER ID' - required: false - zitadel-service-user-token: - description: 'ZITADEL SERVICE USER TOKEN' - required: false jobs: matrix: @@ -38,27 +28,11 @@ jobs: run: | if [ -n "${{ github.event.schedule }}" ]; then echo 'matrix=["test:acceptance:qa", "test:acceptance:prod"]' >> $GITHUB_OUTPUT - elif [ -n "${{ github.event.inputs.zitadel-env }}" ]; then - echo 'matrix=["test:acceptance:${{ github.event.inputs.zitadel-env }}"]' >> $GITHUB_OUTPUT - elif [ -n "${{ github.event.inputs.zitadel-api-url }}" ]; then - echo 'matrix=["test:acceptance:custom"]' >> $GITHUB_OUTPUT + elif [ -n "${{ github.event.inputs.target-env }}" ]; then + echo 'matrix=["test:acceptance:${{ github.event.inputs.target-env }}"]' >> $GITHUB_OUTPUT else echo 'matrix=["format --check", "lint", "test:unit", "test:integration", "test:acceptance"]' >> $GITHUB_OUTPUT fi - - name: Show Matrix - run: echo '${{ steps.matrix.outputs.matrix }}' - - debug: - name: Debug matrix - runs-on: ubuntu-latest - needs: - - matrix - steps: - - name: Debug matrix - run: echo ${{ needs.matrix.outputs.matrix }} - - name: Debug fromJson - run: echo ${{ fromJson(needs.matrix.outputs.matrix) }} - quality: name: Ensure Quality @@ -134,17 +108,13 @@ jobs: run: ZITADEL_DEV_UID=root pnpm run-zitadel if: ${{ matrix.command == 'test:acceptance' }} - - name: Create Production Build - run: pnpm build - if: ${{ matrix.command == 'test:acceptance' }} + - name: Create Cloud Env File + run: echo "${{ matrix.command == 'test:acceptance:prod' && secrets.ENV_FILE_CONTENT_ACCEPTANCE_PROD || secrets.ENV_FILE_CONTENT_ACCEPTANCE_QA }}" >> apps/login/.env.local + if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' }} - name: Create Production Build run: pnpm build - if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' || matrix.command == 'test:acceptance:custom' }} - env: - ZITADEL_API_URL: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-api-url || matrix.command == 'test:acceptance:qa' && secrets.ACCEPTANCE_QA_ZITADEL_API_URL || secrets.ACCEPTANCE_PROD_ZITADEL_API_URL }} - ZITADEL_SERVICE_USER_ID: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-service-user-id || matrix.command == 'test:acceptance:qa' && secrets.ACCEPTANCE_QA_ZITADEL_SERVICE_USER_ID || secrets.ACCEPTANCE_PROD_ZITADEL_SERVICE_USER_ID}} - ZITADEL_SERVICE_USER_TOKEN: ${{ matrix.command == 'test:acceptance:custom' && github.event.inputs.zitadel-service-user-token || matrix.command == 'test:acceptance:qa' && secrets.ACCEPTANCE_QA_ZITADEL_SERVICE_USER_TOKEN || secrets.ACCEPTANCE_PROD_ZITADEL_SERVICE_USER_TOKEN}} + if: ${{ startsWith(matrix.command, 'test:acceptance') }} - name: Check id: check From 30f4347575fcaada25752f59f0f7936d5a4b8c78 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 14:36:18 +0100 Subject: [PATCH 512/640] overwritable image --- acceptance/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index 4ba64880f3..b8d74b8057 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -1,7 +1,7 @@ services: zitadel: user: "${ZITADEL_DEV_UID}" - image: ghcr.io/zitadel/zitadel:v2.65.0 + image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:v2.65.0}" command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled --config /zitadel.yaml --steps /zitadel.yaml' ports: - "8080:8080" From c7cfbb6eacbaf06437c279644bbcece5db2cefe4 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 14:49:40 +0100 Subject: [PATCH 513/640] debug --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a385305fd0..085ef8ef19 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -109,7 +109,11 @@ jobs: if: ${{ matrix.command == 'test:acceptance' }} - name: Create Cloud Env File - run: echo "${{ matrix.command == 'test:acceptance:prod' && secrets.ENV_FILE_CONTENT_ACCEPTANCE_PROD || secrets.ENV_FILE_CONTENT_ACCEPTANCE_QA }}" >> apps/login/.env.local + run: echo "${{ matrix.command == 'test:acceptance:prod' && secrets.ENV_FILE_CONTENT_ACCEPTANCE_PROD || secrets.ENV_FILE_CONTENT_ACCEPTANCE_QA }}" > apps/login/.env.local + if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' }} + + - name: DEBUG + run: head -c 25 apps/login/.env.local if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' }} - name: Create Production Build From 9b3765098f6271c1281af8ab98ecf8105b5a7b0e Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 14:55:29 +0100 Subject: [PATCH 514/640] debug --- .github/workflows/test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 085ef8ef19..6fd60cb703 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -113,7 +113,9 @@ jobs: if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' }} - name: DEBUG - run: head -c 25 apps/login/.env.local + run: | + head -c 54 apps/login/.env.local + wc -l apps/login/.env.local if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' }} - name: Create Production Build From 01fc92e307a566d0dc93600ebf4a40c3aacd9d93 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 15:25:46 +0100 Subject: [PATCH 515/640] dont build --- playwright.config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/playwright.config.ts b/playwright.config.ts index 0ca27fe1ed..f7dc3a77cb 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -72,10 +72,13 @@ export default defineConfig({ ], /* Run local dev server before starting the tests */ + /* webServer: { command: "pnpm start:built", url: "http://127.0.0.1:3000", reuseExistingServer: !process.env.CI, timeout: 5 * 60_000, }, + + */ }); From d887493e946e309bdb4218e1be47bb9a5534398c Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 15:34:01 +0100 Subject: [PATCH 516/640] tee --- .github/workflows/test.yml | 8 +------- playwright.config.ts | 4 +--- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6fd60cb703..6872502afc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -109,13 +109,7 @@ jobs: if: ${{ matrix.command == 'test:acceptance' }} - name: Create Cloud Env File - run: echo "${{ matrix.command == 'test:acceptance:prod' && secrets.ENV_FILE_CONTENT_ACCEPTANCE_PROD || secrets.ENV_FILE_CONTENT_ACCEPTANCE_QA }}" > apps/login/.env.local - if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' }} - - - name: DEBUG - run: | - head -c 54 apps/login/.env.local - wc -l apps/login/.env.local + run: echo "${{ matrix.command == 'test:acceptance:prod' && secrets.ENV_FILE_CONTENT_ACCEPTANCE_PROD || secrets.ENV_FILE_CONTENT_ACCEPTANCE_QA }}" | tee apps/login/.env.local acceptance/tests/.env.local > /dev/null if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' }} - name: Create Production Build diff --git a/playwright.config.ts b/playwright.config.ts index f7dc3a77cb..2d82212a30 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -72,13 +72,11 @@ export default defineConfig({ ], /* Run local dev server before starting the tests */ - /* + webServer: { command: "pnpm start:built", url: "http://127.0.0.1:3000", reuseExistingServer: !process.env.CI, timeout: 5 * 60_000, }, - - */ }); From 6c7a1b9732c42055442d6542a43a836b002d0bd5 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 27 Nov 2024 15:48:58 +0100 Subject: [PATCH 517/640] lint --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index 2d82212a30..342a302461 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -72,7 +72,7 @@ export default defineConfig({ ], /* Run local dev server before starting the tests */ - + webServer: { command: "pnpm start:built", url: "http://127.0.0.1:3000", From f0d9d3b4291834ecbc5bf259b549778f0428e183 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 28 Nov 2024 09:57:20 +0100 Subject: [PATCH 518/640] chore: add totp acceptance tests --- acceptance/tests/login.ts | 8 +- acceptance/tests/user.ts | 94 ++---- .../tests/username-password-otp_sms.spec.ts | 1 - .../tests/username-password-totp.spec.ts | 38 ++- acceptance/tests/zitadel.ts | 105 ++++++- package.json | 3 + pnpm-lock.yaml | 295 ++++++++++-------- 7 files changed, 330 insertions(+), 214 deletions(-) diff --git a/acceptance/tests/login.ts b/acceptance/tests/login.ts index 9af0ecfb76..32c0007a3c 100644 --- a/acceptance/tests/login.ts +++ b/acceptance/tests/login.ts @@ -1,7 +1,8 @@ import { expect, Page } from "@playwright/test"; -import { otpFromSink } from "./code"; +import { code, otpFromSink } from "./code"; import { loginname } from "./loginname"; import { password } from "./password"; +import { totp } from "./zitadel"; export async function startLogin(page: Page) { await page.goto("/loginname"); @@ -33,3 +34,8 @@ export async function loginWithPasswordAndPhoneOTP(page: Page, username: string, await loginWithPassword(page, username, password); await otpFromSink(page, phone); } + +export async function loginWithPasswordAndTOTP(page: Page, username: string, password: string, secret: string) { + await loginWithPassword(page, username, password); + await code(page, totp(secret)); +} diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index 9b2bfb46d5..d8a967aed6 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -1,7 +1,6 @@ import { Page } from "@playwright/test"; -import axios from "axios"; import { registerWithPasskey } from "./register"; -import { getUserByUsername, removeUser } from "./zitadel"; +import { activateOTP, addTOTP, addUser, getUserByUsername, removeUser } from "./zitadel"; export interface userProps { email: string; @@ -21,47 +20,9 @@ class User { } async ensure(page: Page) { - const body = { - username: this.props.email, - organization: { - orgId: this.props.organization, - }, - profile: { - givenName: this.props.firstName, - familyName: this.props.lastName, - }, - email: { - email: this.props.email, - isVerified: true, - }, - phone: { - phone: this.props.phone!, - isVerified: true, - }, - password: { - password: this.props.password!, - }, - }; - - try { - const response = await axios.post(`${process.env.ZITADEL_API_URL}/v2/users/human`, body, { - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, - }, - }); - - if (response.status >= 400) { - const error = `HTTP Error: ${response.status} - ${response.statusText}`; - console.error(error); - throw new Error(error); - } - this.setUserId(response.data.userId); - } catch (error) { - console.error("Error making request:", error); - throw error; - } + const response = await addUser(this.props); + this.setUserId(response.userId); // wait for projection of user await page.waitForTimeout(2000); } @@ -142,43 +103,30 @@ export class PasswordUserWithOTP extends User { async ensure(page: Page) { await super.ensure(page); - let url = "otp_"; - switch (this.type) { - case OtpType.sms: - url = url + "sms"; - break; - case OtpType.email: - url = url + "email"; - break; - } - - try { - const response = await axios.post( - `${process.env.ZITADEL_API_URL}/v2/users/${this.getUserId()}/${url}`, - {}, - { - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, - }, - }, - ); - - if (response.status >= 400) { - const error = `HTTP Error: ${response.status} - ${response.statusText}`; - console.error(error); - throw new Error(error); - } - } catch (error) { - console.error("Error making request:", error); - throw error; - } + await activateOTP(this.getUserId(), this.type); // wait for projection of user await page.waitForTimeout(2000); } } +export class PasswordUserWithTOTP extends User { + private secret: string; + + async ensure(page: Page) { + await super.ensure(page); + + this.secret = await addTOTP(this.getUserId()); + + // wait for projection of user + await page.waitForTimeout(2000); + } + + public getSecret(): string { + return this.secret; + } +} + export interface passkeyUserProps { email: string; firstName: string; diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index 12cdc6c740..86220fd2d6 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -48,7 +48,6 @@ test("username, password and sms otp login, resend code", async ({ user, page }) // User clicks resend code // User receives a new sms with a verification code // User is redirected to the app (default redirect url) - await loginWithPassword(page, user.getUsername(), user.getPassword()); await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone()); await loginScreenExpect(page, user.getFullName()); }); diff --git a/acceptance/tests/username-password-totp.spec.ts b/acceptance/tests/username-password-totp.spec.ts index 8fc00f9fee..33b1d9c09f 100644 --- a/acceptance/tests/username-password-totp.spec.ts +++ b/acceptance/tests/username-password-totp.spec.ts @@ -1,6 +1,32 @@ -import { test } from "@playwright/test"; +import { faker } from "@faker-js/faker"; +import { test as base } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { code } from "./code"; +import { codeScreenExpect } from "./code-screen"; +import { loginScreenExpect, loginWithPassword, loginWithPasswordAndTOTP } from "./login"; +import { PasswordUserWithTOTP } from "./user"; -test("username, password and totp login", async ({ page }) => { +// Read from ".env" file. +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); + +const test = base.extend<{ user: PasswordUserWithTOTP; sink: any }>({ + user: async ({ page }, use) => { + const user = new PasswordUserWithTOTP({ + email: faker.internet.email(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + organization: "", + phone: faker.phone.number({ style: "international" }), + password: "Password1!", + }); + + await user.ensure(page); + await use(user); + }, +}); + +test("username, password and totp login", async ({ user, page }) => { // Given totp is enabled on the organization of the user // Given the user has only totp configured as second factor // User enters username @@ -8,9 +34,11 @@ test("username, password and totp login", async ({ page }) => { // Screen for entering the code is shown directly // User enters the code into the ui // User is redirected to the app (default redirect url) + await loginWithPasswordAndTOTP(page, user.getUsername(), user.getPassword(), user.getSecret()); + await loginScreenExpect(page, user.getFullName()); }); -test("username, password and totp otp login, wrong code", async ({ page }) => { +test("username, password and totp otp login, wrong code", async ({ user, page }) => { // Given totp is enabled on the organization of the user // Given the user has only totp configured as second factor // User enters username @@ -18,6 +46,10 @@ test("username, password and totp otp login, wrong code", async ({ page }) => { // Screen for entering the code is shown directly // User enters a wrond code // Error message - "Invalid code" is shown + const c = "wrongcode"; + await loginWithPassword(page, user.getUsername(), user.getPassword()); + await code(page, c); + await codeScreenExpect(page, c); }); test("username, password and totp login, multiple mfa options", async ({ page }) => { diff --git a/acceptance/tests/zitadel.ts b/acceptance/tests/zitadel.ts index aa0c03ffdf..587723f10d 100644 --- a/acceptance/tests/zitadel.ts +++ b/acceptance/tests/zitadel.ts @@ -1,4 +1,34 @@ +import { Authenticator } from "@otplib/core"; +import { createDigest, createRandomBytes } from "@otplib/plugin-crypto"; +import { keyDecoder, keyEncoder } from "@otplib/plugin-thirty-two"; // use your chosen base32 plugin import axios from "axios"; +import { OtpType, userProps } from "./user"; + +export async function addUser(props: userProps) { + const body = { + username: props.email, + organization: { + orgId: props.organization, + }, + profile: { + givenName: props.firstName, + familyName: props.lastName, + }, + email: { + email: props.email, + isVerified: true, + }, + phone: { + phone: props.phone!, + isVerified: true, + }, + password: { + password: props.password!, + }, + }; + + return await listCall(`${process.env.ZITADEL_API_URL}/v2/users/human`, body); +} export async function removeUserByUsername(username: string) { const resp = await getUserByUsername(username); @@ -9,8 +39,12 @@ export async function removeUserByUsername(username: string) { } export async function removeUser(id: string) { + await deleteCall(`${process.env.ZITADEL_API_URL}/v2/users/${id}`); +} + +async function deleteCall(url: string) { try { - const response = await axios.delete(`${process.env.ZITADEL_API_URL}/v2/users/${id}`, { + const response = await axios.delete(url, { headers: { Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, }, @@ -27,7 +61,7 @@ export async function removeUser(id: string) { } } -export async function getUserByUsername(username: string) { +export async function getUserByUsername(username: string): Promise { const listUsersBody = { queries: [ { @@ -38,8 +72,12 @@ export async function getUserByUsername(username: string) { ], }; + return await listCall(`${process.env.ZITADEL_API_URL}/v2/users`, listUsersBody); +} + +async function listCall(url: string, data: any): Promise { try { - const response = await axios.post(`${process.env.ZITADEL_API_URL}/v2/users`, listUsersBody, { + const response = await axios.post(url, data, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, @@ -58,3 +96,64 @@ export async function getUserByUsername(username: string) { throw error; } } + +export async function activateOTP(userId: string, type: OtpType) { + let url = "otp_"; + switch (type) { + case OtpType.sms: + url = url + "sms"; + break; + case OtpType.email: + url = url + "email"; + break; + } + + await pushCall(`${process.env.ZITADEL_API_URL}/v2/users/${userId}/${url}`, {}); +} + +async function pushCall(url: string, data: any) { + try { + const response = await axios.post(url, data, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, + }, + }); + + if (response.status >= 400) { + const error = `HTTP Error: ${response.status} - ${response.statusText}`; + console.error(error); + throw new Error(error); + } + } catch (error) { + console.error("Error making request:", error); + throw error; + } +} + +export async function addTOTP(userId: string): Promise { + const response = await listCall(`${process.env.ZITADEL_API_URL}/v2/users/${userId}/totp`, {}); + const code = totp(response.secret); + await pushCall(`${process.env.ZITADEL_API_URL}/v2/users/${userId}/totp/verify`, { code: code }); + return response.secret; +} + +export function totp(secret: string) { + const authenticator = new Authenticator({ + createDigest, + createRandomBytes, + keyDecoder, + keyEncoder, + }); + // google authenticator usage + const token = authenticator.generate(secret); + + // check if token can be used + if (!authenticator.verify({ token: token, secret: secret })) { + const error = `Generated token could not be verified`; + console.error(error); + throw new Error(error); + } + + return token; +} diff --git a/package.json b/package.json index 438380b2ff..c2777dc9cd 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,9 @@ } }, "devDependencies": { + "@otplib/core": "^12.0.0", + "@otplib/plugin-thirty-two": "^12.0.0", + "@otplib/plugin-crypto": "^12.0.0", "@faker-js/faker": "^9.2.0", "@changesets/cli": "^2.27.9", "@playwright/test": "^1.48.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5276526bd1..fbb4e116ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,15 @@ importers: '@faker-js/faker': specifier: ^9.2.0 version: 9.2.0 + '@otplib/core': + specifier: ^12.0.0 + version: 12.0.1 + '@otplib/plugin-crypto': + specifier: ^12.0.0 + version: 12.0.1 + '@otplib/plugin-thirty-two': + specifier: ^12.0.0 + version: 12.0.1 '@playwright/test': specifier: ^1.48.2 version: 1.48.2 @@ -415,79 +424,79 @@ packages: engines: {node: '>=6.9.0'} '@bufbuild/buf-darwin-arm64@1.46.0': - resolution: {integrity: sha512-lSmTKyRhg+71acXp9QeX/wm+vjkf0J3n38wph7KOwMfCEeK4A2AkqsGOkoXSiaIvidA2pRU9RJRQYfryzCA9Pg==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.46.0.tgz} + resolution: {integrity: sha512-lSmTKyRhg+71acXp9QeX/wm+vjkf0J3n38wph7KOwMfCEeK4A2AkqsGOkoXSiaIvidA2pRU9RJRQYfryzCA9Pg==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@bufbuild/buf-darwin-arm64@1.47.2': - resolution: {integrity: sha512-74WerFn06y+azgVfsnzhfbI5wla/OLPDnIvaNJBWHaqya/3bfascJkDylW2GVNHmwG1K/cscpmcc/RJPaO7ntQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.47.2.tgz} + resolution: {integrity: sha512-74WerFn06y+azgVfsnzhfbI5wla/OLPDnIvaNJBWHaqya/3bfascJkDylW2GVNHmwG1K/cscpmcc/RJPaO7ntQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@bufbuild/buf-darwin-x64@1.46.0': - resolution: {integrity: sha512-Oa9XTLJshsEjzowyt2mH9XrXW38DRFdz7ml+IYKXVQPotNLr04ix7QES7A1eOBJtxLwuTiri4ScXuBLQGNX8+A==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.46.0.tgz} + resolution: {integrity: sha512-Oa9XTLJshsEjzowyt2mH9XrXW38DRFdz7ml+IYKXVQPotNLr04ix7QES7A1eOBJtxLwuTiri4ScXuBLQGNX8+A==} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@bufbuild/buf-darwin-x64@1.47.2': - resolution: {integrity: sha512-adAiOacOQe8Ym/YXPCEiq9mrPeKRmDtF2TgqPWTcDy6mF7TqR7hMJINkEEuMd1EeACmXnzMOnXlm9ICtvdYgPg==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.47.2.tgz} + resolution: {integrity: sha512-adAiOacOQe8Ym/YXPCEiq9mrPeKRmDtF2TgqPWTcDy6mF7TqR7hMJINkEEuMd1EeACmXnzMOnXlm9ICtvdYgPg==} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@bufbuild/buf-linux-aarch64@1.46.0': - resolution: {integrity: sha512-CbxbLH5sQCRjEKVEcWJySvCKyAPAUhX0vCTifT/eQyZ70FUsqCJKJ6+dKl6Ajk0CgUHqf8jkU/wX/+aQFYXyaA==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.46.0.tgz} + resolution: {integrity: sha512-CbxbLH5sQCRjEKVEcWJySvCKyAPAUhX0vCTifT/eQyZ70FUsqCJKJ6+dKl6Ajk0CgUHqf8jkU/wX/+aQFYXyaA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@bufbuild/buf-linux-aarch64@1.47.2': - resolution: {integrity: sha512-52vY+Owffr5diw2PyfQJqH+Fld6zW6NhNZak4zojvc2MjZKubWM0TfNyM9jXz2YrwyB+cyxkabE60nBI80m37w==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.47.2.tgz} + resolution: {integrity: sha512-52vY+Owffr5diw2PyfQJqH+Fld6zW6NhNZak4zojvc2MjZKubWM0TfNyM9jXz2YrwyB+cyxkabE60nBI80m37w==} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@bufbuild/buf-linux-armv7@1.47.2': - resolution: {integrity: sha512-g9KtpObDeHZ/VG/0b5ZCieOao7L/WYZ0fPqFSs4N07D3APgEDhJG6vLyUcDgJMDgyLcgkNjNz0+XdYQb/tXyQw==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-armv7/-/buf-linux-armv7-1.47.2.tgz} + resolution: {integrity: sha512-g9KtpObDeHZ/VG/0b5ZCieOao7L/WYZ0fPqFSs4N07D3APgEDhJG6vLyUcDgJMDgyLcgkNjNz0+XdYQb/tXyQw==} engines: {node: '>=12'} cpu: [arm] os: [linux] '@bufbuild/buf-linux-x64@1.46.0': - resolution: {integrity: sha512-bMqp+Q+16KPbuwX34/OLDeiimnwt5sfvHqyeMeRz4LLwLshbmM3m+8dGCSHZRo3Lr+4gW1PfunrfaEmcGqPHLQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.46.0.tgz} + resolution: {integrity: sha512-bMqp+Q+16KPbuwX34/OLDeiimnwt5sfvHqyeMeRz4LLwLshbmM3m+8dGCSHZRo3Lr+4gW1PfunrfaEmcGqPHLQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] '@bufbuild/buf-linux-x64@1.47.2': - resolution: {integrity: sha512-MODCK2BzD1Mgoyr+5Sp8xA8qMNdytj8hYheyhA5NnCGTkQf8sfqAjpBSAAmKk6Zar8HOlVXML6tzE/ioDFFGwQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.47.2.tgz} + resolution: {integrity: sha512-MODCK2BzD1Mgoyr+5Sp8xA8qMNdytj8hYheyhA5NnCGTkQf8sfqAjpBSAAmKk6Zar8HOlVXML6tzE/ioDFFGwQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] '@bufbuild/buf-win32-arm64@1.46.0': - resolution: {integrity: sha512-geVYXp1PWJiAAFpwhgP8Cnct0+Rdr89BF/WZoIh5WwFGYITGiu5Hb1Ui9DTrEYwDzahPCyPxgIVwzzW6kPWSag==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.46.0.tgz} + resolution: {integrity: sha512-geVYXp1PWJiAAFpwhgP8Cnct0+Rdr89BF/WZoIh5WwFGYITGiu5Hb1Ui9DTrEYwDzahPCyPxgIVwzzW6kPWSag==} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@bufbuild/buf-win32-arm64@1.47.2': - resolution: {integrity: sha512-563YKYWJl3LrCY3G3+zuhb8HwOs6DzWslwGPFkKV2hwHyWyvd1DR1JjiLvw9zX64IKNctQ0HempSqc3kcboaqQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.47.2.tgz} + resolution: {integrity: sha512-563YKYWJl3LrCY3G3+zuhb8HwOs6DzWslwGPFkKV2hwHyWyvd1DR1JjiLvw9zX64IKNctQ0HempSqc3kcboaqQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@bufbuild/buf-win32-x64@1.46.0': - resolution: {integrity: sha512-6nsxkzj5a1L41NOJFKjli8j6GB/NkPHLIr0T/b27Y3GfprVYQawOComYD5HfojvBLuAiE2cD/kEQIWKK1YRcng==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.46.0.tgz} + resolution: {integrity: sha512-6nsxkzj5a1L41NOJFKjli8j6GB/NkPHLIr0T/b27Y3GfprVYQawOComYD5HfojvBLuAiE2cD/kEQIWKK1YRcng==} engines: {node: '>=12'} cpu: [x64] os: [win32] '@bufbuild/buf-win32-x64@1.47.2': - resolution: {integrity: sha512-Sqcdv7La2xBDh3bTdEYb2f4UTMMqCcYe/D0RELhvQ5wDn6I35V3/2YT1OF5fRuf0BZLCo0OdO37S9L47uHSz2g==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.47.2.tgz} + resolution: {integrity: sha512-Sqcdv7La2xBDh3bTdEYb2f4UTMMqCcYe/D0RELhvQ5wDn6I35V3/2YT1OF5fRuf0BZLCo0OdO37S9L47uHSz2g==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -498,7 +507,7 @@ packages: hasBin: true '@bufbuild/buf@1.47.2': - resolution: {integrity: sha512-glY5kCAoO4+a7HvDb+BLOdoHSdCk4mdXdkp53H8JFz7maOnkxCiHHXgRX+taFyEu25N8ybn7NjZFrZSdRwq2sA==, tarball: https://registry.npmjs.org/@bufbuild/buf/-/buf-1.47.2.tgz} + resolution: {integrity: sha512-glY5kCAoO4+a7HvDb+BLOdoHSdCk4mdXdkp53H8JFz7maOnkxCiHHXgRX+taFyEu25N8ybn7NjZFrZSdRwq2sA==} engines: {node: '>=12'} hasBin: true @@ -506,7 +515,7 @@ packages: resolution: {integrity: sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ==} '@bufbuild/protobuf@2.2.2': - resolution: {integrity: sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==, tarball: https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.2.tgz} + resolution: {integrity: sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==} '@bufbuild/protocompile@0.0.1': resolution: {integrity: sha512-cOTMtjcWLcbjF17dPYgeMtVC5jZyS0bSjz3jy8kDPjOgjgSYMD2u2It7w8aCc2z23hTPIKl/2SNdMnz0Jzu3Xg==} @@ -569,24 +578,24 @@ packages: resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==, tarball: https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz} + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} '@connectrpc/connect-node@2.0.0': - resolution: {integrity: sha512-DoI5T+SUvlS/8QBsxt2iDoUg15dSxqhckegrgZpWOtADtmGohBIVbx1UjtWmjLBrP4RdD0FeBw+XyRUSbpKnJQ==, tarball: https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-2.0.0.tgz} + resolution: {integrity: sha512-DoI5T+SUvlS/8QBsxt2iDoUg15dSxqhckegrgZpWOtADtmGohBIVbx1UjtWmjLBrP4RdD0FeBw+XyRUSbpKnJQ==} engines: {node: '>=18.14.1'} peerDependencies: '@bufbuild/protobuf': ^2.2.0 '@connectrpc/connect': 2.0.0 '@connectrpc/connect-web@2.0.0': - resolution: {integrity: sha512-oeCxqHXLXlWJdmcvp9L3scgAuK+FjNSn+twyhUxc8yvDbTumnt5Io+LnBzSYxAdUdYqTw5yHfTSCJ4hj0QID0g==, tarball: https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-2.0.0.tgz} + resolution: {integrity: sha512-oeCxqHXLXlWJdmcvp9L3scgAuK+FjNSn+twyhUxc8yvDbTumnt5Io+LnBzSYxAdUdYqTw5yHfTSCJ4hj0QID0g==} peerDependencies: '@bufbuild/protobuf': ^2.2.0 '@connectrpc/connect': 2.0.0 '@connectrpc/connect@2.0.0': - resolution: {integrity: sha512-Usm8jgaaULANJU8vVnhWssSA6nrZ4DJEAbkNtXSoZay2YD5fDyMukCxu8NEhCvFzfHvrhxhcjttvgpyhOM7xAQ==, tarball: https://registry.npmjs.org/@connectrpc/connect/-/connect-2.0.0.tgz} + resolution: {integrity: sha512-Usm8jgaaULANJU8vVnhWssSA6nrZ4DJEAbkNtXSoZay2YD5fDyMukCxu8NEhCvFzfHvrhxhcjttvgpyhOM7xAQ==} peerDependencies: '@bufbuild/protobuf': ^2.2.0 @@ -598,283 +607,283 @@ packages: resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz} + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] '@esbuild/aix-ppc64@0.24.0': - resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz} + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz} + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] '@esbuild/android-arm64@0.24.0': - resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz} + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} engines: {node: '>=18'} cpu: [arm64] os: [android] '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz} + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] '@esbuild/android-arm@0.24.0': - resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz} + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} engines: {node: '>=18'} cpu: [arm] os: [android] '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz} + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] '@esbuild/android-x64@0.24.0': - resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz} + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} engines: {node: '>=18'} cpu: [x64] os: [android] '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz} + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@esbuild/darwin-arm64@0.24.0': - resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz} + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz} + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@esbuild/darwin-x64@0.24.0': - resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz} + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz} + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-arm64@0.24.0': - resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz} + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] '@esbuild/freebsd-x64@0.24.0': - resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz} + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz} + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@esbuild/linux-arm64@0.24.0': - resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz} + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz} + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] '@esbuild/linux-arm@0.24.0': - resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz} + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} engines: {node: '>=18'} cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz} + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] '@esbuild/linux-ia32@0.24.0': - resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz} + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz} + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] '@esbuild/linux-loong64@0.24.0': - resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz} + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} engines: {node: '>=18'} cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz} + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] '@esbuild/linux-mips64el@0.24.0': - resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz} + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz} + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] '@esbuild/linux-ppc64@0.24.0': - resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz} + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz} + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] '@esbuild/linux-riscv64@0.24.0': - resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz} + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz} + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] '@esbuild/linux-s390x@0.24.0': - resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz} + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} engines: {node: '>=18'} cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz} + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] '@esbuild/linux-x64@0.24.0': - resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz} + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} engines: {node: '>=18'} cpu: [x64] os: [linux] '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] '@esbuild/netbsd-x64@0.24.0': - resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz} + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] '@esbuild/openbsd-arm64@0.24.0': - resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz} + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] '@esbuild/openbsd-x64@0.24.0': - resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz} + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz} + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] '@esbuild/sunos-x64@0.24.0': - resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz} + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz} + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@esbuild/win32-arm64@0.24.0': - resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz} + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz} + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] '@esbuild/win32-ia32@0.24.0': - resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz} + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz} + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] '@esbuild/win32-x64@0.24.0': - resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz} + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -901,10 +910,6 @@ packages: resolution: {integrity: sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==} engines: {node: '>=18.0.0', npm: '>=9.0.0'} - '@fastify/busboy@2.1.1': - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} - '@floating-ui/core@1.6.8': resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} @@ -1023,55 +1028,55 @@ packages: resolution: {integrity: sha512-+7xh142AdhZGjY9/L0iFo7mqRBMJHe+q+uOL+hto1Lfo9DeWCGcR6no4StlFbVSVcA6fQLKEX6y6qhMsSKbgNQ==} '@next/swc-darwin-arm64@14.2.14': - resolution: {integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==, tarball: https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.14.tgz} + resolution: {integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] '@next/swc-darwin-x64@14.2.14': - resolution: {integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==, tarball: https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.14.tgz} + resolution: {integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] '@next/swc-linux-arm64-gnu@14.2.14': - resolution: {integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==, tarball: https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.14.tgz} + resolution: {integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] '@next/swc-linux-arm64-musl@14.2.14': - resolution: {integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==, tarball: https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.14.tgz} + resolution: {integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] '@next/swc-linux-x64-gnu@14.2.14': - resolution: {integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==, tarball: https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.14.tgz} + resolution: {integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] '@next/swc-linux-x64-musl@14.2.14': - resolution: {integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==, tarball: https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.14.tgz} + resolution: {integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] '@next/swc-win32-arm64-msvc@14.2.14': - resolution: {integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==, tarball: https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.14.tgz} + resolution: {integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] '@next/swc-win32-ia32-msvc@14.2.14': - resolution: {integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==, tarball: https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.14.tgz} + resolution: {integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] '@next/swc-win32-x64-msvc@14.2.14': - resolution: {integrity: sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg==, tarball: https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.14.tgz} + resolution: {integrity: sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1092,90 +1097,99 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@otplib/core@12.0.1': + resolution: {integrity: sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==} + + '@otplib/plugin-crypto@12.0.1': + resolution: {integrity: sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==} + + '@otplib/plugin-thirty-two@12.0.1': + resolution: {integrity: sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==} + '@parcel/watcher-android-arm64@2.5.0': - resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==, tarball: https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz} + resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [android] '@parcel/watcher-darwin-arm64@2.5.0': - resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==, tarball: https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz} + resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [darwin] '@parcel/watcher-darwin-x64@2.5.0': - resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==, tarball: https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz} + resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [darwin] '@parcel/watcher-freebsd-x64@2.5.0': - resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==, tarball: https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz} + resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [freebsd] '@parcel/watcher-linux-arm-glibc@2.5.0': - resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz} + resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] '@parcel/watcher-linux-arm-musl@2.5.0': - resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz} + resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] '@parcel/watcher-linux-arm64-glibc@2.5.0': - resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz} + resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] '@parcel/watcher-linux-arm64-musl@2.5.0': - resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz} + resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] '@parcel/watcher-linux-x64-glibc@2.5.0': - resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz} + resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] '@parcel/watcher-linux-x64-musl@2.5.0': - resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz} + resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] '@parcel/watcher-win32-arm64@2.5.0': - resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==, tarball: https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz} + resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [win32] '@parcel/watcher-win32-ia32@2.5.0': - resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==, tarball: https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz} + resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==} engines: {node: '>= 10.0.0'} cpu: [ia32] os: [win32] '@parcel/watcher-win32-x64@2.5.0': - resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==, tarball: https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz} + resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [win32] '@parcel/watcher@2.5.0': - resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==, tarball: https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz} + resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==} engines: {node: '>= 10.0.0'} '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, tarball: https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz} + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} '@playwright/test@1.48.2': @@ -1245,92 +1259,92 @@ packages: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 '@rollup/rollup-android-arm-eabi@4.25.0': - resolution: {integrity: sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.25.0.tgz} + resolution: {integrity: sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==} cpu: [arm] os: [android] '@rollup/rollup-android-arm64@4.25.0': - resolution: {integrity: sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.25.0.tgz} + resolution: {integrity: sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==} cpu: [arm64] os: [android] '@rollup/rollup-darwin-arm64@4.25.0': - resolution: {integrity: sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.25.0.tgz} + resolution: {integrity: sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==} cpu: [arm64] os: [darwin] '@rollup/rollup-darwin-x64@4.25.0': - resolution: {integrity: sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.25.0.tgz} + resolution: {integrity: sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==} cpu: [x64] os: [darwin] '@rollup/rollup-freebsd-arm64@4.25.0': - resolution: {integrity: sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.25.0.tgz} + resolution: {integrity: sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==} cpu: [arm64] os: [freebsd] '@rollup/rollup-freebsd-x64@4.25.0': - resolution: {integrity: sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.25.0.tgz} + resolution: {integrity: sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==} cpu: [x64] os: [freebsd] '@rollup/rollup-linux-arm-gnueabihf@4.25.0': - resolution: {integrity: sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.25.0.tgz} + resolution: {integrity: sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==} cpu: [arm] os: [linux] '@rollup/rollup-linux-arm-musleabihf@4.25.0': - resolution: {integrity: sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.25.0.tgz} + resolution: {integrity: sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==} cpu: [arm] os: [linux] '@rollup/rollup-linux-arm64-gnu@4.25.0': - resolution: {integrity: sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.25.0.tgz} + resolution: {integrity: sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==} cpu: [arm64] os: [linux] '@rollup/rollup-linux-arm64-musl@4.25.0': - resolution: {integrity: sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.25.0.tgz} + resolution: {integrity: sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==} cpu: [arm64] os: [linux] '@rollup/rollup-linux-powerpc64le-gnu@4.25.0': - resolution: {integrity: sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.25.0.tgz} + resolution: {integrity: sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==} cpu: [ppc64] os: [linux] '@rollup/rollup-linux-riscv64-gnu@4.25.0': - resolution: {integrity: sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.25.0.tgz} + resolution: {integrity: sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==} cpu: [riscv64] os: [linux] '@rollup/rollup-linux-s390x-gnu@4.25.0': - resolution: {integrity: sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.25.0.tgz} + resolution: {integrity: sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==} cpu: [s390x] os: [linux] '@rollup/rollup-linux-x64-gnu@4.25.0': - resolution: {integrity: sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.25.0.tgz} + resolution: {integrity: sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==} cpu: [x64] os: [linux] '@rollup/rollup-linux-x64-musl@4.25.0': - resolution: {integrity: sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.25.0.tgz} + resolution: {integrity: sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==} cpu: [x64] os: [linux] '@rollup/rollup-win32-arm64-msvc@4.25.0': - resolution: {integrity: sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.25.0.tgz} + resolution: {integrity: sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==} cpu: [arm64] os: [win32] '@rollup/rollup-win32-ia32-msvc@4.25.0': - resolution: {integrity: sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.25.0.tgz} + resolution: {integrity: sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==} cpu: [ia32] os: [win32] '@rollup/rollup-win32-x64-msvc@4.25.0': - resolution: {integrity: sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.25.0.tgz} + resolution: {integrity: sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==} cpu: [x64] os: [win32] @@ -1376,7 +1390,7 @@ packages: resolution: {integrity: sha512-1giLc4dzgEKLMx5pgKjL6HlG5fjZMgCjzlKAlpr7yoUtetVPELgER1NtephAI910nMwfPTHNyWKSFmJdHkz2Cw==} '@testing-library/dom@10.4.0': - resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==, tarball: https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz} + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} '@testing-library/jest-dom@6.6.3': @@ -1399,7 +1413,7 @@ packages: optional: true '@types/aria-query@5.0.4': - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==, tarball: https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz} + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1450,7 +1464,7 @@ packages: resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==, tarball: https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz} + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} '@typescript-eslint/parser@7.18.0': resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} @@ -1591,7 +1605,7 @@ packages: engines: {node: '>=8'} ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==, tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz} + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} ansi-styles@6.2.1: @@ -2122,7 +2136,7 @@ packages: engines: {node: '>=6.0.0'} dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==, tarball: https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz} + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} @@ -2528,12 +2542,12 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz} + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz} + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] @@ -3143,7 +3157,7 @@ packages: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==, tarball: https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz} + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true magic-string@0.30.12: @@ -3709,7 +3723,7 @@ packages: engines: {node: '>=6'} pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==, tarball: https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz} + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} process@0.11.10: @@ -3774,7 +3788,7 @@ packages: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==, tarball: https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz} + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} @@ -4172,6 +4186,10 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thirty-two@1.0.2: + resolution: {integrity: sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==} + engines: {node: '>=0.2.6'} + throttleit@1.0.1: resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==} @@ -4308,32 +4326,32 @@ packages: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} turbo-darwin-64@2.2.3: - resolution: {integrity: sha512-Rcm10CuMKQGcdIBS3R/9PMeuYnv6beYIHqfZFeKWVYEWH69sauj4INs83zKMTUiZJ3/hWGZ4jet9AOwhsssLyg==, tarball: https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.2.3.tgz} + resolution: {integrity: sha512-Rcm10CuMKQGcdIBS3R/9PMeuYnv6beYIHqfZFeKWVYEWH69sauj4INs83zKMTUiZJ3/hWGZ4jet9AOwhsssLyg==} cpu: [x64] os: [darwin] turbo-darwin-arm64@2.2.3: - resolution: {integrity: sha512-+EIMHkuLFqUdJYsA3roj66t9+9IciCajgj+DVek+QezEdOJKcRxlvDOS2BUaeN8kEzVSsNiAGnoysFWYw4K0HA==, tarball: https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.2.3.tgz} + resolution: {integrity: sha512-+EIMHkuLFqUdJYsA3roj66t9+9IciCajgj+DVek+QezEdOJKcRxlvDOS2BUaeN8kEzVSsNiAGnoysFWYw4K0HA==} cpu: [arm64] os: [darwin] turbo-linux-64@2.2.3: - resolution: {integrity: sha512-UBhJCYnqtaeOBQLmLo8BAisWbc9v9daL9G8upLR+XGj6vuN/Nz6qUAhverN4Pyej1g4Nt1BhROnj6GLOPYyqxQ==, tarball: https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.2.3.tgz} + resolution: {integrity: sha512-UBhJCYnqtaeOBQLmLo8BAisWbc9v9daL9G8upLR+XGj6vuN/Nz6qUAhverN4Pyej1g4Nt1BhROnj6GLOPYyqxQ==} cpu: [x64] os: [linux] turbo-linux-arm64@2.2.3: - resolution: {integrity: sha512-hJYT9dN06XCQ3jBka/EWvvAETnHRs3xuO/rb5bESmDfG+d9yQjeTMlhRXKrr4eyIMt6cLDt1LBfyi+6CQ+VAwQ==, tarball: https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.2.3.tgz} + resolution: {integrity: sha512-hJYT9dN06XCQ3jBka/EWvvAETnHRs3xuO/rb5bESmDfG+d9yQjeTMlhRXKrr4eyIMt6cLDt1LBfyi+6CQ+VAwQ==} cpu: [arm64] os: [linux] turbo-windows-64@2.2.3: - resolution: {integrity: sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==, tarball: https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.2.3.tgz} + resolution: {integrity: sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==} cpu: [x64] os: [win32] turbo-windows-arm64@2.2.3: - resolution: {integrity: sha512-fnNrYBCqn6zgKPKLHu4sOkihBI/+0oYFr075duRxqUZ+1aLWTAGfHZLgjVeLh3zR37CVzuerGIPWAEkNhkWEIw==, tarball: https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.2.3.tgz} + resolution: {integrity: sha512-fnNrYBCqn6zgKPKLHu4sOkihBI/+0oYFr075duRxqUZ+1aLWTAGfHZLgjVeLh3zR37CVzuerGIPWAEkNhkWEIw==} cpu: [arm64] os: [win32] @@ -5201,8 +5219,6 @@ snapshots: '@faker-js/faker@9.2.0': {} - '@fastify/busboy@2.1.1': {} - '@floating-ui/core@1.6.8': dependencies: '@floating-ui/utils': 0.2.8 @@ -5402,6 +5418,17 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@otplib/core@12.0.1': {} + + '@otplib/plugin-crypto@12.0.1': + dependencies: + '@otplib/core': 12.0.1 + + '@otplib/plugin-thirty-two@12.0.1': + dependencies: + '@otplib/core': 12.0.1 + thirty-two: 1.0.2 + '@parcel/watcher-android-arm64@2.5.0': optional: true @@ -6709,7 +6736,7 @@ snapshots: debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 @@ -6722,7 +6749,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: @@ -6743,7 +6770,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -8700,6 +8727,8 @@ snapshots: dependencies: any-promise: 1.3.0 + thirty-two@1.0.2: {} + throttleit@1.0.1: {} through@2.3.8: {} From 4be67c223564a6b5941af28df003291273e0622d Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 28 Nov 2024 09:59:37 +0100 Subject: [PATCH 519/640] accept local only force mfa, fix nexturl org context --- apps/login/src/components/password-form.tsx | 1 - apps/login/src/lib/client.ts | 31 ++++++++++++--------- apps/login/src/lib/server/password.ts | 6 ++-- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index 6f810ac30f..0d05bcd628 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -59,7 +59,6 @@ export function PasswordForm({ password: { password: values.password }, }), authRequestId, - forceMfa: loginSettings?.forceMfa, }) .catch(() => { setError("Could not verify password"); diff --git a/apps/login/src/lib/client.ts b/apps/login/src/lib/client.ts index 7dc2b83cbb..37d22dc83d 100644 --- a/apps/login/src/lib/client.ts +++ b/apps/login/src/lib/client.ts @@ -15,24 +15,29 @@ export async function getNextUrl( defaultRedirectUri?: string, ): Promise { if ("sessionId" in command && "authRequestId" in command) { - const url = - `/login?` + - new URLSearchParams({ - sessionId: command.sessionId, - authRequest: command.authRequestId, - }); - return url; + const params = new URLSearchParams({ + sessionId: command.sessionId, + authRequest: command.authRequestId, + }); + + if (command.organization) { + params.append("organization", command.organization); + } + + return `/login?` + params; } if (defaultRedirectUri) { return defaultRedirectUri; } - const signedInUrl = - `/signedin?` + - new URLSearchParams({ - loginName: command.loginName, - }); + const params = new URLSearchParams({ + loginName: command.loginName, + }); - return signedInUrl; + if (command.organization) { + params.append("organization", command.organization); + } + + return `/signedin?` + params; } diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 59afca50a5..32e9102913 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -54,7 +54,6 @@ export type UpdateSessionCommand = { organization?: string; checks: Checks; authRequestId?: string; - forceMfa?: boolean; }; export async function sendPassword(command: UpdateSessionCommand) { @@ -209,7 +208,10 @@ export async function sendPassword(command: UpdateSessionCommand) { } return { redirect: `/password/change?` + params }; - } else if (command.forceMfa && !availableSecondFactors.length) { + } else if ( + (loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) && + !availableSecondFactors.length + ) { const params = new URLSearchParams({ loginName: session.factors.user.loginName, force: "true", // this defines if the mfa is forced in the settings From 5d6b6b538838ef435a32fb7e4c57934145f66594 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:38:13 +0100 Subject: [PATCH 520/640] chore: cleanup and some fixes --- acceptance/sink/go.mod | 3 +++ acceptance/tests/register.spec.ts | 10 +++++-- acceptance/tests/register.ts | 4 +++ acceptance/tests/user.ts | 26 ++++++++++++------- acceptance/tests/username-passkey.spec.ts | 1 + .../tests/username-password-changed.spec.ts | 1 + .../tests/username-password-otp_email.spec.ts | 1 + .../tests/username-password-otp_sms.spec.ts | 1 + .../tests/username-password-set.spec.ts | 1 + .../tests/username-password-totp.spec.ts | 1 + acceptance/tests/username-password.spec.ts | 1 + 11 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 acceptance/sink/go.mod diff --git a/acceptance/sink/go.mod b/acceptance/sink/go.mod new file mode 100644 index 0000000000..a33d6ae8bd --- /dev/null +++ b/acceptance/sink/go.mod @@ -0,0 +1,3 @@ +module github.com/zitadel/typescript/acceptance/sink + +go 1.22.6 diff --git a/acceptance/tests/register.spec.ts b/acceptance/tests/register.spec.ts index 98e5669e9e..a3ffc7a67e 100644 --- a/acceptance/tests/register.spec.ts +++ b/acceptance/tests/register.spec.ts @@ -15,9 +15,12 @@ test("register with password", async ({ page }) => { const firstname = faker.person.firstName(); const lastname = faker.person.lastName(); - await removeUserByUsername(username); await registerWithPassword(page, firstname, lastname, username, password, password); await loginScreenExpect(page, firstname + " " + lastname); + + // wait for projection of user + await page.waitForTimeout(2000); + await removeUserByUsername(username); }); test("register with passkey", async ({ page }) => { @@ -25,9 +28,12 @@ test("register with passkey", async ({ page }) => { const firstname = faker.person.firstName(); const lastname = faker.person.lastName(); - await removeUserByUsername(username); await registerWithPasskey(page, firstname, lastname, username); await loginScreenExpect(page, firstname + " " + lastname); + + // wait for projection of user + await page.waitForTimeout(2000); + await removeUserByUsername(username); }); test("register with username and password - only password enabled", async ({ page }) => { diff --git a/acceptance/tests/register.ts b/acceptance/tests/register.ts index f943e5bacc..e250d60b12 100644 --- a/acceptance/tests/register.ts +++ b/acceptance/tests/register.ts @@ -21,5 +21,9 @@ export async function registerWithPasskey(page: Page, firstname: string, lastnam await page.goto("/register"); await registerUserScreenPasskey(page, firstname, lastname, email); await page.getByTestId("submit-button").click(); + + // wait for projection of user + await page.waitForTimeout(2000); + return await passkeyRegister(page); } diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index d8a967aed6..3daefdff08 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -23,16 +23,10 @@ class User { const response = await addUser(this.props); this.setUserId(response.userId); - // wait for projection of user - await page.waitForTimeout(2000); } - async remove() { - const resp: any = await getUserByUsername(this.getUsername()); - if (!resp || !resp.result || !resp.result[0]) { - return; - } - await removeUser(resp.result[0].userId); + async cleanup() { + await removeUser(this.getUserId()); } public setUserId(userId: string) { @@ -68,7 +62,13 @@ class User { } } -export class PasswordUser extends User {} +export class PasswordUser extends User { + async ensure(page: Page) { + await super.ensure(page); + // wait for projection of user + await page.waitForTimeout(2000); + } +} export enum OtpType { sms = "sms", @@ -157,6 +157,14 @@ export class PasskeyUser extends User { await page.waitForTimeout(2000); } + async cleanup() { + const resp: any = await getUserByUsername(this.getUsername()); + if (!resp || !resp.result || !resp.result[0]) { + return; + } + await removeUser(resp.result[0].userId); + } + public getAuthenticatorId(): string { return this.authenticatorId; } diff --git a/acceptance/tests/username-passkey.spec.ts b/acceptance/tests/username-passkey.spec.ts index 7fbf290d99..e73de3547f 100644 --- a/acceptance/tests/username-passkey.spec.ts +++ b/acceptance/tests/username-passkey.spec.ts @@ -19,6 +19,7 @@ const test = base.extend<{ user: PasskeyUser }>({ }); await user.ensure(page); await use(user); + await user.cleanup(); }, }); diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index cf3af995f7..c185e51ec9 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -22,6 +22,7 @@ const test = base.extend<{ user: PasswordUser }>({ }); await user.ensure(page); await use(user); + await user.cleanup(); }, }); diff --git a/acceptance/tests/username-password-otp_email.spec.ts b/acceptance/tests/username-password-otp_email.spec.ts index b684fa80c6..daa2e0a429 100644 --- a/acceptance/tests/username-password-otp_email.spec.ts +++ b/acceptance/tests/username-password-otp_email.spec.ts @@ -24,6 +24,7 @@ const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({ await user.ensure(page); await use(user); + await user.cleanup(); }, }); diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index 86220fd2d6..8fb91a66c7 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -24,6 +24,7 @@ const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({ await user.ensure(page); await use(user); + await user.cleanup(); }, }); diff --git a/acceptance/tests/username-password-set.spec.ts b/acceptance/tests/username-password-set.spec.ts index c2a60bd410..30d442df50 100644 --- a/acceptance/tests/username-password-set.spec.ts +++ b/acceptance/tests/username-password-set.spec.ts @@ -23,6 +23,7 @@ const test = base.extend<{ user: PasswordUser }>({ }); await user.ensure(page); await use(user); + await user.cleanup(); }, }); diff --git a/acceptance/tests/username-password-totp.spec.ts b/acceptance/tests/username-password-totp.spec.ts index 33b1d9c09f..4b6e678931 100644 --- a/acceptance/tests/username-password-totp.spec.ts +++ b/acceptance/tests/username-password-totp.spec.ts @@ -23,6 +23,7 @@ const test = base.extend<{ user: PasswordUserWithTOTP; sink: any }>({ await user.ensure(page); await use(user); + await user.cleanup(); }, }); diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index 3f6bfd48f7..fcb6aad037 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -24,6 +24,7 @@ const test = base.extend<{ user: PasswordUser }>({ }); await user.ensure(page); await use(user); + await user.cleanup(); }, }); From ba3359ff51c503ae4587b60c96bebad05ae17063 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 28 Nov 2024 16:56:46 +0100 Subject: [PATCH 521/640] otp url template, reset with authrequest --- apps/login/src/app/(login)/otp/[method]/page.tsx | 5 +++++ apps/login/src/components/login-otp.tsx | 15 ++++++++++++++- apps/login/src/components/password-form.tsx | 1 + apps/login/src/components/session-item.tsx | 2 +- apps/login/src/lib/server/password.ts | 3 ++- apps/login/src/lib/zitadel.ts | 10 ++++++++-- 6 files changed, 31 insertions(+), 5 deletions(-) diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 9c91b3edd2..1c0904cee2 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -5,6 +5,7 @@ import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -30,6 +31,8 @@ export default async function Page(props: { const loginSettings = await getLoginSettings(organization); + const host = (await headers()).get("host"); + return (
    @@ -67,6 +70,8 @@ export default async function Page(props: { organization={organization} method={method} loginSettings={loginSettings} + host={host} + code={code} > )}
    diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 27fa187c7d..262541eb1b 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -25,6 +25,7 @@ type Props = { method: string; code?: string; loginSettings?: LoginSettings; + host: string | null; }; type Inputs = { @@ -39,6 +40,7 @@ export function LoginOTP({ method, code, loginSettings, + host, }: Props) { const t = useTranslations("otp"); @@ -76,7 +78,18 @@ export function LoginOTP({ if (method === "email") { challenges = create(RequestChallengesSchema, { - otpEmail: { deliveryType: { case: "sendCode", value: {} } }, + otpEmail: { + deliveryType: { + case: "sendCode", + value: host + ? { + urlTemplate: + `${host.includes("localhost") ? "http://" : "https://"}${host}/otp/method=${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}&organization={{.OrgID}}` + + (authRequestId ? `&authRequestId=${authRequestId}` : ""), + } + : {}, + }, + }, }); } diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index 0d05bcd628..a1bc6cd941 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -86,6 +86,7 @@ export function PasswordForm({ const response = await resetPassword({ loginName, organization, + authRequestId, }) .catch(() => { setError("Could not reset password"); diff --git a/apps/login/src/components/session-item.tsx b/apps/login/src/components/session-item.tsx index 6bdab2394a..4df720b056 100644 --- a/apps/login/src/components/session-item.tsx +++ b/apps/login/src/components/session-item.tsx @@ -102,7 +102,7 @@ export function SessionItem({ />
    -
    +
    {session.factors?.user?.displayName} {session.factors?.user?.loginName} diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 32e9102913..49853c9ce2 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -27,6 +27,7 @@ import { getSessionCookieByLoginName } from "../cookies"; type ResetPasswordCommand = { loginName: string; organization?: string; + authRequestId?: string; }; export async function resetPassword(command: ResetPasswordCommand) { @@ -46,7 +47,7 @@ export async function resetPassword(command: ResetPasswordCommand) { } const userId = users.result[0].userId; - return passwordReset(userId, host); + return passwordReset(userId, host, command.authRequestId); } export type UpdateSessionCommand = { diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index be410551fb..0afc4c4dc1 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -504,7 +504,11 @@ export function createUser( * @param userId the id of the user where the email should be set * @returns the newly set email */ -export async function passwordReset(userId: string, host: string | null) { +export async function passwordReset( + userId: string, + host: string | null, + authRequestId?: string, +) { let medium = create(SendPasswordResetLinkSchema, { notificationType: NotificationType.Email, }); @@ -512,7 +516,9 @@ export async function passwordReset(userId: string, host: string | null) { if (host) { medium = { ...medium, - urlTemplate: `${host.includes("localhost") ? "http://" : "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}}` + + (authRequestId ? `&authRequestId=${authRequestId}` : ""), }; } From 837f45b36c003f1d3f95704e52fe1af73237ded6 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 29 Nov 2024 08:23:47 +0100 Subject: [PATCH 522/640] handle password change required on pwd page if no additional factor --- apps/login/src/lib/server/loginname.ts | 37 +++++++++-------- apps/login/src/lib/server/password.ts | 56 ++++++++++++++++++-------- 2 files changed, 60 insertions(+), 33 deletions(-) diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 918202065b..6e2c230db3 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -2,7 +2,6 @@ import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; -import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; import { idpTypeToIdentityProviderType, idpTypeToSlug } from "../idp"; @@ -136,25 +135,29 @@ export async function sendLoginname(command: SendLoginnameCommand) { return { error: "Could not create session for user" }; } - if (users.result[0].state === UserState.INITIAL) { - const params = new URLSearchParams({ - loginName: session.factors?.user?.loginName, - initial: "true", // this does not require a code to be set - }); + // TODO: check if handling of userstate INITIAL is needed - if (command.organization || session.factors?.user?.organizationId) { - params.append( - "organization", - command.organization ?? session.factors?.user?.organizationId, - ); - } + // if ( + // users.result[0].state === UserState.INITIAL + // ) { + // const params = new URLSearchParams({ + // loginName: session.factors?.user?.loginName, + // initial: "true", // this does not require a code to be set + // }); - if (command.authRequestId) { - params.append("authRequestid", command.authRequestId); - } + // if (command.organization || session.factors?.user?.organizationId) { + // params.append( + // "organization", + // command.organization ?? session.factors?.user?.organizationId, + // ); + // } - return { redirect: "/password/set?" + params }; - } + // if (command.authRequestId) { + // params.append("authRequestId", command.authRequestId); + // } + + // return { redirect: "/password/set?" + params }; + // } const methods = await listAuthenticationMethodTypes( session.factors?.user?.id, diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 49853c9ce2..007308ed18 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -18,7 +18,7 @@ import { ChecksSchema, } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { User, UserState } from "@zitadel/proto/zitadel/user/v2/user_pb"; +import { User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; import { getNextUrl } from "../client"; @@ -148,6 +148,26 @@ export async function sendPassword(command: UpdateSessionCommand) { m !== AuthenticationMethodType.PASSKEY, ); + const humanUser = user.type.case === "human" ? user.type.value : undefined; + if ( + availableSecondFactors?.length == 0 && + humanUser?.passwordChangeRequired + ) { + const params = new URLSearchParams({ + loginName: session.factors?.user?.loginName, + }); + + if (command.organization || session.factors?.user?.organizationId) { + params.append("organization", session.factors?.user?.organizationId); + } + + if (command.authRequestId) { + params.append("authRequestId", command.authRequestId); + } + + return { redirect: "/password/change?" + params }; + } + if (availableSecondFactors?.length == 1) { const params = new URLSearchParams({ loginName: session.factors?.user.loginName, @@ -192,24 +212,28 @@ export async function sendPassword(command: UpdateSessionCommand) { } return { redirect: `/mfa?` + params }; - } else if (user.state === UserState.INITIAL) { - const params = new URLSearchParams({ - loginName: session.factors.user.loginName, - }); + } + // TODO: check if handling of userstate INITIAL is needed - if (command.authRequestId) { - params.append("authRequestId", command.authRequestId); - } + // else if (user.state === UserState.INITIAL) { + // const params = new URLSearchParams({ + // loginName: session.factors.user.loginName, + // }); - if (command.organization || session.factors?.user?.organizationId) { - params.append( - "organization", - command.organization ?? session.factors?.user?.organizationId, - ); - } + // if (command.authRequestId) { + // params.append("authRequestId", command.authRequestId); + // } - return { redirect: `/password/change?` + params }; - } else if ( + // if (command.organization || session.factors?.user?.organizationId) { + // params.append( + // "organization", + // command.organization ?? session.factors?.user?.organizationId, + // ); + // } + + // return { redirect: `/password/change?` + params }; + // } + else if ( (loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) && !availableSecondFactors.length ) { From bac941890aa4412f401b6972d70e43ac823f7359 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 29 Nov 2024 13:38:56 +0100 Subject: [PATCH 523/640] idp for invites --- .../app/(login)/authenticator/set/page.tsx | 56 ++-- .../(login)/idp/[provider]/success/page.tsx | 5 +- apps/login/src/app/(login)/loginname/page.tsx | 19 +- .../login/src/components/sign-in-with-idp.tsx | 249 ++++++++++-------- apps/login/src/lib/server/password.ts | 1 + 5 files changed, 185 insertions(+), 145 deletions(-) diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 1c4422327f..1c48ea7a19 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -2,10 +2,12 @@ import { Alert } from "@/components/alert"; import { BackButton } from "@/components/back-button"; import { ChooseAuthenticatorToSetup } from "@/components/choose-authenticator-to-setup"; import { DynamicTheme } from "@/components/dynamic-theme"; +import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { loadMostRecentSession } from "@/lib/session"; import { + getActiveIdentityProviders, getBrandingSettings, getLoginSettings, getSession, @@ -74,6 +76,10 @@ export default async function Page(props: { }); } + if (!sessionWithData) { + return {tError("unknownContext")}; + } + const branding = await getBrandingSettings( sessionWithData.factors?.user?.organizationId, ); @@ -82,22 +88,32 @@ export default async function Page(props: { sessionWithData.factors?.user?.organizationId, ); + const identityProviders = await getActiveIdentityProviders( + sessionWithData.factors?.user?.organizationId, + ).then((resp) => { + return resp.identityProviders; + }); + const params = new URLSearchParams({ initial: "true", // defines that a code is not required and is therefore not shown in the UI }); - if (loginName) { - params.set("loginName", loginName); + if (sessionWithData.factors?.user?.loginName) { + params.set("loginName", sessionWithData.factors?.user?.loginName); } - if (organization) { - params.set("organization", organization); + if (sessionWithData.factors?.user?.organizationId) { + params.set("organization", sessionWithData.factors?.user?.organizationId); } if (authRequestId) { params.set("authRequestId", authRequestId); } + const host = process.env.VERCEL_URL + ? `https://${process.env.VERCEL_URL}` + : "http://localhost:3000"; + return (
    @@ -105,18 +121,14 @@ export default async function Page(props: {

    {t("description")}

    - {sessionWithData && ( - - )} + - {!(loginName || sessionId) && {tError("unknownContext")}} - - {loginSettings && sessionWithData && ( + {loginSettings && ( )} +

    + or sign in with an Identity Provider +

    + + {loginSettings?.allowExternalIdp && identityProviders && ( + + )} +
    diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 48a035f34e..b536456c23 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -37,7 +37,7 @@ export default async function Page(props: { const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "idp" }); - const { id, token, authRequestId, organization } = searchParams; + const { id, token, authRequestId, organization, link } = searchParams; const { provider } = params; const branding = await getBrandingSettings(organization); @@ -50,7 +50,8 @@ export default async function Page(props: { const { idpInformation, userId } = intent; - if (userId) { + // sign in user. If user should be linked continue + if (userId && !link) { // TODO: update user if idp.options.isAutoUpdate is true return ( diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 55a6682ce1..b0a1ba4bb9 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -2,23 +2,14 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { UsernameForm } from "@/components/username-form"; import { + getActiveIdentityProviders, getBrandingSettings, getDefaultOrg, getLoginSettings, - settingsService, } from "@/lib/zitadel"; -import { makeReqCtx } from "@zitadel/client/v2"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { getLocale, getTranslations } from "next-intl/server"; -function getIdentityProviders(orgId?: string) { - return settingsService - .getActiveIdentityProviders({ ctx: makeReqCtx(orgId) }, {}) - .then((resp) => { - return resp.identityProviders; - }); -} - export default async function Page(props: { searchParams: Promise>; }) { @@ -47,9 +38,11 @@ export default async function Page(props: { organization ?? defaultOrganization, ); - const identityProviders = await getIdentityProviders( + const identityProviders = await getActiveIdentityProviders( organization ?? defaultOrganization, - ); + ).then((resp) => { + return resp.identityProviders; + }); const branding = await getBrandingSettings( organization ?? defaultOrganization, @@ -68,7 +61,7 @@ export default async function Page(props: { submit={submit} allowRegister={!!loginSettings?.allowRegister} > - {identityProviders && process.env.ZITADEL_API_URL && ( + {identityProviders && ( (false); const [error, setError] = useState(""); @@ -39,6 +41,10 @@ export function SignInWithIdp({ const params = new URLSearchParams(); + if (linkOnly) { + params.set("link", "true"); + } + if (authRequestId) { params.set("authRequestId", authRequestId); } @@ -70,121 +76,134 @@ export function SignInWithIdp({ return (
    {identityProviders && - identityProviders.map((idp, i) => { - switch (idp.type) { - case IdentityProviderType.APPLE: - return ( - - startFlow(idp.id, idpTypeToSlug(IdentityProviderType.APPLE)) - } - > - ); - case IdentityProviderType.OAUTH: - return ( - - startFlow(idp.id, idpTypeToSlug(IdentityProviderType.OAUTH)) - } - > - ); - case IdentityProviderType.OIDC: - return ( - - startFlow(idp.id, idpTypeToSlug(IdentityProviderType.OIDC)) - } - > - ); - case IdentityProviderType.GITHUB: - return ( - - startFlow( - idp.id, - idpTypeToSlug(IdentityProviderType.GITHUB), - ) - } - > - ); - case IdentityProviderType.GITHUB_ES: - return ( - - startFlow( - idp.id, - idpTypeToSlug(IdentityProviderType.GITHUB_ES), - ) - } - > - ); - case IdentityProviderType.AZURE_AD: - return ( - - startFlow( - idp.id, - idpTypeToSlug(IdentityProviderType.AZURE_AD), - ) - } - > - ); - case IdentityProviderType.GOOGLE: - return ( - - startFlow( - idp.id, - idpTypeToSlug(IdentityProviderType.GOOGLE), - ) - } - > - ); - case IdentityProviderType.GITLAB: - return ( - - startFlow( - idp.id, - idpTypeToSlug(IdentityProviderType.GITLAB), - ) - } - > - ); - case IdentityProviderType.GITLAB_SELF_HOSTED: - return ( - - startFlow( - idp.id, - idpTypeToSlug(IdentityProviderType.GITLAB_SELF_HOSTED), - ) - } - > - ); - default: - return null; - } - })} + identityProviders + // .filter((idp) => + // linkOnly ? idp.config?.options.isLinkingAllowed : true, + // ) + .map((idp, i) => { + switch (idp.type) { + case IdentityProviderType.APPLE: + return ( + + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.APPLE), + ) + } + > + ); + case IdentityProviderType.OAUTH: + return ( + + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.OAUTH), + ) + } + > + ); + case IdentityProviderType.OIDC: + return ( + + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.OIDC), + ) + } + > + ); + case IdentityProviderType.GITHUB: + return ( + + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.GITHUB), + ) + } + > + ); + case IdentityProviderType.GITHUB_ES: + return ( + + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.GITHUB_ES), + ) + } + > + ); + case IdentityProviderType.AZURE_AD: + return ( + + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.AZURE_AD), + ) + } + > + ); + case IdentityProviderType.GOOGLE: + return ( + + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.GOOGLE), + ) + } + > + ); + case IdentityProviderType.GITLAB: + return ( + + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.GITLAB), + ) + } + > + ); + case IdentityProviderType.GITLAB_SELF_HOSTED: + return ( + + startFlow( + idp.id, + idpTypeToSlug(IdentityProviderType.GITLAB_SELF_HOSTED), + ) + } + > + ); + default: + return null; + } + })} {error && (
    {error} diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 007308ed18..8f448c4d7a 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -149,6 +149,7 @@ export async function sendPassword(command: UpdateSessionCommand) { ); const humanUser = user.type.case === "human" ? user.type.value : undefined; + console.log("humanUser", humanUser); if ( availableSecondFactors?.length == 0 && humanUser?.passwordChangeRequired From 3cde9d46a6f14e8cdec5101e1ecc5e911b656c74 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 29 Nov 2024 14:35:06 +0100 Subject: [PATCH 524/640] handle disallow passkey, handle disallow pwd --- apps/login/src/lib/server/loginname.ts | 60 ++++++++++++++------------ apps/login/src/lib/server/password.ts | 25 ++--------- 2 files changed, 37 insertions(+), 48 deletions(-) diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 6e2c230db3..9b04d91fe8 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -6,6 +6,8 @@ import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_se import { headers } from "next/headers"; import { idpTypeToIdentityProviderType, idpTypeToSlug } from "../idp"; +import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { getActiveIdentityProviders, getIDPByID, @@ -34,6 +36,15 @@ export async function sendLoginname(command: SendLoginnameCommand) { const loginSettings = await getLoginSettings(command.organization); + const potentialUsers = users.result.filter((u) => { + const human = u.type.case === "human" ? u.type.value : undefined; + return loginSettings?.disableLoginWithEmail + ? human?.email?.isVerified && human?.email?.email !== command.loginName + : loginSettings?.disableLoginWithPhone + ? human?.phone?.isVerified && human?.phone?.phone !== command.loginName + : true; + }); + const redirectUserToSingleIDPIfAvailable = async () => { const identityProviders = await getActiveIdentityProviders( command.organization, @@ -118,8 +129,8 @@ export async function sendLoginname(command: SendLoginnameCommand) { } }; - if (users.details?.totalResult == BigInt(1) && users.result[0].userId) { - const userId = users.result[0].userId; + if (potentialUsers.length == 1 && potentialUsers[0].userId) { + const userId = potentialUsers[0].userId; const checks = create(ChecksSchema, { user: { search: { case: "userId", value: userId } }, @@ -136,28 +147,9 @@ export async function sendLoginname(command: SendLoginnameCommand) { } // TODO: check if handling of userstate INITIAL is needed - - // if ( - // users.result[0].state === UserState.INITIAL - // ) { - // const params = new URLSearchParams({ - // loginName: session.factors?.user?.loginName, - // initial: "true", // this does not require a code to be set - // }); - - // if (command.organization || session.factors?.user?.organizationId) { - // params.append( - // "organization", - // command.organization ?? session.factors?.user?.organizationId, - // ); - // } - - // if (command.authRequestId) { - // params.append("authRequestId", command.authRequestId); - // } - - // return { redirect: "/password/set?" + params }; - // } + if (potentialUsers[0].state === UserState.INITIAL) { + return { error: "Initial User not supported" }; + } const methods = await listAuthenticationMethodTypes( session.factors?.user?.id, @@ -165,9 +157,9 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (!methods.authMethodTypes || !methods.authMethodTypes.length) { if ( - users.result[0].type.case === "human" && - users.result[0].type.value.email && - !users.result[0].type.value.email.isVerified + potentialUsers[0].type.case === "human" && + potentialUsers[0].type.value.email && + !potentialUsers[0].type.value.email.isVerified ) { const paramsVerify = new URLSearchParams({ loginName: session.factors?.user?.loginName, @@ -212,6 +204,13 @@ export async function sendLoginname(command: SendLoginnameCommand) { const method = methods.authMethodTypes[0]; switch (method) { case AuthenticationMethodType.PASSWORD: // user has only password as auth method + if (!loginSettings?.allowUsernamePassword) { + return { + error: + "Username Password not allowed! Contact your administrator for more information.", + }; + } + const paramsPassword: any = { loginName: session.factors?.user?.loginName, }; @@ -232,6 +231,13 @@ export async function sendLoginname(command: SendLoginnameCommand) { }; case AuthenticationMethodType.PASSKEY: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY + if (loginSettings?.passkeysType === PasskeysType.NOT_ALLOWED) { + return { + error: + "Passkeys not allowed! Contact your administrator for more information.", + }; + } + const paramsPasskey: any = { loginName: command.loginName }; if (command.authRequestId) { paramsPasskey.authRequestId = command.authRequestId; diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 8f448c4d7a..c4794c635d 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -18,7 +18,7 @@ import { ChecksSchema, } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { User } from "@zitadel/proto/zitadel/user/v2/user_pb"; +import { User, UserState } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; import { getNextUrl } from "../client"; @@ -215,26 +215,9 @@ export async function sendPassword(command: UpdateSessionCommand) { return { redirect: `/mfa?` + params }; } // TODO: check if handling of userstate INITIAL is needed - - // else if (user.state === UserState.INITIAL) { - // const params = new URLSearchParams({ - // loginName: session.factors.user.loginName, - // }); - - // if (command.authRequestId) { - // params.append("authRequestId", command.authRequestId); - // } - - // if (command.organization || session.factors?.user?.organizationId) { - // params.append( - // "organization", - // command.organization ?? session.factors?.user?.organizationId, - // ); - // } - - // return { redirect: `/password/change?` + params }; - // } - else if ( + else if (user.state === UserState.INITIAL) { + return { error: "Initial User not supported" }; + } else if ( (loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) && !availableSecondFactors.length ) { From 6f69ba11b2fe163d4b6279acf1649452f3e74fa2 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 3 Dec 2024 09:11:15 +0100 Subject: [PATCH 525/640] allow pwd in tests --- apps/login/cypress/integration/login.cy.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/login/cypress/integration/login.cy.ts b/apps/login/cypress/integration/login.cy.ts index f293653af1..a03863a9ce 100644 --- a/apps/login/cypress/integration/login.cy.ts +++ b/apps/login/cypress/integration/login.cy.ts @@ -49,6 +49,7 @@ describe("login", () => { data: { settings: { passkeysType: 1, + allowUsernamePassword: true, }, }, }); From f409dc363393b2d6a9567430f65b82a0e2b2013b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 3 Dec 2024 09:39:42 +0100 Subject: [PATCH 526/640] complete idp flow --- apps/login/src/components/username-form.tsx | 2 ++ apps/login/src/lib/server/loginname.ts | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/login/src/components/username-form.tsx b/apps/login/src/components/username-form.tsx index 7d18623aba..ab56bc2926 100644 --- a/apps/login/src/components/username-form.tsx +++ b/apps/login/src/components/username-form.tsx @@ -61,6 +61,8 @@ export function UsernameForm({ setLoading(false); }); + console.log(res, "res"); + if (res?.redirect) { return router.push(res.redirect); } diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 9b04d91fe8..99b398fa44 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -94,6 +94,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { const identityProviderId = identityProviders[0].idpId; const idp = await getIDPByID(identityProviderId); + const idpType = idp?.type; if (!idp || !idpType) { @@ -271,7 +272,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } else if ( methods.authMethodTypes.includes(AuthenticationMethodType.IDP) ) { - await redirectUserToIDP(userId); + return redirectUserToIDP(userId); } else if ( methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD) ) { @@ -297,8 +298,10 @@ export async function sendLoginname(command: SendLoginnameCommand) { // user not found, check if register is enabled on organization if (loginSettings?.allowRegister && !loginSettings?.allowUsernamePassword) { // TODO: do we need to handle login hints for IDPs here? - await redirectUserToSingleIDPIfAvailable(); - + const resp = await redirectUserToSingleIDPIfAvailable(); + if (resp) { + return resp; + } return { error: "Could not find user" }; } else if ( loginSettings?.allowRegister && From bb8f33af429e089abc794287cbfe206510b693ef Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 3 Dec 2024 09:45:10 +0100 Subject: [PATCH 527/640] temp remove idp from invite flow --- .../src/app/(login)/authenticator/set/page.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 1c48ea7a19..f4ee7024fe 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -2,12 +2,10 @@ import { Alert } from "@/components/alert"; import { BackButton } from "@/components/back-button"; import { ChooseAuthenticatorToSetup } from "@/components/choose-authenticator-to-setup"; import { DynamicTheme } from "@/components/dynamic-theme"; -import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { loadMostRecentSession } from "@/lib/session"; import { - getActiveIdentityProviders, getBrandingSettings, getLoginSettings, getSession, @@ -88,11 +86,11 @@ export default async function Page(props: { sessionWithData.factors?.user?.organizationId, ); - const identityProviders = await getActiveIdentityProviders( - sessionWithData.factors?.user?.organizationId, - ).then((resp) => { - return resp.identityProviders; - }); + // const identityProviders = await getActiveIdentityProviders( + // sessionWithData.factors?.user?.organizationId, + // ).then((resp) => { + // return resp.identityProviders; + // }); const params = new URLSearchParams({ initial: "true", // defines that a code is not required and is therefore not shown in the UI @@ -136,7 +134,7 @@ export default async function Page(props: { > )} -

    + {/*

    or sign in with an Identity Provider

    @@ -148,7 +146,7 @@ export default async function Page(props: { organization={sessionWithData.factors?.user?.organizationId} linkOnly={true} // tell the callback function to just link the IDP and not login, to get an error when user is already available > - )} + )} */}
    From b0f991ac2c2a3db864e608fc1b3c8bc5bb489b46 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 3 Dec 2024 10:07:14 +0100 Subject: [PATCH 528/640] show validity based on idp --- apps/login/src/components/session-item.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/login/src/components/session-item.tsx b/apps/login/src/components/session-item.tsx index 4df720b056..1845ea63bc 100644 --- a/apps/login/src/components/session-item.tsx +++ b/apps/login/src/components/session-item.tsx @@ -16,12 +16,14 @@ export function isSessionValid(session: Partial): { } { const validPassword = session?.factors?.password?.verifiedAt; const validPasskey = session?.factors?.webAuthN?.verifiedAt; + const validIDP = session?.factors?.intent?.verifiedAt; + const stillValid = session.expirationDate ? timestampDate(session.expirationDate) > new Date() : true; - const verifiedAt = validPassword || validPasskey; - const valid = !!((validPassword || validPasskey) && stillValid); + const verifiedAt = validPassword || validPasskey || validIDP; + const valid = !!((validPassword || validPasskey || validIDP) && stillValid); return { valid, verifiedAt }; } @@ -107,10 +109,16 @@ export function SessionItem({ {session.factors?.user?.loginName} - {valid && ( + {valid ? ( {verifiedAt && moment(timestampDate(verifiedAt)).fromNow()} + ) : ( + + expired{" "} + {session.expirationDate && + moment(timestampDate(session.expirationDate)).fromNow()} + )}
    From d50db37fa24a40f9002e5d23f238fe1b72ca0c11 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 3 Dec 2024 10:26:44 +0100 Subject: [PATCH 529/640] cleanup host on idp login --- apps/login/src/app/(login)/loginname/page.tsx | 5 ----- apps/login/src/components/sign-in-with-idp.tsx | 5 +++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index b0a1ba4bb9..e886de7f87 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -30,10 +30,6 @@ export default async function Page(props: { } } - const host = process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}` - : "http://localhost:3000"; - const loginSettings = await getLoginSettings( organization ?? defaultOrganization, ); @@ -63,7 +59,6 @@ export default async function Page(props: { > {identityProviders && ( Date: Tue, 3 Dec 2024 10:49:33 +0100 Subject: [PATCH 530/640] cleanup --- apps/login/src/app/(login)/idp/page.tsx | 5 ----- apps/login/src/components/sign-in-with-idp.tsx | 9 ++------- apps/login/src/lib/server/idp.ts | 7 +++++-- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index d11bd8c20a..30d1c9fab5 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -24,10 +24,6 @@ export default async function Page(props: { const identityProviders = await getIdentityProviders(organization); - const host = process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}` - : "http://localhost:3000"; - const branding = await getBrandingSettings(organization); return ( @@ -38,7 +34,6 @@ export default async function Page(props: { {identityProviders && ( { setError("Could not start IDP flow"); diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index 0c5d26d45c..2077e1697f 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -1,6 +1,7 @@ "use server"; import { startIdentityProviderFlow } from "@/lib/zitadel"; +import { headers } from "next/headers"; export type StartIDPFlowCommand = { idpId: string; @@ -9,11 +10,13 @@ export type StartIDPFlowCommand = { }; export async function startIDPFlow(command: StartIDPFlowCommand) { + const host = (await headers()).get("host"); + return startIdentityProviderFlow({ idpId: command.idpId, urls: { - successUrl: command.successUrl, - failureUrl: command.failureUrl, + successUrl: `${host}${command.successUrl}`, + failureUrl: `${host}${command.failureUrl}`, }, }).then((response) => { if ( From c2295760dcb421c532a641e1bdb45bd30d566966 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 3 Dec 2024 10:54:07 +0100 Subject: [PATCH 531/640] protocol for host, error handling --- apps/login/src/components/sign-in-with-idp.tsx | 5 +++++ apps/login/src/lib/server/idp.ts | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/login/src/components/sign-in-with-idp.tsx b/apps/login/src/components/sign-in-with-idp.tsx index 555b90feee..e94ba8364b 100644 --- a/apps/login/src/components/sign-in-with-idp.tsx +++ b/apps/login/src/components/sign-in-with-idp.tsx @@ -64,6 +64,11 @@ export function SignInWithIdp({ setLoading(false); }); + if (response && "error" in response && response?.error) { + setError(response.error); + return; + } + if (response && "redirect" in response && response?.redirect) { return router.push(response.redirect); } diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index 2077e1697f..ebb755987e 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -12,11 +12,15 @@ export type StartIDPFlowCommand = { export async function startIDPFlow(command: StartIDPFlowCommand) { const host = (await headers()).get("host"); + if (!host) { + return { error: "Could not get host" }; + } + return startIdentityProviderFlow({ idpId: command.idpId, urls: { - successUrl: `${host}${command.successUrl}`, - failureUrl: `${host}${command.failureUrl}`, + successUrl: `${host.includes("localhost") ? "http://" : "https://"}${host}${command.successUrl}`, + failureUrl: `${host.includes("localhost") ? "http://" : "https://"}${host}${command.failureUrl}`, }, }).then((response) => { if ( From 798cd464a4904b950772f3724adb549ca6a0e19c Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 3 Dec 2024 10:57:04 +0100 Subject: [PATCH 532/640] fix host, error handling --- apps/login/src/app/login/route.ts | 6 +++--- apps/login/src/lib/server/loginname.ts | 22 ++++++++++++++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index af8a74f54c..9ef4d00741 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -174,7 +174,7 @@ export async function GET(request: NextRequest) { const idp = identityProviders.find((idp) => idp.id === idpId); if (idp) { - const host = request.nextUrl.origin; + const origin = request.nextUrl.origin; const identityProviderType = identityProviders[0].type; let provider = idpTypeToSlug(identityProviderType); @@ -193,10 +193,10 @@ export async function GET(request: NextRequest) { idpId, urls: { successUrl: - `${host}/idp/${provider}/success?` + + `${origin}/idp/${provider}/success?` + new URLSearchParams(params), failureUrl: - `${host}/idp/${provider}/failure?` + + `${origin}/idp/${provider}/failure?` + new URLSearchParams(params), }, }).then((resp) => { diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 99b398fa44..295f9b455f 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -54,6 +54,11 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (identityProviders.length === 1) { const host = (await headers()).get("host"); + + if (!host) { + return { error: "Could not get host" }; + } + const identityProviderType = identityProviders[0].type; const provider = idpTypeToSlug(identityProviderType); @@ -72,9 +77,11 @@ export async function sendLoginname(command: SendLoginnameCommand) { idpId: identityProviders[0].id, urls: { successUrl: - `${host}/idp/${provider}/success?` + new URLSearchParams(params), + `${host.includes("localhost") ? "http://" : "https://"}${host}/idp/${provider}/success?` + + new URLSearchParams(params), failureUrl: - `${host}/idp/${provider}/failure?` + new URLSearchParams(params), + `${host.includes("localhost") ? "http://" : "https://"}${host}/idp/${provider}/failure?` + + new URLSearchParams(params), }, }); @@ -91,6 +98,11 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (identityProviders.length === 1) { const host = (await headers()).get("host"); + + if (!host) { + return { error: "Could not get host" }; + } + const identityProviderId = identityProviders[0].idpId; const idp = await getIDPByID(identityProviderId); @@ -118,9 +130,11 @@ export async function sendLoginname(command: SendLoginnameCommand) { idpId: idp.id, urls: { successUrl: - `${host}/idp/${provider}/success?` + new URLSearchParams(params), + `${host.includes("localhost") ? "http://" : "https://"}${host}/idp/${provider}/success?` + + new URLSearchParams(params), failureUrl: - `${host}/idp/${provider}/failure?` + new URLSearchParams(params), + `${host.includes("localhost") ? "http://" : "https://"}${host}/idp/${provider}/failure?` + + new URLSearchParams(params), }, }); From b81afaee15d43f6ad1f59a851f3baa7d7ef990e5 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 3 Dec 2024 13:31:54 +0100 Subject: [PATCH 533/640] not supported readme --- apps/login/readme.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index 2aa09da2e4..bc946cf0c4 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -389,7 +389,8 @@ In future, self service options to jump to are shown below, like: ## Currently NOT Supported -- loginSettings.disableLoginWithEmail -- loginSettings.disableLoginWithPhone -- loginSettings.allowExternalIdp - this will be deprecated with the new login as it can be determined by the available IDPs -- loginSettings.forceMfaLocalOnly +Timebased features like the multifactor init prompt or password expiry, are not supported due to a current limitation in the API. Lockout settings which keeps track of the password retries, will also be implemented in a later stage. + +- Lockout Settings +- Password Expiry Settings +- Login Settings: multifactor init prompt From 30091ddb17808b9f729c39bea7b20c1a70cb8655 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 3 Dec 2024 13:48:07 +0100 Subject: [PATCH 534/640] cleanup --- apps/login/src/app/(login)/authenticator/set/page.tsx | 4 ++++ apps/login/src/components/sign-in-with-idp.tsx | 2 ++ apps/login/src/components/username-form.tsx | 2 -- apps/login/src/lib/server/password.ts | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index f4ee7024fe..36294c8c1c 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -86,6 +86,8 @@ export default async function Page(props: { sessionWithData.factors?.user?.organizationId, ); + /* - TODO: Implement after https://github.com/zitadel/zitadel/issues/8981 */ + // const identityProviders = await getActiveIdentityProviders( // sessionWithData.factors?.user?.organizationId, // ).then((resp) => { @@ -134,6 +136,8 @@ export default async function Page(props: { > )} + {/* - TODO: Implement after https://github.com/zitadel/zitadel/issues/8981 */} + {/*

    or sign in with an Identity Provider

    diff --git a/apps/login/src/components/sign-in-with-idp.tsx b/apps/login/src/components/sign-in-with-idp.tsx index e94ba8364b..a10680eae5 100644 --- a/apps/login/src/components/sign-in-with-idp.tsx +++ b/apps/login/src/components/sign-in-with-idp.tsx @@ -78,6 +78,8 @@ export function SignInWithIdp({
    {identityProviders && identityProviders + /* - TODO: Implement after https://github.com/zitadel/zitadel/issues/8981 */ + // .filter((idp) => // linkOnly ? idp.config?.options.isLinkingAllowed : true, // ) diff --git a/apps/login/src/components/username-form.tsx b/apps/login/src/components/username-form.tsx index ab56bc2926..7d18623aba 100644 --- a/apps/login/src/components/username-form.tsx +++ b/apps/login/src/components/username-form.tsx @@ -61,8 +61,6 @@ export function UsernameForm({ setLoading(false); }); - console.log(res, "res"); - if (res?.redirect) { return router.push(res.redirect); } diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index c4794c635d..5e202aabd5 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -149,7 +149,7 @@ export async function sendPassword(command: UpdateSessionCommand) { ); const humanUser = user.type.case === "human" ? user.type.value : undefined; - console.log("humanUser", humanUser); + if ( availableSecondFactors?.length == 0 && humanUser?.passwordChangeRequired From 017c3215eba4f68ef1f4e190397e9e4c95ab2cef Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 3 Dec 2024 14:31:50 +0100 Subject: [PATCH 535/640] use origin header instead of host --- .../app/(login)/authenticator/set/page.tsx | 4 ---- .../src/app/(login)/otp/[method]/page.tsx | 4 ++-- apps/login/src/components/login-otp.tsx | 8 +++---- apps/login/src/lib/server/idp.ts | 10 ++++---- apps/login/src/lib/server/invite.ts | 4 ++-- apps/login/src/lib/server/loginname.ts | 24 ++++++++----------- apps/login/src/lib/server/passkeys.ts | 2 +- apps/login/src/lib/server/password.ts | 4 ++-- apps/login/src/lib/zitadel.ts | 12 +++++----- 9 files changed, 32 insertions(+), 40 deletions(-) diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 36294c8c1c..a34a9e8c7c 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -110,10 +110,6 @@ export default async function Page(props: { params.set("authRequestId", authRequestId); } - const host = process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}` - : "http://localhost:3000"; - return (
    diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 1c0904cee2..c509cc6ee1 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -31,7 +31,7 @@ export default async function Page(props: { const loginSettings = await getLoginSettings(organization); - const host = (await headers()).get("host"); + const origin = (await headers()).get("origin"); return ( @@ -70,7 +70,7 @@ export default async function Page(props: { organization={organization} method={method} loginSettings={loginSettings} - host={host} + origin={origin} code={code} > )} diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 262541eb1b..6dbacdb66e 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -25,7 +25,7 @@ type Props = { method: string; code?: string; loginSettings?: LoginSettings; - host: string | null; + origin: string | null; }; type Inputs = { @@ -40,7 +40,7 @@ export function LoginOTP({ method, code, loginSettings, - host, + origin, }: Props) { const t = useTranslations("otp"); @@ -81,10 +81,10 @@ export function LoginOTP({ otpEmail: { deliveryType: { case: "sendCode", - value: host + value: origin ? { urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}/otp/method=${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}&organization={{.OrgID}}` + + `${origin}/otp/method=${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}&organization={{.OrgID}}` + (authRequestId ? `&authRequestId=${authRequestId}` : ""), } : {}, diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index ebb755987e..0b376ad4bc 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -10,17 +10,17 @@ export type StartIDPFlowCommand = { }; export async function startIDPFlow(command: StartIDPFlowCommand) { - const host = (await headers()).get("host"); + const origin = (await headers()).get("origin"); - if (!host) { - return { error: "Could not get host" }; + if (!origin) { + return { error: "Could not get origin" }; } return startIdentityProviderFlow({ idpId: command.idpId, urls: { - successUrl: `${host.includes("localhost") ? "http://" : "https://"}${host}${command.successUrl}`, - failureUrl: `${host.includes("localhost") ? "http://" : "https://"}${host}${command.failureUrl}`, + successUrl: `${origin}${command.successUrl}`, + failureUrl: `${origin}${command.failureUrl}`, }, }).then((response) => { if ( diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts index 3c68587898..b9db345b21 100644 --- a/apps/login/src/lib/server/invite.ts +++ b/apps/login/src/lib/server/invite.ts @@ -20,7 +20,7 @@ export type RegisterUserResponse = { }; export async function inviteUser(command: InviteUserCommand) { - const host = (await headers()).get("host"); + const origin = (await headers()).get("origin"); const human = await addHumanUser({ email: command.email, @@ -34,7 +34,7 @@ export async function inviteUser(command: InviteUserCommand) { return { error: "Could not create user" }; } - const codeResponse = await createInviteCode(human.userId, host); + const codeResponse = await createInviteCode(human.userId, origin); if (!codeResponse || !human) { return { error: "Could not create invite code" }; diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 295f9b455f..ca92ad1556 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -53,10 +53,10 @@ export async function sendLoginname(command: SendLoginnameCommand) { }); if (identityProviders.length === 1) { - const host = (await headers()).get("host"); + const origin = (await headers()).get("origin"); - if (!host) { - return { error: "Could not get host" }; + if (!origin) { + return { error: "Could not get origin" }; } const identityProviderType = identityProviders[0].type; @@ -77,11 +77,9 @@ export async function sendLoginname(command: SendLoginnameCommand) { idpId: identityProviders[0].id, urls: { successUrl: - `${host.includes("localhost") ? "http://" : "https://"}${host}/idp/${provider}/success?` + - new URLSearchParams(params), + `${origin}/idp/${provider}/success?` + new URLSearchParams(params), failureUrl: - `${host.includes("localhost") ? "http://" : "https://"}${host}/idp/${provider}/failure?` + - new URLSearchParams(params), + `${origin}/idp/${provider}/failure?` + new URLSearchParams(params), }, }); @@ -97,10 +95,10 @@ export async function sendLoginname(command: SendLoginnameCommand) { }); if (identityProviders.length === 1) { - const host = (await headers()).get("host"); + const origin = (await headers()).get("origin"); - if (!host) { - return { error: "Could not get host" }; + if (!origin) { + return { error: "Could not get origin" }; } const identityProviderId = identityProviders[0].idpId; @@ -130,11 +128,9 @@ export async function sendLoginname(command: SendLoginnameCommand) { idpId: idp.id, urls: { successUrl: - `${host.includes("localhost") ? "http://" : "https://"}${host}/idp/${provider}/success?` + - new URLSearchParams(params), + `${origin}/idp/${provider}/success?` + new URLSearchParams(params), failureUrl: - `${host.includes("localhost") ? "http://" : "https://"}${host}/idp/${provider}/failure?` + - new URLSearchParams(params), + `${origin}/idp/${provider}/failure?` + new URLSearchParams(params), }, }); diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 181962cae1..518b070993 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -40,7 +40,7 @@ export async function registerPasskeyLink( const host = (await headers()).get("host"); if (!host) { - throw new Error("Could not get domain"); + throw new Error("Could not get host"); } const [hostname, port] = host.split(":"); diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 5e202aabd5..5b284a3e5e 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -31,7 +31,7 @@ type ResetPasswordCommand = { }; export async function resetPassword(command: ResetPasswordCommand) { - const host = (await headers()).get("host"); + const origin = (await headers()).get("origin"); const users = await listUsers({ loginName: command.loginName, @@ -47,7 +47,7 @@ export async function resetPassword(command: ResetPasswordCommand) { } const userId = users.result[0].userId; - return passwordReset(userId, host, command.authRequestId); + return passwordReset(userId, origin, command.authRequestId); } export type UpdateSessionCommand = { diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 0afc4c4dc1..7210442fef 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -267,15 +267,15 @@ export async function resendInviteCode(userId: string) { return userService.resendInviteCode({ userId }, {}); } -export async function createInviteCode(userId: string, host: string | null) { +export async function createInviteCode(userId: string, origin: string | null) { let medium = create(SendInviteCodeSchema, { applicationName: "Typescript Login", }); - if (host) { + if (origin) { medium = { ...medium, - urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true`, + urlTemplate: `${origin}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true`, }; } @@ -506,18 +506,18 @@ export function createUser( */ export async function passwordReset( userId: string, - host: string | null, + origin: string | null, authRequestId?: string, ) { let medium = create(SendPasswordResetLinkSchema, { notificationType: NotificationType.Email, }); - if (host) { + if (origin) { medium = { ...medium, urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + + `${origin}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + (authRequestId ? `&authRequestId=${authRequestId}` : ""), }; } From 36db734343525d0827c4e1c61d22e0bc45a4930e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 3 Dec 2024 14:56:21 +0100 Subject: [PATCH 536/640] Revert "use origin header instead of host" This reverts commit 017c3215eba4f68ef1f4e190397e9e4c95ab2cef. --- .../app/(login)/authenticator/set/page.tsx | 4 ++++ .../src/app/(login)/otp/[method]/page.tsx | 4 ++-- apps/login/src/components/login-otp.tsx | 8 +++---- apps/login/src/lib/server/idp.ts | 10 ++++---- apps/login/src/lib/server/invite.ts | 4 ++-- apps/login/src/lib/server/loginname.ts | 24 +++++++++++-------- apps/login/src/lib/server/passkeys.ts | 2 +- apps/login/src/lib/server/password.ts | 4 ++-- apps/login/src/lib/zitadel.ts | 12 +++++----- 9 files changed, 40 insertions(+), 32 deletions(-) diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index a34a9e8c7c..36294c8c1c 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -110,6 +110,10 @@ export default async function Page(props: { params.set("authRequestId", authRequestId); } + const host = process.env.VERCEL_URL + ? `https://${process.env.VERCEL_URL}` + : "http://localhost:3000"; + return (
    diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index c509cc6ee1..1c0904cee2 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -31,7 +31,7 @@ export default async function Page(props: { const loginSettings = await getLoginSettings(organization); - const origin = (await headers()).get("origin"); + const host = (await headers()).get("host"); return ( @@ -70,7 +70,7 @@ export default async function Page(props: { organization={organization} method={method} loginSettings={loginSettings} - origin={origin} + host={host} code={code} > )} diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 6dbacdb66e..262541eb1b 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -25,7 +25,7 @@ type Props = { method: string; code?: string; loginSettings?: LoginSettings; - origin: string | null; + host: string | null; }; type Inputs = { @@ -40,7 +40,7 @@ export function LoginOTP({ method, code, loginSettings, - origin, + host, }: Props) { const t = useTranslations("otp"); @@ -81,10 +81,10 @@ export function LoginOTP({ otpEmail: { deliveryType: { case: "sendCode", - value: origin + value: host ? { urlTemplate: - `${origin}/otp/method=${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}&organization={{.OrgID}}` + + `${host.includes("localhost") ? "http://" : "https://"}${host}/otp/method=${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}&organization={{.OrgID}}` + (authRequestId ? `&authRequestId=${authRequestId}` : ""), } : {}, diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index 0b376ad4bc..ebb755987e 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -10,17 +10,17 @@ export type StartIDPFlowCommand = { }; export async function startIDPFlow(command: StartIDPFlowCommand) { - const origin = (await headers()).get("origin"); + const host = (await headers()).get("host"); - if (!origin) { - return { error: "Could not get origin" }; + if (!host) { + return { error: "Could not get host" }; } return startIdentityProviderFlow({ idpId: command.idpId, urls: { - successUrl: `${origin}${command.successUrl}`, - failureUrl: `${origin}${command.failureUrl}`, + successUrl: `${host.includes("localhost") ? "http://" : "https://"}${host}${command.successUrl}`, + failureUrl: `${host.includes("localhost") ? "http://" : "https://"}${host}${command.failureUrl}`, }, }).then((response) => { if ( diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts index b9db345b21..3c68587898 100644 --- a/apps/login/src/lib/server/invite.ts +++ b/apps/login/src/lib/server/invite.ts @@ -20,7 +20,7 @@ export type RegisterUserResponse = { }; export async function inviteUser(command: InviteUserCommand) { - const origin = (await headers()).get("origin"); + const host = (await headers()).get("host"); const human = await addHumanUser({ email: command.email, @@ -34,7 +34,7 @@ export async function inviteUser(command: InviteUserCommand) { return { error: "Could not create user" }; } - const codeResponse = await createInviteCode(human.userId, origin); + const codeResponse = await createInviteCode(human.userId, host); if (!codeResponse || !human) { return { error: "Could not create invite code" }; diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index ca92ad1556..295f9b455f 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -53,10 +53,10 @@ export async function sendLoginname(command: SendLoginnameCommand) { }); if (identityProviders.length === 1) { - const origin = (await headers()).get("origin"); + const host = (await headers()).get("host"); - if (!origin) { - return { error: "Could not get origin" }; + if (!host) { + return { error: "Could not get host" }; } const identityProviderType = identityProviders[0].type; @@ -77,9 +77,11 @@ export async function sendLoginname(command: SendLoginnameCommand) { idpId: identityProviders[0].id, urls: { successUrl: - `${origin}/idp/${provider}/success?` + new URLSearchParams(params), + `${host.includes("localhost") ? "http://" : "https://"}${host}/idp/${provider}/success?` + + new URLSearchParams(params), failureUrl: - `${origin}/idp/${provider}/failure?` + new URLSearchParams(params), + `${host.includes("localhost") ? "http://" : "https://"}${host}/idp/${provider}/failure?` + + new URLSearchParams(params), }, }); @@ -95,10 +97,10 @@ export async function sendLoginname(command: SendLoginnameCommand) { }); if (identityProviders.length === 1) { - const origin = (await headers()).get("origin"); + const host = (await headers()).get("host"); - if (!origin) { - return { error: "Could not get origin" }; + if (!host) { + return { error: "Could not get host" }; } const identityProviderId = identityProviders[0].idpId; @@ -128,9 +130,11 @@ export async function sendLoginname(command: SendLoginnameCommand) { idpId: idp.id, urls: { successUrl: - `${origin}/idp/${provider}/success?` + new URLSearchParams(params), + `${host.includes("localhost") ? "http://" : "https://"}${host}/idp/${provider}/success?` + + new URLSearchParams(params), failureUrl: - `${origin}/idp/${provider}/failure?` + new URLSearchParams(params), + `${host.includes("localhost") ? "http://" : "https://"}${host}/idp/${provider}/failure?` + + new URLSearchParams(params), }, }); diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 518b070993..181962cae1 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -40,7 +40,7 @@ export async function registerPasskeyLink( const host = (await headers()).get("host"); if (!host) { - throw new Error("Could not get host"); + throw new Error("Could not get domain"); } const [hostname, port] = host.split(":"); diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 5b284a3e5e..5e202aabd5 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -31,7 +31,7 @@ type ResetPasswordCommand = { }; export async function resetPassword(command: ResetPasswordCommand) { - const origin = (await headers()).get("origin"); + const host = (await headers()).get("host"); const users = await listUsers({ loginName: command.loginName, @@ -47,7 +47,7 @@ export async function resetPassword(command: ResetPasswordCommand) { } const userId = users.result[0].userId; - return passwordReset(userId, origin, command.authRequestId); + return passwordReset(userId, host, command.authRequestId); } export type UpdateSessionCommand = { diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 7210442fef..0afc4c4dc1 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -267,15 +267,15 @@ export async function resendInviteCode(userId: string) { return userService.resendInviteCode({ userId }, {}); } -export async function createInviteCode(userId: string, origin: string | null) { +export async function createInviteCode(userId: string, host: string | null) { let medium = create(SendInviteCodeSchema, { applicationName: "Typescript Login", }); - if (origin) { + if (host) { medium = { ...medium, - urlTemplate: `${origin}/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`, }; } @@ -506,18 +506,18 @@ export function createUser( */ export async function passwordReset( userId: string, - origin: string | null, + host: string | null, authRequestId?: string, ) { let medium = create(SendPasswordResetLinkSchema, { notificationType: NotificationType.Email, }); - if (origin) { + if (host) { medium = { ...medium, urlTemplate: - `${origin}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + + `${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + (authRequestId ? `&authRequestId=${authRequestId}` : ""), }; } From 4740bf3a1a298bb03d84e034ee75cfb1aeb39ad6 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 4 Dec 2024 10:18:18 +0100 Subject: [PATCH 537/640] format --- acceptance/tests/register.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/tests/register.ts b/acceptance/tests/register.ts index e250d60b12..693bdfbc0d 100644 --- a/acceptance/tests/register.ts +++ b/acceptance/tests/register.ts @@ -24,6 +24,6 @@ export async function registerWithPasskey(page: Page, firstname: string, lastnam // wait for projection of user await page.waitForTimeout(2000); - + return await passkeyRegister(page); } From 041f2bcef7511a255443ffdefd6dcb29fd75c8c0 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 4 Dec 2024 15:49:49 +0100 Subject: [PATCH 538/640] updateSession error handling --- .../src/app/(login)/otp/[method]/page.tsx | 2 ++ apps/login/src/components/login-otp.tsx | 21 ++++++++++++-- apps/login/src/components/login-passkey.tsx | 10 +++++++ apps/login/src/lib/cookies.ts | 2 +- apps/login/src/lib/server/session.ts | 28 +++++++++++-------- 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 1c0904cee2..ccccf072e5 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -33,6 +33,8 @@ export default async function Page(props: { const host = (await headers()).get("host"); + console.log("host", host); + return (
    diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 262541eb1b..02f698a425 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -76,6 +76,13 @@ export function LoginOTP({ async function updateSessionForOTPChallenge() { let challenges; + if (host) { + console.log( + `${host.includes("localhost") ? "http://" : "https://"}${host}/otp/method=${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}&organization={{.OrgID}}` + + (authRequestId ? `&authRequestId=${authRequestId}` : ""), + ); + } + if (method === "email") { challenges = create(RequestChallengesSchema, { otpEmail: { @@ -107,14 +114,19 @@ export function LoginOTP({ challenges, authRequestId, }) - .catch((error) => { - setError(error.message ?? "Could not request OTP challenge"); + .catch(() => { + setError("Could not request OTP challenge"); return; }) .finally(() => { setLoading(false); }); + if (response && "error" in response && response.error) { + setError(response.error); + return; + } + return response; } @@ -167,6 +179,11 @@ export function LoginOTP({ setLoading(false); }); + if (response && "error" in response && response.error) { + setError(response.error); + return; + } + return response; } diff --git a/apps/login/src/components/login-passkey.tsx b/apps/login/src/components/login-passkey.tsx index d72749e757..2f1cd53363 100644 --- a/apps/login/src/components/login-passkey.tsx +++ b/apps/login/src/components/login-passkey.tsx @@ -110,6 +110,11 @@ export function LoginPasskey({ setLoading(false); }); + if (session && "error" in session && session.error) { + setError(session.error); + return; + } + return session; } @@ -132,6 +137,11 @@ export function LoginPasskey({ setLoading(false); }); + if (response && "error" in response && response.error) { + setError(response.error); + return; + } + return response; } diff --git a/apps/login/src/lib/cookies.ts b/apps/login/src/lib/cookies.ts index 0054065350..4d29b9e7d4 100644 --- a/apps/login/src/lib/cookies.ts +++ b/apps/login/src/lib/cookies.ts @@ -142,7 +142,7 @@ export async function removeSessionFromCookie( } } -export async function getMostRecentSessionCookie(): Promise { +export async function getMostRecentSessionCookie(): Promise { const cookiesList = await cookies(); const stringifiedCookie = cookiesList.get("sessions"); diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 9726ce84e4..6c2b6cebe6 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -132,21 +132,23 @@ export async function updateSession(options: UpdateSessionCommand) { challenges, } = options; const recentSession = sessionId - ? await getSessionCookieById({ sessionId }).catch((error) => { - return Promise.reject(error); - }) + ? await getSessionCookieById({ sessionId }) : loginName - ? await getSessionCookieByLoginName({ loginName, organization }).catch( - (error) => { - return Promise.reject(error); - }, - ) - : await getMostRecentSessionCookie().catch((error) => { - return Promise.reject(error); - }); + ? await getSessionCookieByLoginName({ loginName, organization }) + : await getMostRecentSessionCookie(); + + if (!recentSession) { + return { + error: "Could not find session", + }; + } const host = (await headers()).get("host"); + if (!host) { + return { error: "Could not get host" }; + } + if ( host && challenges && @@ -174,6 +176,10 @@ export async function updateSession(options: UpdateSessionCommand) { lifetime, ); + if (!session) { + return { error: "Could not update session" }; + } + // if password, check if user has MFA methods let authMethods; if (checks && checks.password && session.factors?.user?.id) { From eaeb4db19c1ea8de872e7bc1a05b1745cee07cc8 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 4 Dec 2024 18:05:29 +0100 Subject: [PATCH 539/640] rm org from temp --- apps/login/src/components/login-otp.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 02f698a425..015190d992 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -76,13 +76,6 @@ export function LoginOTP({ async function updateSessionForOTPChallenge() { let challenges; - if (host) { - console.log( - `${host.includes("localhost") ? "http://" : "https://"}${host}/otp/method=${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}&organization={{.OrgID}}` + - (authRequestId ? `&authRequestId=${authRequestId}` : ""), - ); - } - if (method === "email") { challenges = create(RequestChallengesSchema, { otpEmail: { @@ -91,7 +84,7 @@ export function LoginOTP({ value: host ? { urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}/otp/method=${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}&organization={{.OrgID}}` + + `${host.includes("localhost") ? "http://" : "https://"}${host}/otp/method=${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}` + (authRequestId ? `&authRequestId=${authRequestId}` : ""), } : {}, From a780233f2f40bdfa9a1bab99eb6176ffcdbca427 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 4 Dec 2024 18:12:57 +0100 Subject: [PATCH 540/640] rm log --- apps/login/src/app/(login)/otp/[method]/page.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index ccccf072e5..1c0904cee2 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -33,8 +33,6 @@ export default async function Page(props: { const host = (await headers()).get("host"); - console.log("host", host); - return (
    From 81468d3fbfa460eb7e924bd14c4ac93f9ca02d5a Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Thu, 5 Dec 2024 13:12:59 +0100 Subject: [PATCH 541/640] chore(acceptance): use prod secret on command test:acceptance:prod --- .github/workflows/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6872502afc..8b78e95af5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -109,7 +109,12 @@ jobs: if: ${{ matrix.command == 'test:acceptance' }} - name: Create Cloud Env File - run: echo "${{ matrix.command == 'test:acceptance:prod' && secrets.ENV_FILE_CONTENT_ACCEPTANCE_PROD || secrets.ENV_FILE_CONTENT_ACCEPTANCE_QA }}" | tee apps/login/.env.local acceptance/tests/.env.local > /dev/null + run: | + if [ "${{ matrix.command }}" == "test:acceptance:prod" ]; then + echo "${{ secrets.ENV_FILE_CONTENT_ACCEPTANCE_PROD }}" | tee apps/login/.env.local acceptance/tests/.env.local > /dev/null + else + echo "${{ secrets.ENV_FILE_CONTENT_ACCEPTANCE_QA }}" | tee apps/login/.env.local acceptance/tests/.env.local > /dev/null + fi if: ${{ matrix.command == 'test:acceptance:qa' || matrix.command == 'test:acceptance:prod' }} - name: Create Production Build From 1fc3e7250eaa4d42a3a1aa3cd1c8ad001a6a6d6d Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Thu, 5 Dec 2024 13:35:26 +0100 Subject: [PATCH 542/640] chore: add acceptance checkbox to pr template --- .github/pull_request_template.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0ea84c5c8a..b8733b5e06 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -7,6 +7,7 @@ - [ ] All open todos and follow ups are defined in a new ticket and justified - [ ] Deviations from the acceptance criteria and design are agreed with the PO and documented. - [ ] Vitest unit tests ensure that components produce expected outputs on different inputs. -- [ ] Cypress integration tests ensure that login app pages work as expected. The ZITADEL API is mocked. +- [ ] Cypress integration tests ensure that login app pages work as expected on good and bad user inputs, ZITADEL responses or IDP redirects. The ZITADEL API is mocked, IDP redirects are simulated. +- [ ] Playwright acceptances tests ensure that the happy paths of common user journeys work as expected. - [ ] No debug or dead code - [ ] My code has no repetitions From 4610ae8764c122ce82acecfa4e2fec2b768cd730 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Thu, 5 Dec 2024 13:43:16 +0100 Subject: [PATCH 543/640] simulated idp redirects --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b8733b5e06..138d4919af 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,6 +8,6 @@ - [ ] Deviations from the acceptance criteria and design are agreed with the PO and documented. - [ ] Vitest unit tests ensure that components produce expected outputs on different inputs. - [ ] Cypress integration tests ensure that login app pages work as expected on good and bad user inputs, ZITADEL responses or IDP redirects. The ZITADEL API is mocked, IDP redirects are simulated. -- [ ] Playwright acceptances tests ensure that the happy paths of common user journeys work as expected. +- [ ] Playwright acceptances tests ensure that the happy paths of common user journeys work as expected. The ZITADEL API is not mocked but IDP redirects are simulated. - [ ] No debug or dead code - [ ] My code has no repetitions From cd53ccb3b3e7e864a14fd142587b999c1d37846f Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 09:03:59 +0100 Subject: [PATCH 544/640] login hint --- apps/login/src/app/login/route.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 9ef4d00741..9531cebccb 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -1,5 +1,6 @@ import { getAllSessions } from "@/lib/cookies"; import { idpTypeToSlug } from "@/lib/idp"; +import { sendLoginname } from "@/lib/server/loginname"; import { createCallback, getActiveIdentityProviders, @@ -241,14 +242,19 @@ export async function GET(request: NextRequest) { if (authRequest.prompt.includes(Prompt.SELECT_ACCOUNT)) { return gotoAccounts(); } else if (authRequest.prompt.includes(Prompt.LOGIN)) { - // if prompt is login + // if a hint is provided, skip loginname page and jump to the next page + if (authRequest.loginHint) { + return sendLoginname({ + loginName: authRequest.loginHint, + organization, + authRequestId: authRequest.id, + }); + } + const loginNameUrl = new URL("/loginname", request.url); if (authRequest?.id) { loginNameUrl.searchParams.set("authRequestId", authRequest?.id); } - if (authRequest.loginHint) { - loginNameUrl.searchParams.set("loginName", authRequest.loginHint); - } if (organization) { loginNameUrl.searchParams.set("organization", organization); } From 1a7d97421f066c27ef648defcf77a2f4ad1892e5 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 09:14:43 +0100 Subject: [PATCH 545/640] handle error, use redirect response --- apps/login/src/app/login/route.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 9531cebccb..615d3547f2 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -244,17 +244,28 @@ export async function GET(request: NextRequest) { } else if (authRequest.prompt.includes(Prompt.LOGIN)) { // if a hint is provided, skip loginname page and jump to the next page if (authRequest.loginHint) { - return sendLoginname({ - loginName: authRequest.loginHint, - organization, - authRequestId: authRequest.id, - }); + try { + const res = await sendLoginname({ + loginName: authRequest.loginHint, + organization, + authRequestId: authRequest.id, + }); + + if (res?.redirect) { + return NextResponse.redirect(res.redirect); + } + } catch (error) { + console.error("Failed to execute sendLoginname:", error); + } } const loginNameUrl = new URL("/loginname", request.url); if (authRequest?.id) { loginNameUrl.searchParams.set("authRequestId", authRequest?.id); } + if (authRequest.loginHint) { + loginNameUrl.searchParams.set("loginName", authRequest.loginHint); + } if (organization) { loginNameUrl.searchParams.set("organization", organization); } From 2e2ae590f9d1d0a57079e336ddd0ccf8fb7a17ad Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 09:44:56 +0100 Subject: [PATCH 546/640] check for valid sessions, cleanup --- apps/login/src/app/login/route.ts | 194 +++++++++++++++++------------- 1 file changed, 111 insertions(+), 83 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 615d3547f2..a4bacd282f 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -9,7 +9,7 @@ import { listSessions, startIdentityProviderFlow, } from "@/lib/zitadel"; -import { create } from "@zitadel/client"; +import { create, timestampDate } from "@zitadel/client"; import { AuthRequest, Prompt, @@ -37,24 +37,51 @@ const ORG_SCOPE_REGEX = /urn:zitadel:iam:org:id:([0-9]+)/; const ORG_DOMAIN_SCOPE_REGEX = /urn:zitadel:iam:org:domain:primary:(.+)/; // TODO: check regex for all domain character options const IDP_SCOPE_REGEX = /urn:zitadel:iam:org:idp:id:(.+)/; -function findSession( +function isSessionValid(session: Session): boolean { + const validPassword = session?.factors?.password?.verifiedAt; + const validPasskey = session?.factors?.webAuthN?.verifiedAt; + const validIDP = session?.factors?.intent?.verifiedAt; + + const stillValid = session.expirationDate + ? timestampDate(session.expirationDate) > new Date() + : true; + + const validFactors = !!( + (validPassword || validPasskey || validIDP) && + stillValid + ); + + return stillValid && validFactors; +} + +function findValidSession( sessions: Session[], authRequest: AuthRequest, ): Session | undefined { - if (authRequest.hintUserId) { - console.log(`find session for hintUserId: ${authRequest.hintUserId}`); - return sessions.find((s) => s.factors?.user?.id === authRequest.hintUserId); + const validSessionsWithHint = sessions + .filter((s) => { + if (authRequest.hintUserId) { + return s.factors?.user?.id === authRequest.hintUserId; + } + if (authRequest.loginHint) { + return s.factors?.user?.loginName === authRequest.loginHint; + } + return false; + }) + .filter(isSessionValid); + + if (validSessionsWithHint.length === 0) { + return undefined; } - if (authRequest.loginHint) { - console.log(`find session for loginHint: ${authRequest.loginHint}`); - return sessions.find( - (s) => s.factors?.user?.loginName === authRequest.loginHint, - ); - } - if (sessions.length) { - return sessions[0]; - } - return undefined; + + validSessionsWithHint.sort((a, b) => { + const dateA = a.changeDate ? timestampDate(a.changeDate).getTime() : 0; + const dateB = b.changeDate ? timestampDate(b.changeDate).getTime() : 0; + return dateB - dateA; + }); + + // return most recently changed session + return sessions[0]; } export async function GET(request: NextRequest) { @@ -226,8 +253,8 @@ export async function GET(request: NextRequest) { if (authRequest && authRequest.prompt.includes(Prompt.CREATE)) { const registerUrl = new URL("/register", request.url); - if (authRequest?.id) { - registerUrl.searchParams.set("authRequestId", authRequest?.id); + if (authRequest.id) { + registerUrl.searchParams.set("authRequestId", authRequest.id); } if (organization) { registerUrl.searchParams.set("organization", organization); @@ -260,8 +287,8 @@ export async function GET(request: NextRequest) { } const loginNameUrl = new URL("/loginname", request.url); - if (authRequest?.id) { - loginNameUrl.searchParams.set("authRequestId", authRequest?.id); + if (authRequest.id) { + loginNameUrl.searchParams.set("authRequestId", authRequest.id); } if (authRequest.loginHint) { loginNameUrl.searchParams.set("loginName", authRequest.loginHint); @@ -272,81 +299,82 @@ export async function GET(request: NextRequest) { return NextResponse.redirect(loginNameUrl); } else if (authRequest.prompt.includes(Prompt.NONE)) { // NONE prompt - silent authentication + const selectedSession = findValidSession(sessions, authRequest); - let selectedSession = findSession(sessions, authRequest); - - if (selectedSession && selectedSession.id) { - const cookie = sessionCookies.find( - (cookie) => cookie.id === selectedSession?.id, - ); - - if (cookie && cookie.id && cookie.token) { - const session = { - sessionId: cookie?.id, - sessionToken: cookie?.token, - }; - const { callbackUrl } = await createCallback( - create(CreateCallbackRequestSchema, { - authRequestId, - callbackKind: { - case: "session", - value: create(SessionSchema, session), - }, - }), - ); - return NextResponse.redirect(callbackUrl); - } else { - return NextResponse.json( - { error: "No active session found" }, - { status: 400 }, // TODO: check for correct status code - ); - } - } else { + if (!selectedSession || !selectedSession.id) { return NextResponse.json( { error: "No active session found" }, - { status: 400 }, // TODO: check for correct status code + { status: 400 }, ); } - } else { - // check for loginHint, userId hint sessions - let selectedSession = findSession(sessions, authRequest); - if (selectedSession && selectedSession.id) { - const cookie = sessionCookies.find( - (cookie) => cookie.id === selectedSession?.id, + const cookie = sessionCookies.find( + (cookie) => cookie.id === selectedSession.id, + ); + + if (!cookie || !cookie.id || !cookie.token) { + return NextResponse.json( + { error: "No active session found" }, + { status: 400 }, ); + } - if (cookie && cookie.id && cookie.token) { - const session = { - sessionId: cookie?.id, - sessionToken: cookie?.token, - }; - try { - const { callbackUrl } = await createCallback( - create(CreateCallbackRequestSchema, { - authRequestId, - callbackKind: { - case: "session", - value: create(SessionSchema, session), - }, - }), - ); - if (callbackUrl) { - return NextResponse.redirect(callbackUrl); - } else { - console.log( - "could not create callback, redirect user to choose other account", - ); - return gotoAccounts(); - } - } catch (error) { - console.error(error); - return gotoAccounts(); - } + const session = { + sessionId: cookie.id, + sessionToken: cookie.token, + }; + + const { callbackUrl } = await createCallback( + create(CreateCallbackRequestSchema, { + authRequestId, + callbackKind: { + case: "session", + value: create(SessionSchema, session), + }, + }), + ); + return NextResponse.redirect(callbackUrl); + } else { + // check for loginHint, userId hint and valid sessions + let selectedSession = findValidSession(sessions, authRequest); + + if (!selectedSession || !selectedSession.id) { + return gotoAccounts(); + } + + const cookie = sessionCookies.find( + (cookie) => cookie.id === selectedSession.id, + ); + + if (!cookie || !cookie.id || !cookie.token) { + return gotoAccounts(); + } + + const session = { + sessionId: cookie.id, + sessionToken: cookie.token, + }; + + try { + const { callbackUrl } = await createCallback( + create(CreateCallbackRequestSchema, { + authRequestId, + callbackKind: { + case: "session", + value: create(SessionSchema, session), + }, + }), + ); + if (callbackUrl) { + return NextResponse.redirect(callbackUrl); } else { + console.log( + "could not create callback, redirect user to choose other account", + ); return gotoAccounts(); } - } else { + } catch (error) { + console.error(error); return gotoAccounts(); } } From c645bcfcfa62aecec566b185dfbec593bd568899 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 09:58:14 +0100 Subject: [PATCH 547/640] absolute url for login prompt --- apps/login/src/app/login/route.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index a4bacd282f..cf1e2fa156 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -279,7 +279,8 @@ export async function GET(request: NextRequest) { }); if (res?.redirect) { - return NextResponse.redirect(res.redirect); + const absoluteUrl = new URL(res.redirect, request.url); + return NextResponse.redirect(absoluteUrl.toString()); } } catch (error) { console.error("Failed to execute sendLoginname:", error); From 68066b99afa2e6619685a0a7e55c595304f12548 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 10:16:54 +0100 Subject: [PATCH 548/640] only filter sessions if hint is provided --- apps/login/src/app/login/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index cf1e2fa156..ae23dea679 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -66,7 +66,7 @@ function findValidSession( if (authRequest.loginHint) { return s.factors?.user?.loginName === authRequest.loginHint; } - return false; + return true; }) .filter(isSessionValid); From 2bc7629cca1572c15543ec0b0d7694e3a3b19e1b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 10:48:50 +0100 Subject: [PATCH 549/640] document, session item --- apps/login/src/app/login/route.ts | 10 +++++++++- apps/login/src/components/session-item.tsx | 12 +++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index ae23dea679..40200ef39f 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -269,6 +269,10 @@ export async function GET(request: NextRequest) { if (authRequest.prompt.includes(Prompt.SELECT_ACCOUNT)) { return gotoAccounts(); } else if (authRequest.prompt.includes(Prompt.LOGIN)) { + /** + * The login prompt instructs the authentication server to prompt the user for re-authentication, regardless of whether the user is already authenticated + */ + // if a hint is provided, skip loginname page and jump to the next page if (authRequest.loginHint) { try { @@ -299,7 +303,11 @@ export async function GET(request: NextRequest) { } return NextResponse.redirect(loginNameUrl); } else if (authRequest.prompt.includes(Prompt.NONE)) { - // NONE prompt - silent authentication + /** + * With an OIDC none prompt, the authentication server must not display any authentication or consent user interface pages. + * This means that the user should not be prompted to enter their password again. + * Instead, the server attempts to silently authenticate the user using an existing session or other authentication mechanisms that do not require user interaction + **/ const selectedSession = findValidSession(sessions, authRequest); if (!selectedSession || !selectedSession.id) { diff --git a/apps/login/src/components/session-item.tsx b/apps/login/src/components/session-item.tsx index 1845ea63bc..7cd5e9a7f9 100644 --- a/apps/login/src/components/session-item.tsx +++ b/apps/login/src/components/session-item.tsx @@ -114,11 +114,13 @@ export function SessionItem({ {verifiedAt && moment(timestampDate(verifiedAt)).fromNow()} ) : ( - - expired{" "} - {session.expirationDate && - moment(timestampDate(session.expirationDate)).fromNow()} - + verifiedAt && ( + + expired{" "} + {session.expirationDate && + moment(timestampDate(session.expirationDate)).fromNow()} + + ) )}
    From 40e51e11fe022ae159b76d3785d4da55edc3de2c Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 11:44:12 +0100 Subject: [PATCH 550/640] push instead of redirect --- apps/login/src/components/session-item.tsx | 6 +++++- apps/login/src/lib/server/session.ts | 3 +-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/login/src/components/session-item.tsx b/apps/login/src/components/session-item.tsx index 7cd5e9a7f9..ade3153c74 100644 --- a/apps/login/src/components/session-item.tsx +++ b/apps/login/src/components/session-item.tsx @@ -65,10 +65,14 @@ export function SessionItem({ diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 0afc4c4dc1..e273a24c65 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -445,11 +445,6 @@ export async function verifyEmail(userId: string, verificationCode: string) { ); } -/** - * - * @param userId the id of the user where the email should be set - * @returns the newly set email - */ export async function resendEmailCode(userId: string) { return userService.resendEmailCode( { From 2402a5473e1014a95a7bf57fe34bd77b06584ec8 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 14:14:23 +0100 Subject: [PATCH 556/640] catch already handled --- apps/login/src/app/login/route.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 23e9613522..057410ee12 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -48,6 +48,8 @@ async function isSessionValid( ): Promise { let mfaValid = true; if (checkLoginSettings && session.factors?.user?.organizationId) { + // TODO: check for auth methods of the user to know if the session has all required mfa methods + const loginSettings = await getLoginSettings( session.factors?.user?.organizationId, ); @@ -172,8 +174,33 @@ export async function GET(request: NextRequest) { { status: 500 }, ); } - } catch (error) { - return NextResponse.json({ error }, { status: 500 }); + } catch (error: unknown) { + // handle already handled gracefully as these could come up if old emails with authRequestId are used (reset password, register emails etc.) + console.error(error); + if ( + error && + typeof error === "object" && + "code" in error && + error?.code === 9 + ) { + const signedinUrl = new URL("/signedin", request.url); + + if (selectedSession.factors?.user?.loginName) { + signedinUrl.searchParams.set( + "loginName", + selectedSession.factors?.user?.loginName, + ); + } + if (selectedSession.factors?.user?.organizationId) { + signedinUrl.searchParams.set( + "organization", + selectedSession.factors?.user?.organizationId, + ); + } + return NextResponse.redirect(signedinUrl); + } else { + return NextResponse.json({ error }, { status: 500 }); + } } } } From 6690a451468bdc1960543b72dceef5f9d8dcb5b5 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 14:21:18 +0100 Subject: [PATCH 557/640] redirect to defaultredirect if authrequest is already handled --- apps/login/src/app/login/route.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 057410ee12..d3635bf396 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -183,6 +183,14 @@ export async function GET(request: NextRequest) { "code" in error && error?.code === 9 ) { + const loginSettings = await getLoginSettings( + selectedSession.factors?.user?.organizationId, + ); + + if (loginSettings?.defaultRedirectUri) { + return NextResponse.redirect(loginSettings.defaultRedirectUri); + } + const signedinUrl = new URL("/signedin", request.url); if (selectedSession.factors?.user?.loginName) { From 40b474821fb7ae228193b471dffa2e32f9958090 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 14:44:10 +0100 Subject: [PATCH 558/640] check user auth methods --- apps/login/src/app/login/route.ts | 44 ++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index d3635bf396..9eef8d1ae3 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -7,6 +7,7 @@ import { getAuthRequest, getLoginSettings, getOrgsByDomain, + listAuthenticationMethodTypes, listSessions, startIdentityProviderFlow, } from "@/lib/zitadel"; @@ -20,6 +21,7 @@ import { SessionSchema, } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { NextRequest, NextResponse } from "next/server"; export const dynamic = "force-dynamic"; @@ -42,14 +44,38 @@ const IDP_SCOPE_REGEX = /urn:zitadel:iam:org:idp:id:(.+)/; * mfa is required, session is not valid anymore (e.g. session expired, user logged out, etc.) * to check for mfa for automatically selected session -> const response = await listAuthenticationMethodTypes(userId); **/ -async function isSessionValid( - session: Session, - checkLoginSettings?: boolean, -): Promise { - let mfaValid = true; - if (checkLoginSettings && session.factors?.user?.organizationId) { - // TODO: check for auth methods of the user to know if the session has all required mfa methods +async function isSessionValid(session: Session): Promise { + // session can't be checked without user + if (!session.factors?.user) { + return false; + } + let mfaValid = true; + + const authMethodTypes = await listAuthenticationMethodTypes( + session.factors.user.id, + ); + + const authMethods = authMethodTypes.authMethodTypes; + if (authMethods && authMethods.includes(AuthenticationMethodType.TOTP)) { + mfaValid = !!session.factors.totp?.verifiedAt; + } else if ( + authMethods && + authMethods.includes(AuthenticationMethodType.OTP_EMAIL) + ) { + mfaValid = !!session.factors.otpEmail?.verifiedAt; + } else if ( + authMethods && + authMethods.includes(AuthenticationMethodType.OTP_SMS) + ) { + mfaValid = !!session.factors.otpSms?.verifiedAt; + } else if ( + authMethods && + authMethods.includes(AuthenticationMethodType.U2F) + ) { + mfaValid = !!session.factors.webAuthN?.verifiedAt; + } else { + // only check settings if no auth methods are available, as this would require a setup const loginSettings = await getLoginSettings( session.factors?.user?.organizationId, ); @@ -106,7 +132,7 @@ async function findValidSession( // return the first valid session according to settings for (const session of sessionsWithHint) { - if (await isSessionValid(session, true)) { + if (await isSessionValid(session)) { return session; } } @@ -142,7 +168,7 @@ export async function GET(request: NextRequest) { if (selectedSession && selectedSession.id) { console.log(`Found session ${selectedSession.id}`); - const isValid = await isSessionValid(selectedSession, true); + const isValid = await isSessionValid(selectedSession); if (isValid) { const cookie = sessionCookies.find( From a749c76f4f12483719d4f5f9cc04e9235365c7d1 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 14:57:39 +0100 Subject: [PATCH 559/640] let them reauthenticate with a bad session --- apps/login/src/app/login/route.ts | 128 +++++++++++++++++------------- 1 file changed, 71 insertions(+), 57 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 9eef8d1ae3..ffacab46e4 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -170,71 +170,85 @@ export async function GET(request: NextRequest) { const isValid = await isSessionValid(selectedSession); - if (isValid) { - const cookie = sessionCookies.find( - (cookie) => cookie.id === selectedSession?.id, - ); + if (!isValid && selectedSession.factors?.user) { + // if the session is not valid anymore, we need to redirect the user to re-authenticate + const command: SendLoginnameCommand = { + loginName: selectedSession.factors.user?.loginName, + organization: selectedSession.factors?.user?.organizationId, + authRequestId: authRequestId, + }; - if (cookie && cookie.id && cookie.token) { - const session = { - sessionId: cookie?.id, - sessionToken: cookie?.token, - }; + const res = await sendLoginname(command); - // works not with _rsc request - try { - const { callbackUrl } = await createCallback( - create(CreateCallbackRequestSchema, { - authRequestId, - callbackKind: { - case: "session", - value: create(SessionSchema, session), - }, - }), + if (res?.redirect) { + const absoluteUrl = new URL(res.redirect, request.url); + return NextResponse.redirect(absoluteUrl.toString()); + } + } + + const cookie = sessionCookies.find( + (cookie) => cookie.id === selectedSession?.id, + ); + + if (cookie && cookie.id && cookie.token) { + const session = { + sessionId: cookie?.id, + sessionToken: cookie?.token, + }; + + // works not with _rsc request + try { + const { callbackUrl } = await createCallback( + create(CreateCallbackRequestSchema, { + authRequestId, + callbackKind: { + case: "session", + value: create(SessionSchema, session), + }, + }), + ); + if (callbackUrl) { + return NextResponse.redirect(callbackUrl); + } else { + return NextResponse.json( + { error: "An error occurred!" }, + { status: 500 }, ); - if (callbackUrl) { - return NextResponse.redirect(callbackUrl); - } else { - return NextResponse.json( - { error: "An error occurred!" }, - { status: 500 }, + } + } catch (error: unknown) { + // handle already handled gracefully as these could come up if old emails with authRequestId are used (reset password, register emails etc.) + console.error(error); + if ( + error && + typeof error === "object" && + "code" in error && + error?.code === 9 + ) { + const loginSettings = await getLoginSettings( + selectedSession.factors?.user?.organizationId, + ); + + if (loginSettings?.defaultRedirectUri) { + return NextResponse.redirect(loginSettings.defaultRedirectUri); + } + + const signedinUrl = new URL("/signedin", request.url); + + if (selectedSession.factors?.user?.loginName) { + signedinUrl.searchParams.set( + "loginName", + selectedSession.factors?.user?.loginName, ); } - } catch (error: unknown) { - // handle already handled gracefully as these could come up if old emails with authRequestId are used (reset password, register emails etc.) - console.error(error); - if ( - error && - typeof error === "object" && - "code" in error && - error?.code === 9 - ) { - const loginSettings = await getLoginSettings( + if (selectedSession.factors?.user?.organizationId) { + signedinUrl.searchParams.set( + "organization", selectedSession.factors?.user?.organizationId, ); - - if (loginSettings?.defaultRedirectUri) { - return NextResponse.redirect(loginSettings.defaultRedirectUri); - } - - const signedinUrl = new URL("/signedin", request.url); - - if (selectedSession.factors?.user?.loginName) { - signedinUrl.searchParams.set( - "loginName", - selectedSession.factors?.user?.loginName, - ); - } - if (selectedSession.factors?.user?.organizationId) { - signedinUrl.searchParams.set( - "organization", - selectedSession.factors?.user?.organizationId, - ); - } - return NextResponse.redirect(signedinUrl); - } else { - return NextResponse.json({ error }, { status: 500 }); } + return NextResponse.redirect(signedinUrl); + } else { + return NextResponse.json({ error }, { status: 500 }); } } } From 41f7c5a4b38dacdf2d8fd75ce3a76dcaac2d25fb Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 15:12:51 +0100 Subject: [PATCH 560/640] wait for 2 seconds --- apps/login/src/components/login-otp.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 01ccee1bf0..d8afe203db 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -182,6 +182,9 @@ export function LoginOTP({ function setCodeAndContinue(values: Inputs, organization?: string) { return submitCode(values, organization).then(async (response) => { + // Wait for 2 seconds to avoid eventual consistency issues with an OTP code being verified in the /login endpoint + await new Promise((resolve) => setTimeout(resolve, 2000)); + if (response) { const url = authRequestId && response.sessionId From 096486ac556fbde6d2c3085c851d671be2d7fffc Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 15:27:03 +0100 Subject: [PATCH 561/640] log issues when validating --- apps/login/src/app/login/route.ts | 46 +++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index ffacab46e4..25eb87e384 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -47,6 +47,7 @@ const IDP_SCOPE_REGEX = /urn:zitadel:iam:org:idp:id:(.+)/; async function isSessionValid(session: Session): Promise { // session can't be checked without user if (!session.factors?.user) { + console.warn("Session has no user"); return false; } @@ -59,21 +60,45 @@ async function isSessionValid(session: Session): Promise { const authMethods = authMethodTypes.authMethodTypes; if (authMethods && authMethods.includes(AuthenticationMethodType.TOTP)) { mfaValid = !!session.factors.totp?.verifiedAt; + if (!mfaValid) { + console.warn( + "Session has no valid totpEmail factor", + session.factors.totp?.verifiedAt, + ); + } } else if ( authMethods && authMethods.includes(AuthenticationMethodType.OTP_EMAIL) ) { mfaValid = !!session.factors.otpEmail?.verifiedAt; + if (!mfaValid) { + console.warn( + "Session has no valid otpEmail factor", + session.factors.otpEmail?.verifiedAt, + ); + } } else if ( authMethods && authMethods.includes(AuthenticationMethodType.OTP_SMS) ) { mfaValid = !!session.factors.otpSms?.verifiedAt; + if (!mfaValid) { + console.warn( + "Session has no valid otpSms factor", + session.factors.otpSms?.verifiedAt, + ); + } } else if ( authMethods && authMethods.includes(AuthenticationMethodType.U2F) ) { mfaValid = !!session.factors.webAuthN?.verifiedAt; + if (!mfaValid) { + console.warn( + "Session has no valid u2f factor", + session.factors.webAuthN?.verifiedAt, + ); + } } else { // only check settings if no auth methods are available, as this would require a setup const loginSettings = await getLoginSettings( @@ -87,6 +112,12 @@ async function isSessionValid(session: Session): Promise { // must have one single check mfaValid = !!(otpEmail || otpSms || totp || webAuthN); + if (!mfaValid) { + console.warn( + "Session has no valid multifactor", + JSON.stringify(session.factors), + ); + } } else { mfaValid = true; } @@ -97,12 +128,21 @@ async function isSessionValid(session: Session): Promise { const validIDP = session?.factors?.intent?.verifiedAt; const stillValid = session.expirationDate - ? timestampDate(session.expirationDate) > new Date() + ? timestampDate(session.expirationDate).getTime() > new Date().getTime() : true; - const validFactors = !!(validPassword || validPasskey || validIDP); + if (!stillValid) { + console.warn( + "Session is expired", + session.expirationDate + ? timestampDate(session.expirationDate).toDateString() + : "no expiration date", + ); + } - return stillValid && validFactors && mfaValid; + const validChecks = !!(validPassword || validPasskey || validIDP); + + return stillValid && validChecks && mfaValid; } async function findValidSession( From 574e8de7b669b025d59b65d77735c050134fcfc5 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 15:31:00 +0100 Subject: [PATCH 562/640] wait for longer --- apps/login/src/components/login-otp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index d8afe203db..d63f4f9149 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -183,7 +183,7 @@ export function LoginOTP({ function setCodeAndContinue(values: Inputs, organization?: string) { return submitCode(values, organization).then(async (response) => { // Wait for 2 seconds to avoid eventual consistency issues with an OTP code being verified in the /login endpoint - await new Promise((resolve) => setTimeout(resolve, 2000)); + await new Promise((resolve) => setTimeout(resolve, 4000)); if (response) { const url = From 275233e4e1f3abb6b620fbb9ef72b9c123ab10e3 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 9 Dec 2024 15:40:37 +0100 Subject: [PATCH 563/640] session context --- apps/login/src/app/(login)/otp/[method]/page.tsx | 9 +++++++-- apps/login/src/components/login-otp.tsx | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 1c0904cee2..64b7c9ed2e 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -27,9 +27,14 @@ export default async function Page(props: { organization, }); - const branding = await getBrandingSettings(organization); + // email links do not come with organization, thus we need to use the session's organization + const branding = await getBrandingSettings( + organization ?? session?.factors?.user?.organizationId, + ); - const loginSettings = await getLoginSettings(organization); + const loginSettings = await getLoginSettings( + organization ?? session?.factors?.user?.organizationId, + ); const host = (await headers()).get("host"); diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index d63f4f9149..1c867265ee 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -182,6 +182,7 @@ export function LoginOTP({ function setCodeAndContinue(values: Inputs, organization?: string) { return submitCode(values, organization).then(async (response) => { + setLoading(true); // Wait for 2 seconds to avoid eventual consistency issues with an OTP code being verified in the /login endpoint await new Promise((resolve) => setTimeout(resolve, 4000)); @@ -206,6 +207,7 @@ export function LoginOTP({ ) : null; + setLoading(false); if (url) { router.push(url); } From 13a1dc30c007576b587dd4bca7b41f07dee64595 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 10 Dec 2024 09:03:45 +0100 Subject: [PATCH 564/640] provide more context --- .../src/app/(login)/otp/[method]/page.tsx | 45 ++++++++++++++----- apps/login/src/components/login-otp.tsx | 2 +- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 64b7c9ed2e..1755561238 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -2,8 +2,13 @@ import { Alert } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { LoginOTP } from "@/components/login-otp"; import { UserAvatar } from "@/components/user-avatar"; +import { getSessionCookieById } from "@/lib/cookies"; import { loadMostRecentSession } from "@/lib/session"; -import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel"; +import { + getBrandingSettings, + getLoginSettings, + getSession, +} from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; @@ -17,15 +22,33 @@ export default async function Page(props: { const t = await getTranslations({ locale, namespace: "otp" }); const tError = await getTranslations({ locale, namespace: "error" }); - const { loginName, authRequestId, sessionId, organization, code, submit } = - searchParams; + const { + loginName, // send from password page + userId, // send from email link + authRequestId, + sessionId, + organization, + code, + submit, + } = searchParams; const { method } = params; - const session = await loadMostRecentSession({ - loginName, - organization, - }); + const session = sessionId + ? await loadSessionById(sessionId, organization) + : await loadMostRecentSession({ loginName, organization }); + + async function loadSessionById(sessionId: string, organization?: string) { + const recent = await getSessionCookieById({ sessionId, organization }); + return getSession({ + sessionId: recent.id, + sessionToken: recent.token, + }).then((response) => { + if (response?.session) { + return response.session; + } + }); + } // email links do not come with organization, thus we need to use the session's organization const branding = await getBrandingSettings( @@ -67,12 +90,14 @@ export default async function Page(props: { > )} - {method && ( + {method && session && ( { setLoading(true); // Wait for 2 seconds to avoid eventual consistency issues with an OTP code being verified in the /login endpoint - await new Promise((resolve) => setTimeout(resolve, 4000)); + await new Promise((resolve) => setTimeout(resolve, 2000)); if (response) { const url = From 4187028e7b23a9b135ddeada4024820c4c75b39a Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 10 Dec 2024 09:20:59 +0100 Subject: [PATCH 565/640] do not propagate click event --- apps/login/readme.md | 1 + apps/login/src/components/session-item.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/login/readme.md b/apps/login/readme.md index 5aaa30d131..f206fbf482 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -395,3 +395,4 @@ Timebased features like the multifactor init prompt or password expiry, are not - Password Expiry Settings - Login Settings: multifactor init prompt - forceMFA on login settings is not checked for IDPs +- disablePhone / disableEmail from loginSettings will be implemented right after https://github.com/zitadel/zitadel/issues/9016 is merged diff --git a/apps/login/src/components/session-item.tsx b/apps/login/src/components/session-item.tsx index ade3153c74..449f7dc955 100644 --- a/apps/login/src/components/session-item.tsx +++ b/apps/login/src/components/session-item.tsx @@ -140,6 +140,7 @@ export function SessionItem({ className="hidden group-hover:block h-5 w-5 transition-all opacity-50 hover:opacity-100" onClick={(event) => { event.preventDefault(); + event.stopPropagation(); clearSession(session.id).then(() => { reload(); }); From 60218306d12b662ad3e874f117a485763bedca54 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 10 Dec 2024 09:59:37 +0100 Subject: [PATCH 566/640] fix url template --- apps/login/src/components/login-otp.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index c010c89d62..0b0f8c2f0c 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -84,7 +84,7 @@ export function LoginOTP({ value: host ? { urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}/otp/method=${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}` + + `${host.includes("localhost") ? "http://" : "https://"}${host}/otp/${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}` + (authRequestId ? `&authRequestId=${authRequestId}` : ""), } : {}, @@ -182,11 +182,11 @@ export function LoginOTP({ function setCodeAndContinue(values: Inputs, organization?: string) { return submitCode(values, organization).then(async (response) => { - setLoading(true); - // Wait for 2 seconds to avoid eventual consistency issues with an OTP code being verified in the /login endpoint - await new Promise((resolve) => setTimeout(resolve, 2000)); + if (response && "sessionId" in response) { + setLoading(true); + // Wait for 2 seconds to avoid eventual consistency issues with an OTP code being verified in the /login endpoint + await new Promise((resolve) => setTimeout(resolve, 2000)); - if (response) { const url = authRequestId && response.sessionId ? await getNextUrl( From 7dbf2ab6c29f37cc56c5b94aaea357dadaa46903 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 10 Dec 2024 10:07:54 +0100 Subject: [PATCH 567/640] do not rerequest code --- apps/login/src/components/login-otp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 0b0f8c2f0c..21d895e370 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -59,7 +59,7 @@ export function LoginOTP({ }); useEffect(() => { - if (!initialized.current && ["email", "sms"].includes(method)) { + if (!initialized.current && ["email", "sms"].includes(method) && !code) { initialized.current = true; setLoading(true); updateSessionForOTPChallenge() From 6e6c667d5ff16b4108c34410244aff22fa7fad4b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 10 Dec 2024 10:57:26 +0100 Subject: [PATCH 568/640] resend switch --- apps/login/src/lib/server/email.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/login/src/lib/server/email.ts b/apps/login/src/lib/server/email.ts index e5a1529660..51eb1a019b 100644 --- a/apps/login/src/lib/server/email.ts +++ b/apps/login/src/lib/server/email.ts @@ -84,8 +84,8 @@ type resendVerifyEmailCommand = { export async function resendVerification(command: resendVerifyEmailCommand) { return command.isInvite - ? resendEmailCode(command.userId) - : resendInviteCode(command.userId); + ? resendInviteCode(command.userId) + : resendEmailCode(command.userId); } export async function sendVerificationRedirectWithoutCheck(command: { From 44354b3a85f375e3941aa9129264c567ae2d3dd4 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 10 Dec 2024 11:18:51 +0100 Subject: [PATCH 569/640] complete redirect on password change --- apps/login/src/components/change-password-form.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/login/src/components/change-password-form.tsx b/apps/login/src/components/change-password-form.tsx index e43e71f752..416966c62b 100644 --- a/apps/login/src/components/change-password-form.tsx +++ b/apps/login/src/components/change-password-form.tsx @@ -12,6 +12,7 @@ import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { useTranslations } from "next-intl"; +import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; import { Alert } from "./alert"; @@ -44,6 +45,7 @@ export function ChangePasswordForm({ organization, }: Props) { const t = useTranslations("password"); + const router = useRouter(); const { register, handleSubmit, watch, formState } = useForm({ mode: "onBlur", @@ -107,6 +109,14 @@ export function ChangePasswordForm({ return; } + if ( + passwordResponse && + "redirect" in passwordResponse && + passwordResponse.redirect + ) { + return router.push(passwordResponse.redirect); + } + return; } From 20c85bb97b4e0fcc933d4f8ff5641bbeb0fb40fa Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 11 Dec 2024 08:52:56 +0100 Subject: [PATCH 570/640] rm stringify --- apps/login/src/app/login/route.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 25eb87e384..61cc6e5cb5 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -113,10 +113,7 @@ async function isSessionValid(session: Session): Promise { // must have one single check mfaValid = !!(otpEmail || otpSms || totp || webAuthN); if (!mfaValid) { - console.warn( - "Session has no valid multifactor", - JSON.stringify(session.factors), - ); + console.warn("Session has no valid multifactor", session.factors); } } else { mfaValid = true; From fb331ce935ebe1f93390232fd3e160aa5d4aaa26 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 11 Dec 2024 09:20:40 +0100 Subject: [PATCH 571/640] improve language handling --- apps/login/src/app/sessions/route.ts | 30 ---------------------------- apps/login/src/i18n/request.ts | 22 +++++++++++++++++--- apps/login/src/lib/i18n.ts | 1 + 3 files changed, 20 insertions(+), 33 deletions(-) delete mode 100644 apps/login/src/app/sessions/route.ts diff --git a/apps/login/src/app/sessions/route.ts b/apps/login/src/app/sessions/route.ts deleted file mode 100644 index 9f7655664d..0000000000 --- a/apps/login/src/app/sessions/route.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getAllSessions } from "@/lib/cookies"; -import { listSessions } from "@/lib/zitadel"; -import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { NextRequest, NextResponse } from "next/server"; - -async function loadSessions(ids: string[]): Promise { - const response = await listSessions( - ids.filter((id: string | undefined) => !!id), - ); - - return response?.sessions ?? []; -} - -export async function GET(request: NextRequest) { - const sessionCookies = await getAllSessions(); - const ids = sessionCookies.map((s) => s.id); - let sessions: Session[] = []; - if (ids && ids.length) { - sessions = await loadSessions(ids); - } - - const responseHeaders = new Headers(); - responseHeaders.set("Access-Control-Allow-Origin", "*"); - responseHeaders.set("Access-Control-Allow-Headers", "*"); - - return NextResponse.json( - { sessions }, - { status: 200, headers: responseHeaders }, - ); -} diff --git a/apps/login/src/i18n/request.ts b/apps/login/src/i18n/request.ts index 2641c0fa26..59c9da42cc 100644 --- a/apps/login/src/i18n/request.ts +++ b/apps/login/src/i18n/request.ts @@ -1,12 +1,28 @@ -import { LANGUAGE_COOKIE_NAME } from "@/lib/i18n"; +import { LANGS, LANGUAGE_COOKIE_NAME, LANGUAGE_HEADER_NAME } from "@/lib/i18n"; import deepmerge from "deepmerge"; import { getRequestConfig } from "next-intl/server"; -import { cookies } from "next/headers"; +import { cookies, headers } from "next/headers"; export default getRequestConfig(async () => { const fallback = "en"; const cookiesList = await cookies(); - const locale: string = cookiesList.get(LANGUAGE_COOKIE_NAME)?.value ?? "en"; + + let locale: string = fallback; + + const languageHeader = await (await headers()).get(LANGUAGE_HEADER_NAME); + if (languageHeader) { + const headerLocale = languageHeader.split(",")[0].split("-")[0]; // Extract the language code + if (LANGS.map((l) => l.code).includes(headerLocale)) { + locale = headerLocale; + } + } + + const languageCookie = cookiesList?.get(LANGUAGE_COOKIE_NAME); + if (languageCookie && languageCookie.value) { + if (LANGS.map((l) => l.code).includes(languageCookie.value)) { + locale = languageCookie.value; + } + } const userMessages = (await import(`../../locales/${locale}.json`)).default; const fallbackMessages = (await import(`../../locales/${fallback}.json`)) diff --git a/apps/login/src/lib/i18n.ts b/apps/login/src/lib/i18n.ts index aba1d3069c..6d41256077 100644 --- a/apps/login/src/lib/i18n.ts +++ b/apps/login/src/lib/i18n.ts @@ -23,3 +23,4 @@ export const LANGS: Lang[] = [ ]; export const LANGUAGE_COOKIE_NAME = "NEXT_LOCALE"; +export const LANGUAGE_HEADER_NAME = "accept-language"; From 1a26192a0d6b14ac56702610cf85e148f01c3552 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 11 Dec 2024 09:56:49 +0100 Subject: [PATCH 572/640] missing zh translations --- apps/login/locales/zh.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/login/locales/zh.json b/apps/login/locales/zh.json index 68348b3fe7..e09fe04215 100644 --- a/apps/login/locales/zh.json +++ b/apps/login/locales/zh.json @@ -122,6 +122,18 @@ } }, "register": { + "methods": { + "passkey": "密钥", + "password": "密码" + }, + "disabled": { + "title": "注册已禁用", + "description": "您的设置不允许注册新用户。" + }, + "missingdata": { + "title": "缺少数据", + "description": "请提供所有必需的数据。" + }, "title": "注册", "description": "创建您的 ZITADEL 账户。", "selectMethod": "选择您想使用的认证方法", @@ -151,7 +163,8 @@ }, "signedin": { "title": "欢迎 {user}!", - "description": "您已登录。" + "description": "您已登录。", + "continue": "继续" }, "verify": { "userIdMissing": "未提供用户 ID!", From b68ea3274814aab4a830eeb0be84198f88a3d82f Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 12 Dec 2024 16:37:58 +0100 Subject: [PATCH 573/640] let users change their password if mfa is enforce but no mfa is yet set --- .../src/app/(login)/password/change/page.tsx | 5 +- .../src/components/change-password-form.tsx | 19 ++-- apps/login/src/lib/self.ts | 14 +-- apps/login/src/lib/zitadel.ts | 90 +++++++++++++++++++ 4 files changed, 111 insertions(+), 17 deletions(-) diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx index 1ba48a589d..d20b8b935c 100644 --- a/apps/login/src/app/(login)/password/change/page.tsx +++ b/apps/login/src/app/(login)/password/change/page.tsx @@ -32,7 +32,9 @@ export default async function Page(props: { sessionFactors?.factors?.user?.organizationId, ); - const loginSettings = await getLoginSettings(organization); + const loginSettings = await getLoginSettings( + sessionFactors?.factors?.user?.organizationId, + ); return ( @@ -68,6 +70,7 @@ export default async function Page(props: { authRequestId={authRequestId} organization={organization} passwordComplexitySettings={passwordComplexity} + loginSettings={loginSettings} /> ) : (
    diff --git a/apps/login/src/components/change-password-form.tsx b/apps/login/src/components/change-password-form.tsx index 416966c62b..5cb794a473 100644 --- a/apps/login/src/components/change-password-form.tsx +++ b/apps/login/src/components/change-password-form.tsx @@ -6,10 +6,11 @@ import { symbolValidator, upperCaseValidator, } from "@/helpers/validators"; -import { setMyPassword } from "@/lib/self"; import { sendPassword } from "@/lib/server/password"; +import { checkSessionAndSetPassword } from "@/lib/zitadel"; import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; @@ -31,6 +32,7 @@ type Inputs = type Props = { passwordComplexitySettings: PasswordComplexitySettings; + loginSettings?: LoginSettings; sessionId: string; loginName: string; authRequestId?: string; @@ -39,6 +41,7 @@ type Props = { export function ChangePasswordForm({ passwordComplexitySettings, + loginSettings, sessionId, loginName, authRequestId, @@ -60,9 +63,11 @@ export function ChangePasswordForm({ async function submitChange(values: Inputs) { setLoading(true); - const changeResponse = await setMyPassword({ - sessionId: sessionId, + + const changeResponse = checkSessionAndSetPassword({ + sessionId, password: values.password, + forceMfa: !!(loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly), }) .catch(() => { setError("Could not change password"); @@ -72,8 +77,12 @@ export function ChangePasswordForm({ setLoading(false); }); - if (changeResponse && "error" in changeResponse) { - setError(changeResponse.error); + if (changeResponse && "error" in changeResponse && changeResponse.error) { + setError( + typeof changeResponse.error === "string" + ? changeResponse.error + : "Unknown error", + ); return; } diff --git a/apps/login/src/lib/self.ts b/apps/login/src/lib/self.ts index ebe08fe518..6eea8206c9 100644 --- a/apps/login/src/lib/self.ts +++ b/apps/login/src/lib/self.ts @@ -1,9 +1,6 @@ "use server"; -import { - createSessionServiceClient, - createUserServiceClient, -} from "@zitadel/client/v2"; +import { createUserServiceClient } from "@zitadel/client/v2"; import { createServerTransport } from "@zitadel/node"; import { getSessionCookieById } from "./cookies"; import { getSession } from "./zitadel"; @@ -13,12 +10,6 @@ const transport = (token: string) => baseUrl: process.env.ZITADEL_API_URL!, }); -const sessionService = (sessionId: string) => { - return getSessionCookieById({ sessionId }).then((session) => { - return createSessionServiceClient(transport(session.token)); - }); -}; - const myUserService = (sessionToken: string) => { return createUserServiceClient(transport(sessionToken)); }; @@ -41,7 +32,7 @@ export async function setMyPassword({ return { error: "Could not load session" }; } - const service = await myUserService(sessionCookie.token); + const service = await myUserService(`${sessionCookie.token}`); if (!session?.factors?.user?.id) { return { error: "No user id found in session" }; @@ -56,6 +47,7 @@ export async function setMyPassword({ {}, ) .catch((error) => { + console.log(error); if (error.code === 7) { return { error: "Session is not valid." }; } diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index e273a24c65..3740c578da 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -12,6 +12,7 @@ import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_p import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { + AuthenticationMethodType, RetrieveIdentityProviderIntentRequest, SetPasswordRequestSchema, VerifyPasskeyRegistrationRequest, @@ -38,6 +39,7 @@ import { UserState, } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { unstable_cacheLife as cacheLife } from "next/cache"; +import { getSessionCookieById } from "./cookies"; import { PROVIDER_MAPPING } from "./idp"; const transport = createServerTransport( @@ -582,6 +584,94 @@ export async function setPassword( }); } +type CheckSessionAndSetPasswordCommand = { + sessionId: string; + password: string; + forceMfa: boolean; +}; + +export async function checkSessionAndSetPassword({ + sessionId, + password, + forceMfa, +}: CheckSessionAndSetPasswordCommand) { + const sessionCookie = await getSessionCookieById({ sessionId }); + + const { session } = await getSession({ + sessionId: sessionCookie.id, + sessionToken: sessionCookie.token, + }); + + if (!session || !session.factors?.user?.id) { + return { error: "Could not load session" }; + } + + const payload = create(SetPasswordRequestSchema, { + userId: session.factors.user.id, + newPassword: { + password, + }, + }); + + // check if the user has no password set in order to set a password + const authmethods = await listAuthenticationMethodTypes( + session.factors.user.id, + ); + + if (!authmethods) { + return { error: "Could not load auth methods" }; + } + + const requiredAuthMethodsForForceMFA = [ + AuthenticationMethodType.OTP_EMAIL, + AuthenticationMethodType.OTP_SMS, + AuthenticationMethodType.TOTP, + AuthenticationMethodType.U2F, + ]; + + const hasNoMFAMethods = requiredAuthMethodsForForceMFA.every( + (method) => !authmethods.authMethodTypes.includes(method), + ); + + // if the user has no MFA but MFA is enforced, we can set a password otherwise we use the token of the user + if (forceMfa && hasNoMFAMethods) { + return userService.setPassword(payload, {}).catch((error) => { + // throw error if failed precondition (ex. User is not yet initialized) + if (error.code === 9 && error.message) { + return { error: "Failed precondition" }; + } else { + throw error; + } + }); + } else { + const myUserService = (sessionToken: string) => { + return createUserServiceClient( + createServerTransport(sessionToken, { + baseUrl: process.env.ZITADEL_API_URL!, + }), + ); + }; + + const selfService = await myUserService(`${sessionCookie.token}`); + + return selfService + .setPassword( + { + userId: session.factors.user.id, + newPassword: { password, changeRequired: false }, + }, + {}, + ) + .catch((error) => { + console.log(error); + if (error.code === 7) { + return { error: "Session is not valid." }; + } + throw error; + }); + } +} + /** * * @param server From 911edd39b0fb1a8e9591b626088b31e1e4803ebb Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 12 Dec 2024 17:46:11 +0100 Subject: [PATCH 574/640] streamlined resend code buttons --- apps/login/locales/de.json | 2 + apps/login/locales/en.json | 2 + apps/login/locales/es.json | 2 + apps/login/locales/it.json | 2 + apps/login/locales/zh.json | 2 + .../src/components/set-password-form.tsx | 52 +++++++++++-------- apps/login/src/components/verify-form.tsx | 30 ++++++++--- 7 files changed, 62 insertions(+), 30 deletions(-) diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 4d5f0c1530..ba0fce27e3 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -24,6 +24,7 @@ "title": "Passwort festlegen", "description": "Legen Sie das Passwort für Ihr Konto fest", "codeSent": "Ein Code wurde an Ihre E-Mail-Adresse gesendet.", + "noCodeReceived": "Keinen Code erhalten?", "resend": "Erneut senden", "submit": "Weiter" }, @@ -173,6 +174,7 @@ "verify": { "title": "Benutzer verifizieren", "description": "Geben Sie den Code ein, der in der Bestätigungs-E-Mail angegeben ist.", + "noCodeReceived": "Keinen Code erhalten?", "resendCode": "Code erneut senden", "submit": "Weiter" } diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index c7fd5e30b9..265304387f 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -24,6 +24,7 @@ "title": "Set Password", "description": "Set the password for your account", "codeSent": "A code has been sent to your email address.", + "noCodeReceived": "Didn't receive a code?", "resend": "Resend code", "submit": "Continue" }, @@ -173,6 +174,7 @@ "verify": { "title": "Verify user", "description": "Enter the Code provided in the verification email.", + "noCodeReceived": "Didn't receive a code?", "resendCode": "Resend code", "submit": "Continue" } diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index e722db5812..35bbb9385d 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -24,6 +24,7 @@ "title": "Establecer Contraseña", "description": "Establece la contraseña para tu cuenta", "codeSent": "Se ha enviado un código a su correo electrónico.", + "noCodeReceived": "¿No recibiste un código?", "resend": "Reenviar código", "submit": "Continuar" }, @@ -173,6 +174,7 @@ "verify": { "title": "Verificar usuario", "description": "Introduce el código proporcionado en el correo electrónico de verificación.", + "noCodeReceived": "¿No recibiste un código?", "resendCode": "Reenviar código", "submit": "Continuar" } diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index 9467f0ba84..4e21e3dc9d 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -24,6 +24,7 @@ "title": "Imposta Password", "description": "Imposta la password per il tuo account", "codeSent": "Un codice è stato inviato al tuo indirizzo email.", + "noCodeReceived": "Non hai ricevuto un codice?", "resend": "Invia di nuovo", "submit": "Continua" }, @@ -173,6 +174,7 @@ "verify": { "title": "Verifica utente", "description": "Inserisci il codice fornito nell'email di verifica.", + "noCodeReceived": "Non hai ricevuto un codice?", "resendCode": "Invia di nuovo il codice", "submit": "Continua" } diff --git a/apps/login/locales/zh.json b/apps/login/locales/zh.json index e09fe04215..818a8b449e 100644 --- a/apps/login/locales/zh.json +++ b/apps/login/locales/zh.json @@ -24,6 +24,7 @@ "title": "设置密码", "description": "为您的账户设置密码", "codeSent": "验证码已发送到您的邮箱。", + "noCodeReceived": "没有收到验证码?", "resend": "重发验证码", "submit": "继续" }, @@ -173,6 +174,7 @@ "verify": { "title": "验证用户", "description": "输入验证邮件中的验证码。", + "noCodeReceived": "没有收到验证码?", "resendCode": "重发验证码", "submit": "继续" } diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index d2e5c73940..fd801640d7 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -18,7 +18,7 @@ import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; -import { Alert } from "./alert"; +import { Alert, AlertType } from "./alert"; import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; @@ -192,31 +192,39 @@ export function SetPasswordForm({
    {codeRequired && ( -
    -
    - -
    - -
    - +
    + + )} + {codeRequired && ( +
    +
    )}
    diff --git a/apps/login/src/components/verify-form.tsx b/apps/login/src/components/verify-form.tsx index 377d5b9cfc..308732cba9 100644 --- a/apps/login/src/components/verify-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -1,11 +1,12 @@ "use client"; -import { Alert } from "@/components/alert"; +import { Alert, AlertType } from "@/components/alert"; import { resendVerification, sendVerification } from "@/lib/server/email"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; +import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; @@ -96,6 +97,25 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { return ( <> + +
    + + {t("verify.noCodeReceived")} + + +
    +
    - + +
    -
    - -
    -
    - + + ); } diff --git a/apps/login/src/components/idps/pages/linking-failed.tsx b/apps/login/src/components/idps/pages/linking-failed.tsx new file mode 100644 index 0000000000..94678fb293 --- /dev/null +++ b/apps/login/src/components/idps/pages/linking-failed.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { LanguageProvider } from "@/components/language-provider"; +import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; +import { useTranslations } from "next-intl"; +import { Alert, AlertType } from "../../alert"; +import { DynamicTheme } from "../../dynamic-theme"; + +export function linkingFailed(branding?: BrandingSettings) { + const t = useTranslations("idp"); + + return ( + + +
    +

    {t("linkingError.title")}

    +
    + { + + {t("linkingError.description")} + + } +
    +
    +
    +
    + ); +} diff --git a/apps/login/src/components/idps/pages/linking-success.tsx b/apps/login/src/components/idps/pages/linking-success.tsx new file mode 100644 index 0000000000..39671d8f60 --- /dev/null +++ b/apps/login/src/components/idps/pages/linking-success.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; +import { useTranslations } from "next-intl"; +import { DynamicTheme } from "../../dynamic-theme"; +import { IdpSignin } from "../../idp-signin"; + +export function linkingSuccess( + userId: string, + idpIntent: { idpIntentId: string; idpIntentToken: string }, + authRequestId?: string, + branding?: BrandingSettings, +) { + const t = useTranslations("idp"); + + return ( + +
    +

    {t("linkingSuccess.title")}

    +

    {t("linkingSuccess.description")}

    + + +
    +
    + ); +} diff --git a/apps/login/src/components/idps/pages/login-failed.tsx b/apps/login/src/components/idps/pages/login-failed.tsx new file mode 100644 index 0000000000..5bddf29a08 --- /dev/null +++ b/apps/login/src/components/idps/pages/login-failed.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { LanguageProvider } from "@/components/language-provider"; +import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; +import { useTranslations } from "next-intl"; +import { Alert, AlertType } from "../../alert"; +import { DynamicTheme } from "../../dynamic-theme"; + +export function loginFailed(branding?: BrandingSettings, error: string = "") { + const t = useTranslations("idp"); + + return ( + + +
    +

    {t("loginError.title")}

    +

    {t("loginError.description")}

    + {error && ( +
    + {{error}} +
    + )} +
    +
    +
    + ); +} diff --git a/apps/login/src/components/idps/pages/login-success.tsx b/apps/login/src/components/idps/pages/login-success.tsx new file mode 100644 index 0000000000..39aa71c642 --- /dev/null +++ b/apps/login/src/components/idps/pages/login-success.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { LanguageProvider } from "@/components/language-provider"; +import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; +import { useTranslations } from "next-intl"; +import { DynamicTheme } from "../../dynamic-theme"; +import { IdpSignin } from "../../idp-signin"; + +export function loginSuccess( + userId: string, + idpIntent: { idpIntentId: string; idpIntentToken: string }, + authRequestId?: string, + branding?: BrandingSettings, +) { + const t = useTranslations("idp"); + + return ( + + +
    +

    {t("loginSuccess.title")}

    +

    {t("loginSuccess.description")}

    + + +
    +
    +
    + ); +} From d3111d3ba3f1bb6c64d8edaa05b14f412e614eea Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 23 Dec 2024 09:32:01 +0100 Subject: [PATCH 609/640] handle error when linking --- .../(login)/idp/[provider]/failure/page.tsx | 2 +- .../(login)/idp/[provider]/success/page.tsx | 43 +++++++++++-------- .../components/idps/pages/linking-failed.tsx | 9 ++-- .../components/idps/pages/linking-success.tsx | 9 ++-- .../components/idps/pages/login-failed.tsx | 12 +++--- .../components/idps/pages/login-success.tsx | 9 ++-- 6 files changed, 45 insertions(+), 39 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx index a8bc6d9cb5..a3cc0ee883 100644 --- a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -28,7 +28,7 @@ export default async function Page(props: {

    {t("loginError.title")}

    -
    {t("loginError.description")}
    +

    {t("loginError.description")}

    ); diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 4ae6126c77..cc3f7129d1 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -74,17 +74,20 @@ export default async function Page(props: { if (link && options?.isLinkingAllowed) { console.log(userId); - const idpLink = await addIDPLink( - { - id: idpInformation.idpId, - userId: idpInformation.userId, - userName: idpInformation.userName, - }, - userId, - ).catch((error) => { + let idpLink; + try { + idpLink = await addIDPLink( + { + id: idpInformation.idpId, + userId: idpInformation.userId, + userName: idpInformation.userName, + }, + userId, + ); + } catch (error) { console.error(error); return linkingFailed(branding); - }); + } if (!idpLink) { console.log("linking failed"); @@ -126,17 +129,21 @@ export default async function Page(props: { } if (foundUser) { - const idpLink = await addIDPLink( - { - id: idpInformation.idpId, - userId: idpInformation.userId, - userName: idpInformation.userName, - }, - foundUser.userId, - ).catch((error) => { + let idpLink; + try { + idpLink = await addIDPLink( + { + id: idpInformation.idpId, + userId: idpInformation.userId, + userName: idpInformation.userName, + }, + foundUser.userId, + ); + } catch (error) { console.error(error); return linkingFailed(branding); - }); + } + if (!idpLink) { return linkingFailed(branding); } else { diff --git a/apps/login/src/components/idps/pages/linking-failed.tsx b/apps/login/src/components/idps/pages/linking-failed.tsx index 94678fb293..82d6a06739 100644 --- a/apps/login/src/components/idps/pages/linking-failed.tsx +++ b/apps/login/src/components/idps/pages/linking-failed.tsx @@ -1,13 +1,12 @@ -"use client"; - import { LanguageProvider } from "@/components/language-provider"; import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; -import { useTranslations } from "next-intl"; +import { getLocale, getTranslations } from "next-intl/server"; import { Alert, AlertType } from "../../alert"; import { DynamicTheme } from "../../dynamic-theme"; -export function linkingFailed(branding?: BrandingSettings) { - const t = useTranslations("idp"); +export async function linkingFailed(branding?: BrandingSettings) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "idp" }); return ( diff --git a/apps/login/src/components/idps/pages/linking-success.tsx b/apps/login/src/components/idps/pages/linking-success.tsx index 39671d8f60..66098ed6ff 100644 --- a/apps/login/src/components/idps/pages/linking-success.tsx +++ b/apps/login/src/components/idps/pages/linking-success.tsx @@ -1,17 +1,16 @@ -"use client"; - import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; -import { useTranslations } from "next-intl"; +import { getLocale, getTranslations } from "next-intl/server"; import { DynamicTheme } from "../../dynamic-theme"; import { IdpSignin } from "../../idp-signin"; -export function linkingSuccess( +export async function linkingSuccess( userId: string, idpIntent: { idpIntentId: string; idpIntentToken: string }, authRequestId?: string, branding?: BrandingSettings, ) { - const t = useTranslations("idp"); + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "idp" }); return ( diff --git a/apps/login/src/components/idps/pages/login-failed.tsx b/apps/login/src/components/idps/pages/login-failed.tsx index 5bddf29a08..ea8b22ccf5 100644 --- a/apps/login/src/components/idps/pages/login-failed.tsx +++ b/apps/login/src/components/idps/pages/login-failed.tsx @@ -1,13 +1,15 @@ -"use client"; - import { LanguageProvider } from "@/components/language-provider"; import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; -import { useTranslations } from "next-intl"; +import { getLocale, getTranslations } from "next-intl/server"; import { Alert, AlertType } from "../../alert"; import { DynamicTheme } from "../../dynamic-theme"; -export function loginFailed(branding?: BrandingSettings, error: string = "") { - const t = useTranslations("idp"); +export async function loginFailed( + branding?: BrandingSettings, + error: string = "", +) { + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "idp" }); return ( diff --git a/apps/login/src/components/idps/pages/login-success.tsx b/apps/login/src/components/idps/pages/login-success.tsx index 39aa71c642..62da260c04 100644 --- a/apps/login/src/components/idps/pages/login-success.tsx +++ b/apps/login/src/components/idps/pages/login-success.tsx @@ -1,18 +1,17 @@ -"use client"; - import { LanguageProvider } from "@/components/language-provider"; import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; -import { useTranslations } from "next-intl"; +import { getLocale, getTranslations } from "next-intl/server"; import { DynamicTheme } from "../../dynamic-theme"; import { IdpSignin } from "../../idp-signin"; -export function loginSuccess( +export async function loginSuccess( userId: string, idpIntent: { idpIntentId: string; idpIntentToken: string }, authRequestId?: string, branding?: BrandingSettings, ) { - const t = useTranslations("idp"); + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "idp" }); return ( From 2bb46137ceeddcbae109ffef5cf110e30fd0ebe1 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 23 Dec 2024 09:35:07 +0100 Subject: [PATCH 610/640] rm logs --- apps/login/src/app/(login)/idp/[provider]/success/page.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index cc3f7129d1..3049ce835c 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -73,7 +73,6 @@ export default async function Page(props: { const providerType = idpTypeToIdentityProviderType(idp.type); if (link && options?.isLinkingAllowed) { - console.log(userId); let idpLink; try { idpLink = await addIDPLink( @@ -85,12 +84,10 @@ export default async function Page(props: { userId, ); } catch (error) { - console.error(error); return linkingFailed(branding); } if (!idpLink) { - console.log("linking failed"); return linkingFailed(branding); } else { return linkingSuccess( @@ -140,7 +137,6 @@ export default async function Page(props: { foundUser.userId, ); } catch (error) { - console.error(error); return linkingFailed(branding); } From 0546a1c06e6d1ae012f3382d9cf19b33dca9ac02 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 23 Dec 2024 09:36:19 +0100 Subject: [PATCH 611/640] rm redundant language providers --- apps/login/src/app/error.tsx | 21 +++++++--------- .../components/idps/pages/linking-failed.tsx | 25 ++++++++----------- .../components/idps/pages/login-failed.tsx | 25 ++++++++----------- .../components/idps/pages/login-success.tsx | 25 ++++++++----------- 4 files changed, 42 insertions(+), 54 deletions(-) diff --git a/apps/login/src/app/error.tsx b/apps/login/src/app/error.tsx index d475786620..bee6516a59 100644 --- a/apps/login/src/app/error.tsx +++ b/apps/login/src/app/error.tsx @@ -2,7 +2,6 @@ import { Boundary } from "@/components/boundary"; import { Button } from "@/components/button"; -import { LanguageProvider } from "@/components/language-provider"; import { useTranslations } from "next-intl"; import { useEffect } from "react"; @@ -14,17 +13,15 @@ export default function Error({ error, reset }: any) { const t = useTranslations("error"); return ( - - -
    -
    - Error: {error?.message} -
    -
    - -
    + +
    +
    + Error: {error?.message}
    - - +
    + +
    +
    +
    ); } diff --git a/apps/login/src/components/idps/pages/linking-failed.tsx b/apps/login/src/components/idps/pages/linking-failed.tsx index 82d6a06739..bb632846d0 100644 --- a/apps/login/src/components/idps/pages/linking-failed.tsx +++ b/apps/login/src/components/idps/pages/linking-failed.tsx @@ -1,4 +1,3 @@ -import { LanguageProvider } from "@/components/language-provider"; import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; import { getLocale, getTranslations } from "next-intl/server"; import { Alert, AlertType } from "../../alert"; @@ -9,19 +8,17 @@ export async function linkingFailed(branding?: BrandingSettings) { const t = await getTranslations({ locale, namespace: "idp" }); return ( - - -
    -

    {t("linkingError.title")}

    -
    - { - - {t("linkingError.description")} - - } -
    + +
    +

    {t("linkingError.title")}

    +
    + { + + {t("linkingError.description")} + + }
    - - +
    +
    ); } diff --git a/apps/login/src/components/idps/pages/login-failed.tsx b/apps/login/src/components/idps/pages/login-failed.tsx index ea8b22ccf5..8da5eee32f 100644 --- a/apps/login/src/components/idps/pages/login-failed.tsx +++ b/apps/login/src/components/idps/pages/login-failed.tsx @@ -1,4 +1,3 @@ -import { LanguageProvider } from "@/components/language-provider"; import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; import { getLocale, getTranslations } from "next-intl/server"; import { Alert, AlertType } from "../../alert"; @@ -12,18 +11,16 @@ export async function loginFailed( const t = await getTranslations({ locale, namespace: "idp" }); return ( - - -
    -

    {t("loginError.title")}

    -

    {t("loginError.description")}

    - {error && ( -
    - {{error}} -
    - )} -
    -
    -
    + +
    +

    {t("loginError.title")}

    +

    {t("loginError.description")}

    + {error && ( +
    + {{error}} +
    + )} +
    +
    ); } diff --git a/apps/login/src/components/idps/pages/login-success.tsx b/apps/login/src/components/idps/pages/login-success.tsx index 62da260c04..3a9a371995 100644 --- a/apps/login/src/components/idps/pages/login-success.tsx +++ b/apps/login/src/components/idps/pages/login-success.tsx @@ -1,4 +1,3 @@ -import { LanguageProvider } from "@/components/language-provider"; import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; import { getLocale, getTranslations } from "next-intl/server"; import { DynamicTheme } from "../../dynamic-theme"; @@ -14,19 +13,17 @@ export async function loginSuccess( const t = await getTranslations({ locale, namespace: "idp" }); return ( - - -
    -

    {t("loginSuccess.title")}

    -

    {t("loginSuccess.description")}

    + +
    +

    {t("loginSuccess.title")}

    +

    {t("loginSuccess.description")}

    - -
    -
    - + +
    +
    ); } From 2951b617cea1d8d16cb4e2bd313e16817ab687e1 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 23 Dec 2024 16:26:20 +0100 Subject: [PATCH 612/640] verify check, mfa check response --- apps/login/src/lib/server/idp.ts | 5 ++++- apps/login/src/lib/server/password.ts | 6 +++++- apps/login/src/lib/server/register.ts | 31 +++++++++++++++++++++++---- apps/login/src/lib/server/verify.ts | 12 +++++++++-- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index 35eadc70f9..fb9cf66a4f 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -98,7 +98,10 @@ export async function createNewSessionFromIdpIntent( } // TODO: check if user has MFA methods - // checkMFAFactors(session, loginSettings, authMethods, organization, authRequestId); + // const mfaFactorCheck = checkMFAFactors(session, loginSettings, authMethods, organization, authRequestId); + // if (mfaFactorCheck?.redirect) { + // return mfaFactorCheck; + // } const url = await getNextUrl( command.authRequestId && session.id diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 68206f06a3..3a6805e59e 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -185,7 +185,7 @@ export async function sendPassword(command: UpdateSessionCommand) { return { error: "Could not verify password!" }; } - checkMFAFactors( + const mfaFactorCheck = checkMFAFactors( session, loginSettings, authMethods, @@ -193,6 +193,10 @@ export async function sendPassword(command: UpdateSessionCommand) { command.authRequestId, ); + if (mfaFactorCheck?.redirect) { + return mfaFactorCheck; + } + if (command.authRequestId && session.id) { const nextUrl = await getNextUrl( { diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 01ddb0d8c8..2902d9ac60 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -1,7 +1,7 @@ "use server"; import { createSessionAndUpdateCookie } from "@/lib/server/cookie"; -import { addHumanUser, getLoginSettings } from "@/lib/zitadel"; +import { addHumanUser, getLoginSettings, getUserByID } from "@/lib/zitadel"; import { create } from "@zitadel/client"; import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { @@ -9,6 +9,7 @@ import { ChecksSchema, } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { getNextUrl } from "../client"; +import { checkEmailVerification } from "../verify-helper"; type RegisterUserCommand = { email: string; @@ -25,7 +26,7 @@ export type RegisterUserResponse = { factors: Factors | undefined; }; export async function registerUser(command: RegisterUserCommand) { - const human = await addHumanUser({ + const addResponse = await addHumanUser({ email: command.email, firstName: command.firstName, lastName: command.lastName, @@ -33,14 +34,14 @@ export async function registerUser(command: RegisterUserCommand) { organization: command.organization, }); - if (!human) { + if (!addResponse) { return { error: "Could not create user" }; } const loginSettings = await getLoginSettings(command.organization); let checkPayload: any = { - user: { search: { case: "userId", value: human.userId } }, + user: { search: { case: "userId", value: addResponse.userId } }, }; if (command.password) { @@ -75,6 +76,28 @@ export async function registerUser(command: RegisterUserCommand) { return { redirect: "/passkey/set?" + params }; } else { + const userResponse = await getUserByID(session?.factors?.user?.id); + + if (!userResponse.user) { + return { error: "Could not find user" }; + } + + const humanUser = + userResponse.user.type.case === "human" + ? userResponse.user.type.value + : undefined; + + const emailVerificationCheck = checkEmailVerification( + session, + humanUser, + session.factors.user.organizationId, + command.authRequestId, + ); + + if (emailVerificationCheck?.redirect) { + return emailVerificationCheck; + } + const url = await getNextUrl( command.authRequestId && session.id ? { diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index a91ea9842a..61c4bbb806 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -139,7 +139,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { } // redirect to mfa factor if user has one, or redirect to set one up - checkMFAFactors( + const mfaFactorCheck = checkMFAFactors( session, loginSettings, authMethodResponse.authMethodTypes, @@ -147,6 +147,10 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { command.authRequestId, ); + if (mfaFactorCheck?.redirect) { + return mfaFactorCheck; + } + // login user if no additional steps are required if (command.authRequestId && session.id) { const nextUrl = await getNextUrl( @@ -299,7 +303,7 @@ export async function sendVerificationRedirectWithoutCheck( const loginSettings = await getLoginSettings(user.details?.resourceOwner); // redirect to mfa factor if user has one, or redirect to set one up - checkMFAFactors( + const mfaFactorCheck = checkMFAFactors( session, loginSettings, authMethodResponse.authMethodTypes, @@ -307,6 +311,10 @@ export async function sendVerificationRedirectWithoutCheck( command.authRequestId, ); + if (mfaFactorCheck?.redirect) { + return mfaFactorCheck; + } + // login user if no additional steps are required if (command.authRequestId && session.id) { const nextUrl = await getNextUrl( From 53fc22e048be489b10d641ab340b6514b2edeff0 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 24 Dec 2024 09:15:12 +0100 Subject: [PATCH 613/640] skipsend, checkinvite --- apps/login/src/app/(login)/verify/page.tsx | 26 ++++++++++++++-- apps/login/src/components/verify-form.tsx | 4 +-- apps/login/src/lib/server/loginname.ts | 35 +++++++++------------- apps/login/src/lib/verify-helper.ts | 33 ++++++++++++++++---- 4 files changed, 68 insertions(+), 30 deletions(-) diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 0a45ce5a3e..b4a46f7541 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -3,6 +3,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { UserAvatar } from "@/components/user-avatar"; import { VerifyForm } from "@/components/verify-form"; import { VerifyRedirectButton } from "@/components/verify-redirect-button"; +import { resendVerification } from "@/lib/server/verify"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -19,8 +20,15 @@ export default async function Page(props: { searchParams: Promise }) { const t = await getTranslations({ locale, namespace: "verify" }); const tError = await getTranslations({ locale, namespace: "error" }); - const { userId, loginName, code, organization, authRequestId, invite } = - searchParams; + const { + userId, + loginName, + code, + organization, + authRequestId, + invite, + skipsend, + } = searchParams; const branding = await getBrandingSettings(organization); @@ -34,7 +42,21 @@ export default async function Page(props: { searchParams: Promise }) { loginName, organization, }); + + if (!skipsend && sessionFactors?.factors?.user?.id) { + await resendVerification({ + userId: sessionFactors?.factors?.user?.id, + isInvite: invite === "true", + }); + } } else if ("userId" in searchParams && userId) { + if (!skipsend) { + await resendVerification({ + userId, + isInvite: invite === "true", + }); + } + const userResponse = await getUserByID(userId); if (userResponse) { user = userResponse.user; diff --git a/apps/login/src/components/verify-form.tsx b/apps/login/src/components/verify-form.tsx index 2eea113398..6b6189297e 100644 --- a/apps/login/src/components/verify-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -88,12 +88,12 @@ export function VerifyForm({ setLoading(false); }); - if (response?.error) { + if (response && "error" in response && response?.error) { setError(response.error); return; } - if (response?.redirect) { + if (response && "redirect" in response && response?.redirect) { return router.push(response?.redirect); } }, diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 74d1bacdaa..54a989f986 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -8,6 +8,7 @@ import { idpTypeToIdentityProviderType, idpTypeToSlug } from "../idp"; import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb"; +import { checkInvite } from "../verify-helper"; import { getActiveIdentityProviders, getIDPByID, @@ -171,29 +172,21 @@ export async function sendLoginname(command: SendLoginnameCommand) { ); if (!methods.authMethodTypes || !methods.authMethodTypes.length) { - if ( - potentialUsers[0].type.case === "human" && - potentialUsers[0].type.value.email && - !potentialUsers[0].type.value.email.isVerified - ) { - const paramsVerify = new URLSearchParams({ - loginName: session.factors?.user?.loginName, - userId: session.factors?.user?.id, // verify needs user id - invite: "true", // TODO: check - set this to true as we dont expect old email verification method here - }); + const humanUser = + potentialUsers[0].type.case === "human" + ? potentialUsers[0].type.value + : undefined; - if (command.organization || session.factors?.user?.organizationId) { - paramsVerify.append( - "organization", - command.organization ?? session.factors?.user?.organizationId, - ); - } + // redirect to /verify invite if no auth method is set and email is not verified + const inviteCheck = checkInvite( + session, + humanUser, + session.factors.user.organizationId, + command.authRequestId, + ); - if (command.authRequestId) { - paramsVerify.append("authRequestId", command.authRequestId); - } - - return { redirect: "/verify?" + paramsVerify }; + if (inviteCheck?.redirect) { + return inviteCheck; } const paramsAuthenticatorSetup = new URLSearchParams({ diff --git a/apps/login/src/lib/verify-helper.ts b/apps/login/src/lib/verify-helper.ts index 2161e08168..cc58f8c5dc 100644 --- a/apps/login/src/lib/verify-helper.ts +++ b/apps/login/src/lib/verify-helper.ts @@ -29,17 +29,40 @@ export function checkPasswordChangeRequired( } } +export function checkInvite( + session: Session, + humanUser?: HumanUser, + organization?: string, + authRequestId?: string, +) { + if (humanUser?.email && humanUser.email.isVerified) { + const paramsVerify = new URLSearchParams({ + loginName: session.factors?.user?.loginName as string, + userId: session.factors?.user?.id as string, // verify needs user id + invite: "true", // TODO: check - set this to true as we dont expect old email verification method here + }); + + if (organization || session.factors?.user?.organizationId) { + paramsVerify.append( + "organization", + organization ?? (session.factors?.user?.organizationId as string), + ); + } + + if (authRequestId) { + paramsVerify.append("authRequestId", authRequestId); + } + + return { redirect: "/verify?" + paramsVerify }; + } +} + export function checkEmailVerification( session: Session, humanUser?: HumanUser, organization?: string, authRequestId?: string, ) { - console.log( - humanUser?.email, - process.env.EMAIL_VERIFICATION, - process.env.EMAIL_VERIFICATION === "true", - ); if ( !humanUser?.email?.isVerified && process.env.EMAIL_VERIFICATION === "true" From 807f01f5b5b6184dd51f74a6d2a72e6bfcd25da5 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 24 Dec 2024 09:50:40 +0100 Subject: [PATCH 614/640] fix error handler, skip send --- apps/login/src/app/{ => (login)}/error.tsx | 0 apps/login/src/app/(login)/verify/page.tsx | 6 ++++++ apps/login/src/lib/server/register.ts | 1 + apps/login/src/lib/verify-helper.ts | 5 +++++ 4 files changed, 12 insertions(+) rename apps/login/src/app/{ => (login)}/error.tsx (100%) diff --git a/apps/login/src/app/error.tsx b/apps/login/src/app/(login)/error.tsx similarity index 100% rename from apps/login/src/app/error.tsx rename to apps/login/src/app/(login)/error.tsx diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index b4a46f7541..62f2284485 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -47,6 +47,9 @@ export default async function Page(props: { searchParams: Promise }) { await resendVerification({ userId: sessionFactors?.factors?.user?.id, isInvite: invite === "true", + }).catch((error) => { + console.error("Could not resend verification email", error); + throw Error("Could not request email"); }); } } else if ("userId" in searchParams && userId) { @@ -54,6 +57,9 @@ export default async function Page(props: { searchParams: Promise }) { await resendVerification({ userId, isInvite: invite === "true", + }).catch((error) => { + console.error("Could not resend verification email", error); + throw Error("Could not request email"); }); } diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 2902d9ac60..a73867deb0 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -92,6 +92,7 @@ export async function registerUser(command: RegisterUserCommand) { humanUser, session.factors.user.organizationId, command.authRequestId, + //true, // skip send as a mail was send during registration ); if (emailVerificationCheck?.redirect) { diff --git a/apps/login/src/lib/verify-helper.ts b/apps/login/src/lib/verify-helper.ts index cc58f8c5dc..85b5ca68e4 100644 --- a/apps/login/src/lib/verify-helper.ts +++ b/apps/login/src/lib/verify-helper.ts @@ -62,6 +62,7 @@ export function checkEmailVerification( humanUser?: HumanUser, organization?: string, authRequestId?: string, + skipSend?: boolean, ) { if ( !humanUser?.email?.isVerified && @@ -82,6 +83,10 @@ export function checkEmailVerification( ); } + if (skipSend) { + params.append("skipsend", "true"); + } + return { redirect: `/verify?` + params }; } } From d59b2ed612801735f903ade05ce48a6b130fb1a2 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 24 Dec 2024 11:40:37 +0100 Subject: [PATCH 615/640] remove unnec initializer --- apps/login/src/components/idps/pages/login-failed.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/login/src/components/idps/pages/login-failed.tsx b/apps/login/src/components/idps/pages/login-failed.tsx index 8da5eee32f..e4187af791 100644 --- a/apps/login/src/components/idps/pages/login-failed.tsx +++ b/apps/login/src/components/idps/pages/login-failed.tsx @@ -3,10 +3,7 @@ import { getLocale, getTranslations } from "next-intl/server"; import { Alert, AlertType } from "../../alert"; import { DynamicTheme } from "../../dynamic-theme"; -export async function loginFailed( - branding?: BrandingSettings, - error: string = "", -) { +export async function loginFailed(branding?: BrandingSettings, error?: string) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "idp" }); From c39f1b4e6db81a59ede0e068e0829c041a095985 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Dec 2024 08:42:31 +0100 Subject: [PATCH 616/640] fix org context for accounts page - new user --- apps/login/src/app/(login)/accounts/page.tsx | 21 ++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index 9d2e6a0b9f..49fbad6202 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -49,6 +49,16 @@ export default async function Page(props: { organization ?? defaultOrganization, ); + const params = new URLSearchParams(); + + if (authRequestId) { + params.append("authRequestId", authRequestId); + } + + if (organization) { + params.append("organization", organization); + } + return (
    @@ -57,16 +67,7 @@ export default async function Page(props: {
    - +
    From 13f6cbaf8fda8f3e4d3cfc608df5da0e290148fb Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Dec 2024 09:55:16 +0100 Subject: [PATCH 617/640] not a module --- packages/zitadel-tsconfig/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/zitadel-tsconfig/package.json b/packages/zitadel-tsconfig/package.json index 238d7fbd70..b20542f468 100644 --- a/packages/zitadel-tsconfig/package.json +++ b/packages/zitadel-tsconfig/package.json @@ -2,7 +2,6 @@ "name": "@zitadel/tsconfig", "version": "0.0.0", "private": true, - "type": "module", "license": "MIT", "publishConfig": { "access": "public" From 52d97533580d0909138fd6a3033af76da0f8e6ff Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Dec 2024 11:43:12 +0100 Subject: [PATCH 618/640] Update apps/login/src/lib/server/loginname.ts Co-authored-by: Elio Bischof --- apps/login/src/lib/server/loginname.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 5100541a21..295f9b455f 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -170,8 +170,6 @@ export async function sendLoginname(command: SendLoginnameCommand) { session.factors?.user?.id, ); - console.log(methods); - if (!methods.authMethodTypes || !methods.authMethodTypes.length) { if ( potentialUsers[0].type.case === "human" && From aa6a5971910f9963467d3f73def783c7e072cd53 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Dec 2024 11:48:11 +0100 Subject: [PATCH 619/640] fix: ordering of conditions --- .../src/app/(login)/idp/[provider]/success/page.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 3049ce835c..d84f5ac55e 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -153,8 +153,11 @@ export default async function Page(props: { } } - // if link === true, do not create user - if (options?.isCreationAllowed && options.isAutoCreation && !link) { + if (link) { + return linkingFailed(branding); + } + + if (options?.isCreationAllowed && options.isAutoCreation) { let orgToRegisterOn: string | undefined = organization; let userData: AddHumanUserRequest = @@ -209,10 +212,6 @@ export default async function Page(props: { } } - if (link) { - return linkingFailed(branding); - } - // return login failed if no linking or creation is allowed and no user was found return loginFailed(branding, "No user found"); } From ab2136d5abc5f765f138b1de06da690bf1416e90 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Dec 2024 11:55:35 +0100 Subject: [PATCH 620/640] show error --- .../(login)/idp/[provider]/success/page.tsx | 2 ++ .../components/idps/pages/linking-failed.tsx | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index d84f5ac55e..8b0d0c256e 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -84,6 +84,7 @@ export default async function Page(props: { userId, ); } catch (error) { + console.error(error); return linkingFailed(branding); } @@ -137,6 +138,7 @@ export default async function Page(props: { foundUser.userId, ); } catch (error) { + console.error(error); return linkingFailed(branding); } diff --git a/apps/login/src/components/idps/pages/linking-failed.tsx b/apps/login/src/components/idps/pages/linking-failed.tsx index bb632846d0..f4016a3394 100644 --- a/apps/login/src/components/idps/pages/linking-failed.tsx +++ b/apps/login/src/components/idps/pages/linking-failed.tsx @@ -3,7 +3,10 @@ import { getLocale, getTranslations } from "next-intl/server"; import { Alert, AlertType } from "../../alert"; import { DynamicTheme } from "../../dynamic-theme"; -export async function linkingFailed(branding?: BrandingSettings) { +export async function linkingFailed( + branding?: BrandingSettings, + error?: string, +) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "idp" }); @@ -11,13 +14,12 @@ export async function linkingFailed(branding?: BrandingSettings) {

    {t("linkingError.title")}

    -
    - { - - {t("linkingError.description")} - - } -
    +

    {t("linkingError.description")}

    + {error && ( +
    + {{error}} +
    + )}
    ); From d8532140bc6d7001914fb922e72c89de705f1940 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Dec 2024 12:09:16 +0100 Subject: [PATCH 621/640] cleanup --- .../src/app/(login)/idp/[provider]/success/page.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 8b0d0c256e..296a25bbe4 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -72,7 +72,11 @@ export default async function Page(props: { const providerType = idpTypeToIdentityProviderType(idp.type); - if (link && options?.isLinkingAllowed) { + if (link) { + if (!options?.isLinkingAllowed) { + return linkingFailed(branding, "Linking is no longer allowed"); + } + let idpLink; try { idpLink = await addIDPLink( @@ -155,10 +159,6 @@ export default async function Page(props: { } } - if (link) { - return linkingFailed(branding); - } - if (options?.isCreationAllowed && options.isAutoCreation) { let orgToRegisterOn: string | undefined = organization; From 36187ecdc45f84dc4fb06339bd0e5375cefe13a8 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Fri, 27 Dec 2024 12:13:07 +0100 Subject: [PATCH 622/640] Update apps/login/src/app/(login)/idp/[provider]/success/page.tsx --- apps/login/src/app/(login)/idp/[provider]/success/page.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 296a25bbe4..0de29fd1ba 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -74,6 +74,7 @@ export default async function Page(props: { if (link) { if (!options?.isLinkingAllowed) { + // linking was probably disallowed since the invitation was created return linkingFailed(branding, "Linking is no longer allowed"); } From 2b43be789331a716116a3dc605ddcb1e3897062d Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Dec 2024 14:47:58 +0100 Subject: [PATCH 623/640] send code on verify page visit, generate from branch --- apps/login/src/app/(login)/verify/page.tsx | 6 ++--- apps/login/src/lib/server/verify.ts | 6 +++++ apps/login/src/lib/zitadel.ts | 27 ++++++++++++++++++++++ packages/zitadel-proto/package.json | 2 +- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 62f2284485..ca007a22f4 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -3,7 +3,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { UserAvatar } from "@/components/user-avatar"; import { VerifyForm } from "@/components/verify-form"; import { VerifyRedirectButton } from "@/components/verify-redirect-button"; -import { resendVerification } from "@/lib/server/verify"; +import { sendCode } from "@/lib/server/verify"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -44,7 +44,7 @@ export default async function Page(props: { searchParams: Promise }) { }); if (!skipsend && sessionFactors?.factors?.user?.id) { - await resendVerification({ + await sendCode({ userId: sessionFactors?.factors?.user?.id, isInvite: invite === "true", }).catch((error) => { @@ -54,7 +54,7 @@ export default async function Page(props: { searchParams: Promise }) { } } else if ("userId" in searchParams && userId) { if (!skipsend) { - await resendVerification({ + await sendCode({ userId, isInvite: invite === "true", }).catch((error) => { diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index 61c4bbb806..1dab0f5c90 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -7,6 +7,7 @@ import { listAuthenticationMethodTypes, resendEmailCode, resendInviteCode, + sendEmailCode, verifyEmail, verifyInviteCode, } from "@/lib/zitadel"; @@ -191,6 +192,11 @@ export async function resendVerification(command: resendVerifyEmailCommand) { : resendEmailCode(command.userId, host, command.authRequestId); } +export async function sendCode(command: resendVerifyEmailCommand) { + const host = (await headers()).get("host"); + return sendEmailCode(command.userId, host, command.authRequestId); +} + export type SendVerificationRedirectWithoutCheckCommand = { organization?: string; authRequestId?: string; diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 8cf756807b..ac4d61816b 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -15,6 +15,7 @@ import { ResendEmailCodeRequest, ResendEmailCodeRequestSchema, RetrieveIdentityProviderIntentRequest, + SendEmailCodeRequestSchema, SetPasswordRequest, SetPasswordRequestSchema, VerifyPasskeyRegistrationRequest, @@ -273,6 +274,32 @@ export async function resendInviteCode(userId: string) { return userService.resendInviteCode({ userId }, {}); } +export async function sendEmailCode( + userId: string, + host: string | null, + authRequestId?: string, +) { + let medium = create(SendEmailCodeRequestSchema, { + userId, + }); + + if (host) { + medium = create(SendEmailCodeRequestSchema, { + ...medium, + verification: { + case: "sendCode", + value: create(SendEmailVerificationCodeSchema, { + urlTemplate: + `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + + (authRequestId ? `&authRequestId=${authRequestId}` : ""), + }), + }, + }); + } + + return userService.sendEmailCode(medium, {}); +} + export async function createInviteCode(userId: string, host: string | null) { let medium = create(SendInviteCodeSchema, { applicationName: "Typescript Login", diff --git a/packages/zitadel-proto/package.json b/packages/zitadel-proto/package.json index 6d2df46f40..008e406a4c 100644 --- a/packages/zitadel-proto/package.json +++ b/packages/zitadel-proto/package.json @@ -11,7 +11,7 @@ ], "sideEffects": false, "scripts": { - "generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel", + "generate": "buf generate https://github.com/zitadel/zitadel.git#branch=send-email-code --path ./proto/zitadel", "clean": "rm -rf zitadel .turbo node_modules google protoc-gen-openapiv2 validate" }, "dependencies": { From be13b0e1bfd2a733d4eade9f00c174412f76e72d Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Dec 2024 15:06:01 +0100 Subject: [PATCH 624/640] fix build --- apps/login/src/app/login/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index bbeda736a9..e96326518f 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -221,7 +221,7 @@ export async function GET(request: NextRequest) { const res = await sendLoginname(command); - if (res?.redirect) { + if (res && "redirect" in res && res?.redirect) { const absoluteUrl = new URL(res.redirect, request.url); return NextResponse.redirect(absoluteUrl.toString()); } @@ -429,7 +429,7 @@ export async function GET(request: NextRequest) { const res = await sendLoginname(command); - if (res?.redirect) { + if (res && "redirect" in res && res?.redirect) { const absoluteUrl = new URL(res.redirect, request.url); return NextResponse.redirect(absoluteUrl.toString()); } From 4ff9b44f88674f714eae1bc5fe94b1fe558674ba Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Dec 2024 15:08:19 +0100 Subject: [PATCH 625/640] build --- apps/login/src/components/session-item.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/login/src/components/session-item.tsx b/apps/login/src/components/session-item.tsx index 449f7dc955..c3c28a03fd 100644 --- a/apps/login/src/components/session-item.tsx +++ b/apps/login/src/components/session-item.tsx @@ -88,11 +88,11 @@ export function SessionItem({ setLoading(false); }); - if (res?.redirect) { + if (res && "redirect" in res && res.redirect) { return router.push(res.redirect); } - if (res?.error) { + if (res && "error" in res && res.error) { setError(res.error); return; } From e0c1626f09d92f2f7d2ee343c2d9e9c7f0301199 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Dec 2024 15:13:15 +0100 Subject: [PATCH 626/640] username null check --- apps/login/src/components/username-form.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/login/src/components/username-form.tsx b/apps/login/src/components/username-form.tsx index 7d18623aba..887c454333 100644 --- a/apps/login/src/components/username-form.tsx +++ b/apps/login/src/components/username-form.tsx @@ -61,11 +61,11 @@ export function UsernameForm({ setLoading(false); }); - if (res?.redirect) { + if (res && "redirect" in res && res.redirect) { return router.push(res.redirect); } - if (res?.error) { + if (res && "error" in res && res.error) { setError(res.error); return; } From 00df9134b240333219ff6896490abea37135ff7f Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Dec 2024 15:33:05 +0100 Subject: [PATCH 627/640] fix integration test --- apps/login/cypress/integration/verify.cy.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index 7fe4f4afb6..3f2de5d1dc 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -17,6 +17,8 @@ describe("verify invite", () => { }, }); + stub("zitadel.user.v2.UserService", "SendEmailCode"); + stub("zitadel.user.v2.UserService", "GetUserByID", { data: { user: { From 506f7b828beb1cb3cc39e3299f0bd7c23e365ef8 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Dec 2024 15:38:26 +0100 Subject: [PATCH 628/640] override stub for invite --- apps/login/cypress/integration/verify.cy.ts | 28 +++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index 3f2de5d1dc..7c28350e52 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -84,6 +84,34 @@ describe("verify invite", () => { }); it.only("shows authenticators after successful invite verification", () => { + stub("zitadel.user.v2.UserService", "GetUserByID", { + data: { + user: { + userId: "221394658884845598", + state: 1, + username: "john@zitadel.com", + loginNames: ["john@zitadel.com"], + preferredLoginName: "john@zitadel.com", + human: { + userId: "221394658884845598", + state: 1, + username: "john@zitadel.com", + loginNames: ["john@zitadel.com"], + preferredLoginName: "john@zitadel.com", + profile: { + givenName: "John", + familyName: "Doe", + avatarUrl: "https://zitadel.com/avatar.jpg", + }, + email: { + email: "john@zitadel.com", + isVerified: true, // email needs to be verified + }, + }, + }, + }, + }); + stub("zitadel.user.v2.UserService", "VerifyInviteCode"); cy.visit("/verify?userId=221394658884845598&code=abc&invite=true"); cy.location("pathname", { timeout: 10_000 }).should( From 0111673e5a8cc26c2184f29f51f3b787fe427a6e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 30 Dec 2024 09:23:18 +0100 Subject: [PATCH 629/640] rm branch gen --- packages/zitadel-proto/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zitadel-proto/package.json b/packages/zitadel-proto/package.json index 008e406a4c..6d2df46f40 100644 --- a/packages/zitadel-proto/package.json +++ b/packages/zitadel-proto/package.json @@ -11,7 +11,7 @@ ], "sideEffects": false, "scripts": { - "generate": "buf generate https://github.com/zitadel/zitadel.git#branch=send-email-code --path ./proto/zitadel", + "generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel", "clean": "rm -rf zitadel .turbo node_modules google protoc-gen-openapiv2 validate" }, "dependencies": { From 96a62f59dbf5fcfe763d11b03294dec29eef10e5 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 30 Dec 2024 10:58:22 +0100 Subject: [PATCH 630/640] escape mfa checks for passkey auth --- apps/login/src/lib/verify-helper.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/login/src/lib/verify-helper.ts b/apps/login/src/lib/verify-helper.ts index 85b5ca68e4..0c5480ecd7 100644 --- a/apps/login/src/lib/verify-helper.ts +++ b/apps/login/src/lib/verify-helper.ts @@ -104,6 +104,16 @@ export function checkMFAFactors( m !== AuthenticationMethodType.PASSKEY, ); + const hasAuthenticatedWithPasskey = + session.factors?.webAuthN?.verifiedAt && + session.factors?.webAuthN?.userVerified; + + // escape further checks if user has authenticated with passkey + if (hasAuthenticatedWithPasskey) { + return; + } + + // if user has not authenticated with passkey and has only one additional mfa factor, redirect to that if (availableMultiFactors?.length == 1) { const params = new URLSearchParams({ loginName: session.factors?.user?.loginName as string, @@ -131,7 +141,7 @@ export function checkMFAFactors( } else if (factor === AuthenticationMethodType.U2F) { return { redirect: `/u2f?` + params }; } - } else if (availableMultiFactors?.length >= 1) { + } else if (availableMultiFactors?.length > 1) { const params = new URLSearchParams({ loginName: session.factors?.user?.loginName as string, }); From 52f26dc1ee3e961c6460d1e5ea5f7ed971b6a09d Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 31 Dec 2024 08:56:25 +0100 Subject: [PATCH 631/640] fix invite check --- apps/login/src/lib/verify-helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/lib/verify-helper.ts b/apps/login/src/lib/verify-helper.ts index 0c5480ecd7..37600c0a7e 100644 --- a/apps/login/src/lib/verify-helper.ts +++ b/apps/login/src/lib/verify-helper.ts @@ -35,7 +35,7 @@ export function checkInvite( organization?: string, authRequestId?: string, ) { - if (humanUser?.email && humanUser.email.isVerified) { + if (!humanUser?.email?.isVerified) { const paramsVerify = new URLSearchParams({ loginName: session.factors?.user?.loginName as string, userId: session.factors?.user?.id as string, // verify needs user id From 6e7c82cc198487f6f04ecd27bd3552ee11dbf437 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 31 Dec 2024 14:32:24 +0100 Subject: [PATCH 632/640] split invite, email verification --- apps/login/cypress/integration/invite.cy.ts | 105 ++++++++++++++++++++ apps/login/cypress/integration/login.cy.ts | 1 + apps/login/cypress/integration/verify.cy.ts | 89 ++--------------- apps/login/src/app/(login)/verify/page.tsx | 16 +-- apps/login/src/lib/server/loginname.ts | 1 + apps/login/src/lib/server/verify.ts | 11 +- 6 files changed, 135 insertions(+), 88 deletions(-) create mode 100644 apps/login/cypress/integration/invite.cy.ts diff --git a/apps/login/cypress/integration/invite.cy.ts b/apps/login/cypress/integration/invite.cy.ts new file mode 100644 index 0000000000..5b343dccab --- /dev/null +++ b/apps/login/cypress/integration/invite.cy.ts @@ -0,0 +1,105 @@ +import { stub } from "../support/mock"; + +describe("verify invite", () => { + beforeEach(() => { + stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { + data: { + details: { + totalResult: 1, + }, + result: [{ id: "256088834543534543" }], + }, + }); + + stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { + data: { + authMethodTypes: [], // user with no auth methods was invited + }, + }); + + stub("zitadel.user.v2.UserService", "GetUserByID", { + data: { + user: { + userId: "221394658884845598", + state: 1, + username: "john@zitadel.com", + loginNames: ["john@zitadel.com"], + preferredLoginName: "john@zitadel.com", + human: { + userId: "221394658884845598", + state: 1, + username: "john@zitadel.com", + loginNames: ["john@zitadel.com"], + preferredLoginName: "john@zitadel.com", + profile: { + givenName: "John", + familyName: "Doe", + avatarUrl: "https://zitadel.com/avatar.jpg", + }, + email: { + email: "john@zitadel.com", + isVerified: true, // email needs to be verified + }, + }, + }, + }, + }); + + stub("zitadel.session.v2.SessionService", "CreateSession", { + data: { + details: { + sequence: 859, + changeDate: new Date("2024-04-04T09:40:55.577Z"), + resourceOwner: "220516472055706145", + }, + sessionId: "221394658884845598", + sessionToken: + "SDMc7DlYXPgwRJ-Tb5NlLqynysHjEae3csWsKzoZWLplRji0AYY3HgAkrUEBqtLCvOayLJPMd0ax4Q", + challenges: undefined, + }, + }); + + stub("zitadel.session.v2.SessionService", "GetSession", { + data: { + session: { + id: "221394658884845598", + creationDate: new Date("2024-04-04T09:40:55.577Z"), + changeDate: new Date("2024-04-04T09:40:55.577Z"), + sequence: 859, + factors: { + user: { + id: "221394658884845598", + loginName: "john@zitadel.com", + }, + password: undefined, + webAuthN: undefined, + intent: undefined, + }, + metadata: {}, + }, + }, + }); + }); + + it.only("shows authenticators after successful invite verification", () => { + stub("zitadel.user.v2.UserService", "VerifyInviteCode"); + + cy.visit("/verify?userId=221394658884845598&code=abc&invite=true"); + cy.location("pathname", { timeout: 10_000 }).should( + "eq", + "/authenticator/set", + ); + }); + + it("shows an error if invite code validation failed", () => { + stub("zitadel.user.v2.UserService", "VerifyInviteCode", { + code: 3, + error: "error validating code", + }); + + // TODO: Avoid uncaught exception in application + cy.once("uncaught:exception", () => false); + cy.visit("/verify?userId=221394658884845598&code=abc&invite=true"); + cy.contains("Could not verify invite", { timeout: 10_000 }); + }); +}); diff --git a/apps/login/cypress/integration/login.cy.ts b/apps/login/cypress/integration/login.cy.ts index a03863a9ce..3e74c0f7fe 100644 --- a/apps/login/cypress/integration/login.cy.ts +++ b/apps/login/cypress/integration/login.cy.ts @@ -165,6 +165,7 @@ describe("login", () => { }, }); }); + it("should redirect a user with passwordless authentication to /passkey", () => { cy.visit("/loginname?loginName=john%40zitadel.com&submit=true"); cy.location("pathname", { timeout: 10_000 }).should("eq", "/passkey"); diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index 7c28350e52..c4e173d25f 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -1,6 +1,6 @@ import { stub } from "../support/mock"; -describe("verify invite", () => { +describe("verify email", () => { beforeEach(() => { stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { data: { @@ -13,7 +13,7 @@ describe("verify invite", () => { stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { data: { - authMethodTypes: [], + authMethodTypes: [1], // set one method such that we know that the user was not invited }, }); @@ -83,83 +83,16 @@ describe("verify invite", () => { }); }); - it.only("shows authenticators after successful invite verification", () => { - stub("zitadel.user.v2.UserService", "GetUserByID", { - data: { - user: { - userId: "221394658884845598", - state: 1, - username: "john@zitadel.com", - loginNames: ["john@zitadel.com"], - preferredLoginName: "john@zitadel.com", - human: { - userId: "221394658884845598", - state: 1, - username: "john@zitadel.com", - loginNames: ["john@zitadel.com"], - preferredLoginName: "john@zitadel.com", - profile: { - givenName: "John", - familyName: "Doe", - avatarUrl: "https://zitadel.com/avatar.jpg", - }, - email: { - email: "john@zitadel.com", - isVerified: true, // email needs to be verified - }, - }, - }, - }, - }); + // it("shows password and passkey method after successful invite verification", () => { + // stub("zitadel.user.v2.UserService", "VerifyEmail"); + // cy.visit("/verify?userId=221394658884845598&code=abc"); + // cy.location("pathname", { timeout: 10_000 }).should( + // "eq", + // "/authenticator/set", + // ); + // }); - stub("zitadel.user.v2.UserService", "VerifyInviteCode"); - cy.visit("/verify?userId=221394658884845598&code=abc&invite=true"); - cy.location("pathname", { timeout: 10_000 }).should( - "eq", - "/authenticator/set", - ); - }); - - it("shows an error if invite code validation failed", () => { - stub("zitadel.user.v2.UserService", "VerifyInviteCode", { - code: 3, - error: "error validating code", - }); - // TODO: Avoid uncaught exception in application - cy.once("uncaught:exception", () => false); - cy.visit("/verify?userId=221394658884845598&code=abc&invite=true"); - cy.contains("Could not verify invite", { timeout: 10_000 }); - }); -}); - -describe("verify email", () => { - beforeEach(() => { - stub("zitadel.org.v2.OrganizationService", "ListOrganizations", { - data: { - details: { - totalResult: 1, - }, - result: [{ id: "256088834543534543" }], - }, - }); - - stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", { - data: { - authMethodTypes: [], - }, - }); - }); - - it("shows password and passkey method after successful invite verification", () => { - stub("zitadel.user.v2.UserService", "VerifyEmail"); - cy.visit("/verify?userId=221394658884845598&code=abc"); - cy.location("pathname", { timeout: 10_000 }).should( - "eq", - "/authenticator/set", - ); - }); - - it("shows an error if invite code validation failed", () => { + it("shows an error if email code validation failed", () => { stub("zitadel.user.v2.UserService", "VerifyEmail", { code: 3, error: "error validating code", diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index ca007a22f4..959998c814 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -3,7 +3,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { UserAvatar } from "@/components/user-avatar"; import { VerifyForm } from "@/components/verify-form"; import { VerifyRedirectButton } from "@/components/verify-redirect-button"; -import { sendCode } from "@/lib/server/verify"; +import { sendEmailCode } from "@/lib/server/verify"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -37,26 +37,28 @@ export default async function Page(props: { searchParams: Promise }) { let human: HumanUser | undefined; let id: string | undefined; + const doSend = !skipsend && invite !== "true"; + if ("loginName" in searchParams) { sessionFactors = await loadMostRecentSession({ loginName, organization, }); - if (!skipsend && sessionFactors?.factors?.user?.id) { - await sendCode({ + if (doSend && sessionFactors?.factors?.user?.id) { + await sendEmailCode({ userId: sessionFactors?.factors?.user?.id, - isInvite: invite === "true", + authRequestId, }).catch((error) => { console.error("Could not resend verification email", error); throw Error("Could not request email"); }); } } else if ("userId" in searchParams && userId) { - if (!skipsend) { - await sendCode({ + if (doSend) { + await sendEmailCode({ userId, - isInvite: invite === "true", + authRequestId, }).catch((error) => { console.error("Could not resend verification email", error); throw Error("Could not request email"); diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 54a989f986..b380a10a0d 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -171,6 +171,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { session.factors?.user?.id, ); + // this can be expected to be an invite as users created in console have a password set. if (!methods.authMethodTypes || !methods.authMethodTypes.length) { const humanUser = potentialUsers[0].type.case === "human" diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index 1dab0f5c90..bfb1ec51c0 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -7,9 +7,9 @@ import { listAuthenticationMethodTypes, resendEmailCode, resendInviteCode, - sendEmailCode, verifyEmail, verifyInviteCode, + sendEmailCode as zitadelSendEmailCode, } from "@/lib/zitadel"; import { create } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; @@ -192,9 +192,14 @@ export async function resendVerification(command: resendVerifyEmailCommand) { : resendEmailCode(command.userId, host, command.authRequestId); } -export async function sendCode(command: resendVerifyEmailCommand) { +type sendEmailCommand = { + userId: string; + authRequestId?: string; +}; + +export async function sendEmailCode(command: sendEmailCommand) { const host = (await headers()).get("host"); - return sendEmailCode(command.userId, host, command.authRequestId); + return zitadelSendEmailCode(command.userId, host, command.authRequestId); } export type SendVerificationRedirectWithoutCheckCommand = { From 5a3c4d84b846cbdca3f3fc6976e3587f4f306f02 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 2 Jan 2025 09:07:19 +0100 Subject: [PATCH 633/640] fix invite tests, convenience command --- apps/login/cypress/integration/invite.cy.ts | 11 ++++++++++- .../zitadel.settings.v2.SettingsService.json | 8 +------- apps/login/package.json | 1 + apps/login/src/components/verify-redirect-button.tsx | 4 ++-- apps/login/src/lib/server/verify.ts | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/apps/login/cypress/integration/invite.cy.ts b/apps/login/cypress/integration/invite.cy.ts index 5b343dccab..3014f5a2e5 100644 --- a/apps/login/cypress/integration/invite.cy.ts +++ b/apps/login/cypress/integration/invite.cy.ts @@ -38,7 +38,7 @@ describe("verify invite", () => { }, email: { email: "john@zitadel.com", - isVerified: true, // email needs to be verified + isVerified: false, }, }, }, @@ -79,6 +79,15 @@ describe("verify invite", () => { }, }, }); + + stub("zitadel.settings.v2.SettingsService", "GetLoginSettings", { + data: { + settings: { + passkeysType: 1, + allowUsernamePassword: true, + }, + }, + }); }); it.only("shows authenticators after successful invite verification", () => { diff --git a/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json b/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json index f62b5da077..07e9980f9b 100644 --- a/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json +++ b/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json @@ -3,13 +3,7 @@ "service": "zitadel.settings.v2.SettingsService", "method": "GetBrandingSettings", "out": { - "data": { - "settings": { - "darkTheme": { - "backgroundColor": "#ff0000" - } - } - } + "data": {} } }, { diff --git a/apps/login/package.json b/apps/login/package.json index f8f3b89a3f..2311366c11 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -12,6 +12,7 @@ "test:integration:watch": "concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:run\\\"\"'", "test:integration:run": "cypress run --config-file ./cypress/cypress.config.ts --quiet", "test:integration:open": "cypress open --config-file ./cypress/cypress.config.ts", + "test:integration:runall": "concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:open\\\"\"'", "mock": "pnpm mock:build && pnpm mock:run", "mock:run": "pnpm mock:stop && docker run --rm --name zitadel-mock-grpc-server --publish 22220:22220 --publish 22222:22222 zitadel-mock-grpc-server", "mock:build": "DOCKER_BUILDKIT=1 docker build --tag zitadel-mock-grpc-server ./mock", diff --git a/apps/login/src/components/verify-redirect-button.tsx b/apps/login/src/components/verify-redirect-button.tsx index 09a00efe37..552e787ebc 100644 --- a/apps/login/src/components/verify-redirect-button.tsx +++ b/apps/login/src/components/verify-redirect-button.tsx @@ -51,8 +51,8 @@ export function VerifyRedirectButton({ } await sendVerificationRedirectWithoutCheck(command) - .catch((error) => { - setError("Could not verify user"); + .catch(() => { + setError("Could not verify"); return; }) .finally(() => { diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index bfb1ec51c0..1028fb1c2b 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -40,7 +40,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { }); if (!verifyResponse) { - return { error: "Could not verify user" }; + return { error: "Could not verify" }; } let session: Session | undefined; From 03fbcf2e52291bb19589dfbf2d6b146dc98bdab9 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 2 Jan 2025 09:27:50 +0100 Subject: [PATCH 634/640] fix verify tests --- apps/login/cypress/integration/verify.cy.ts | 11 +---------- apps/login/src/components/verify-email-form.tsx | 0 apps/login/src/lib/server/verify.ts | 4 ++++ 3 files changed, 5 insertions(+), 10 deletions(-) delete mode 100644 apps/login/src/components/verify-email-form.tsx diff --git a/apps/login/cypress/integration/verify.cy.ts b/apps/login/cypress/integration/verify.cy.ts index c4e173d25f..464bf02e59 100644 --- a/apps/login/cypress/integration/verify.cy.ts +++ b/apps/login/cypress/integration/verify.cy.ts @@ -83,15 +83,6 @@ describe("verify email", () => { }); }); - // it("shows password and passkey method after successful invite verification", () => { - // stub("zitadel.user.v2.UserService", "VerifyEmail"); - // cy.visit("/verify?userId=221394658884845598&code=abc"); - // cy.location("pathname", { timeout: 10_000 }).should( - // "eq", - // "/authenticator/set", - // ); - // }); - it("shows an error if email code validation failed", () => { stub("zitadel.user.v2.UserService", "VerifyEmail", { code: 3, @@ -99,7 +90,7 @@ describe("verify email", () => { }); // TODO: Avoid uncaught exception in application cy.once("uncaught:exception", () => false); - cy.visit("/verify?userId=221394658884845598&code=abc&submit=true"); + cy.visit("/verify?userId=221394658884845598&code=abc"); cy.contains("Could not verify email", { timeout: 10_000 }); }); }); diff --git a/apps/login/src/components/verify-email-form.tsx b/apps/login/src/components/verify-email-form.tsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index 1028fb1c2b..2c0c78272a 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -39,6 +39,10 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { return { error: "Could not verify email" }; }); + if ("error" in verifyResponse) { + return verifyResponse; + } + if (!verifyResponse) { return { error: "Could not verify" }; } From f12709e97fc4300e691bf46bace8d6dd293cb185 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 2 Jan 2025 14:44:37 +0100 Subject: [PATCH 635/640] remove @zitadel/node, move fcn to /client --- README.md | 5 +- apps/login/package.json | 1 - apps/login/src/lib/self.ts | 2 +- apps/login/src/lib/server/password.ts | 3 +- apps/login/src/lib/zitadel.ts | 2 +- apps/login/turbo.json | 8 ++-- packages/zitadel-client/package.json | 3 ++ .../index.ts => zitadel-client/src/grpc.ts} | 2 +- packages/zitadel-client/src/index.ts | 2 + packages/zitadel-node/.eslintrc.cjs | 5 -- packages/zitadel-node/package.json | 46 ------------------- packages/zitadel-node/tsconfig.json | 5 -- packages/zitadel-node/tsup.config.ts | 13 ------ packages/zitadel-node/turbo.json | 15 ------ pnpm-lock.yaml | 34 +++----------- 15 files changed, 21 insertions(+), 125 deletions(-) rename packages/{zitadel-node/src/index.ts => zitadel-client/src/grpc.ts} (92%) delete mode 100644 packages/zitadel-node/.eslintrc.cjs delete mode 100644 packages/zitadel-node/package.json delete mode 100644 packages/zitadel-node/tsconfig.json delete mode 100644 packages/zitadel-node/tsup.config.ts delete mode 100644 packages/zitadel-node/turbo.json diff --git a/README.md b/README.md index 3c47b35f3b..b08fbba545 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Login UI. The scope of functionality of this repo and packages is under active development. -The `@zitadel/client` and `@zitadel/node` packages are using [@connectrpc/connect](https://github.com/connectrpc/connect-es#readme). +The `@zitadel/client` package is using [@connectrpc/connect](https://github.com/connectrpc/connect-es#readme). You can read the [contribution guide](/CONTRIBUTING.md) on how to contribute. Questions can be raised in our [Discord channel](https://discord.gg/erh5Brh7jE) or as @@ -30,8 +30,7 @@ We think the easiest path of getting up and running, is the following: ## Included Apps And Packages - `login`: The login UI used by ZITADEL Cloud, powered by Next.js -- `@zitadel/node`: core components for establishing node client connection -- `@zitadel/client`: shared client utilities +- `@zitadel/client`: shared client utilities for node and browser environments - `@zitadel/proto`: shared protobuf types - `@zitadel/tsconfig`: shared `tsconfig.json`s used throughout the monorepo - `eslint-config-zitadel`: ESLint preset diff --git a/apps/login/package.json b/apps/login/package.json index f8f3b89a3f..e07d1601f8 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -39,7 +39,6 @@ "@tailwindcss/forms": "0.5.7", "@vercel/analytics": "^1.2.2", "@zitadel/client": "workspace:*", - "@zitadel/node": "workspace:*", "@zitadel/proto": "workspace:*", "clsx": "1.2.1", "copy-to-clipboard": "^3.3.3", diff --git a/apps/login/src/lib/self.ts b/apps/login/src/lib/self.ts index 6eea8206c9..c67005a346 100644 --- a/apps/login/src/lib/self.ts +++ b/apps/login/src/lib/self.ts @@ -1,7 +1,7 @@ "use server"; +import { createServerTransport } from "@zitadel/client"; import { createUserServiceClient } from "@zitadel/client/v2"; -import { createServerTransport } from "@zitadel/node"; import { getSessionCookieById } from "./cookies"; import { getSession } from "./zitadel"; diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 47bf260eaa..2a1b646c27 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -14,9 +14,8 @@ import { setPassword, setUserPassword, } from "@/lib/zitadel"; -import { create } from "@zitadel/client"; +import { create, createServerTransport } from "@zitadel/client"; import { createUserServiceClient } from "@zitadel/client/v2"; -import { createServerTransport } from "@zitadel/node"; import { Checks, ChecksSchema, diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 849f36de11..4ab0377847 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -1,3 +1,4 @@ +import { createServerTransport } from "@zitadel/client"; import { createIdpServiceClient, createOIDCServiceClient, @@ -7,7 +8,6 @@ import { createUserServiceClient, makeReqCtx, } from "@zitadel/client/v2"; -import { createServerTransport } from "@zitadel/node"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { diff --git a/apps/login/turbo.json b/apps/login/turbo.json index 8d03cac2be..e8a243feaf 100644 --- a/apps/login/turbo.json +++ b/apps/login/turbo.json @@ -6,16 +6,16 @@ "dependsOn": ["^build"] }, "test": { - "dependsOn": ["@zitadel/node#build", "@zitadel/client#build"] + "dependsOn": ["@zitadel/client#build"] }, "test:integration": { - "dependsOn": ["@zitadel/node#build", "@zitadel/client#build"] + "dependsOn": ["@zitadel/client#build"] }, "test:unit": { - "dependsOn": ["@zitadel/node#build", "@zitadel/client#build"] + "dependsOn": ["@zitadel/client#build"] }, "test:watch": { - "dependsOn": ["@zitadel/node#build", "@zitadel/client#build"] + "dependsOn": ["@zitadel/client#build"] } } } diff --git a/packages/zitadel-client/package.json b/packages/zitadel-client/package.json index c9c127a5bf..fac78870ca 100644 --- a/packages/zitadel-client/package.json +++ b/packages/zitadel-client/package.json @@ -46,6 +46,9 @@ "dependencies": { "@bufbuild/protobuf": "^2.2.2", "@connectrpc/connect": "^2.0.0", + "@connectrpc/connect-node": "^2.0.0", + "@connectrpc/connect-web": "^2.0.0", + "jose": "^5.3.0", "@zitadel/proto": "workspace:*" }, "devDependencies": { diff --git a/packages/zitadel-node/src/index.ts b/packages/zitadel-client/src/grpc.ts similarity index 92% rename from packages/zitadel-node/src/index.ts rename to packages/zitadel-client/src/grpc.ts index e614d0447a..c5900bb592 100644 --- a/packages/zitadel-node/src/index.ts +++ b/packages/zitadel-client/src/grpc.ts @@ -1,6 +1,6 @@ import { createGrpcTransport, GrpcTransportOptions } from "@connectrpc/connect-node"; -import { NewAuthorizationBearerInterceptor } from "@zitadel/client"; import { importPKCS8, SignJWT } from "jose"; +import { NewAuthorizationBearerInterceptor } from "./interceptors"; /** * Create a server transport with the given token and configuration options. diff --git a/packages/zitadel-client/src/index.ts b/packages/zitadel-client/src/index.ts index 7cf14163bf..978279cb53 100644 --- a/packages/zitadel-client/src/index.ts +++ b/packages/zitadel-client/src/index.ts @@ -5,3 +5,5 @@ export { NewAuthorizationBearerInterceptor } from "./interceptors"; export { create, fromJson, toJson } from "@bufbuild/protobuf"; export { TimestampSchema, timestampDate, timestampFromDate, timestampFromMs, timestampMs } from "@bufbuild/protobuf/wkt"; export type { Duration, Timestamp } from "@bufbuild/protobuf/wkt"; + +export { createServerTransport, newSystemToken } from "./grpc"; diff --git a/packages/zitadel-node/.eslintrc.cjs b/packages/zitadel-node/.eslintrc.cjs deleted file mode 100644 index 51720b7c3a..0000000000 --- a/packages/zitadel-node/.eslintrc.cjs +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - root: true, - // TODO: React is not used in the server package - extends: ["zitadel"], -}; diff --git a/packages/zitadel-node/package.json b/packages/zitadel-node/package.json deleted file mode 100644 index 6bd00b6e0b..0000000000 --- a/packages/zitadel-node/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "@zitadel/node", - "version": "0.0.0", - "type": "module", - "sideEffects": false, - "license": "MIT", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - } - }, - "files": [ - "dist/**" - ], - "publishConfig": { - "access": "public" - }, - "scripts": { - "build": "tsup", - "test": "pnpm test:unit", - "test:watch": "pnpm test:unit:watch", - "test:unit": "vitest", - "test:unit:watch": "vitest --watch", - "dev": "tsup --watch", - "lint": "eslint \"src/**/*.ts*\"", - "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist && rm -rf src/proto" - }, - "peerDependencies": { - "@zitadel/client": "workspace:*", - "@connectrpc/connect": "^2.0.0" - }, - "dependencies": { - "@connectrpc/connect-node": "^2.0.0", - "@connectrpc/connect-web": "^2.0.0", - "jose": "^5.3.0" - }, - "devDependencies": { - "@connectrpc/connect": "^2.0.0", - "@types/node": "^22.9.0", - "@zitadel/client": "workspace:*", - "@zitadel/tsconfig": "workspace:*", - "eslint-config-zitadel": "workspace:*" - } -} diff --git a/packages/zitadel-node/tsconfig.json b/packages/zitadel-node/tsconfig.json deleted file mode 100644 index 5f0ea69110..0000000000 --- a/packages/zitadel-node/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "@zitadel/tsconfig/tsup.json", - "include": ["./src/**/*"], - "exclude": ["dist", "build", "node_modules"] -} diff --git a/packages/zitadel-node/tsup.config.ts b/packages/zitadel-node/tsup.config.ts deleted file mode 100644 index b978211b1e..0000000000 --- a/packages/zitadel-node/tsup.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineConfig, Options } from "tsup"; - -export default defineConfig((options: Options) => ({ - treeshake: false, - splitting: true, - entry: ["src/index.ts"], - format: ["esm", "cjs"], - dts: true, - minify: false, - clean: true, - sourcemap: true, - ...options, -})); diff --git a/packages/zitadel-node/turbo.json b/packages/zitadel-node/turbo.json deleted file mode 100644 index ba7648a89d..0000000000 --- a/packages/zitadel-node/turbo.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": [ - "//" - ], - "tasks": { - "build": { - "outputs": [ - "dist/**" - ], - "dependsOn": [ - "@zitadel/client#build" - ] - } - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 217a7b6897..7e617f8e78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,9 +91,6 @@ importers: '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client - '@zitadel/node': - specifier: workspace:* - version: link:../../packages/zitadel-node '@zitadel/proto': specifier: workspace:* version: link:../../packages/zitadel-proto @@ -263,41 +260,22 @@ importers: '@connectrpc/connect': specifier: ^2.0.0 version: 2.0.0(@bufbuild/protobuf@2.2.2) - '@zitadel/proto': - specifier: workspace:* - version: link:../zitadel-proto - devDependencies: - '@bufbuild/protocompile': - specifier: ^0.0.1 - version: 0.0.1(@bufbuild/buf@1.47.2) - '@zitadel/tsconfig': - specifier: workspace:* - version: link:../zitadel-tsconfig - eslint-config-zitadel: - specifier: workspace:* - version: link:../eslint-config-zitadel - - packages/zitadel-node: - dependencies: '@connectrpc/connect-node': specifier: ^2.0.0 version: 2.0.0(@bufbuild/protobuf@2.2.2)(@connectrpc/connect@2.0.0(@bufbuild/protobuf@2.2.2)) '@connectrpc/connect-web': specifier: ^2.0.0 version: 2.0.0(@bufbuild/protobuf@2.2.2)(@connectrpc/connect@2.0.0(@bufbuild/protobuf@2.2.2)) + '@zitadel/proto': + specifier: workspace:* + version: link:../zitadel-proto jose: specifier: ^5.3.0 version: 5.8.0 devDependencies: - '@connectrpc/connect': - specifier: ^2.0.0 - version: 2.0.0(@bufbuild/protobuf@2.2.2) - '@types/node': - specifier: ^22.9.0 - version: 22.9.0 - '@zitadel/client': - specifier: workspace:* - version: link:../zitadel-client + '@bufbuild/protocompile': + specifier: ^0.0.1 + version: 0.0.1(@bufbuild/buf@1.47.2) '@zitadel/tsconfig': specifier: workspace:* version: link:../zitadel-tsconfig From a91e147532e8cb7e82bd74fd3d8027bbbc647082 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 2 Jan 2025 14:54:51 +0100 Subject: [PATCH 636/640] move to subfolder --- apps/login/src/lib/self.ts | 2 +- apps/login/src/lib/server/password.ts | 3 ++- apps/login/src/lib/zitadel.ts | 2 +- packages/zitadel-client/package.json | 5 +++++ packages/zitadel-client/src/index.ts | 2 -- packages/zitadel-client/src/{grpc.ts => node.ts} | 0 packages/zitadel-client/tsup.config.ts | 2 +- 7 files changed, 10 insertions(+), 6 deletions(-) rename packages/zitadel-client/src/{grpc.ts => node.ts} (100%) diff --git a/apps/login/src/lib/self.ts b/apps/login/src/lib/self.ts index c67005a346..0328adfaff 100644 --- a/apps/login/src/lib/self.ts +++ b/apps/login/src/lib/self.ts @@ -1,6 +1,6 @@ "use server"; -import { createServerTransport } from "@zitadel/client"; +import { createServerTransport } from "@zitadel/client/node"; import { createUserServiceClient } from "@zitadel/client/v2"; import { getSessionCookieById } from "./cookies"; import { getSession } from "./zitadel"; diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 2a1b646c27..76bb1f4482 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -14,7 +14,8 @@ import { setPassword, setUserPassword, } from "@/lib/zitadel"; -import { create, createServerTransport } from "@zitadel/client"; +import { create } from "@zitadel/client"; +import { createServerTransport } from "@zitadel/client/node"; import { createUserServiceClient } from "@zitadel/client/v2"; import { Checks, diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 4ab0377847..687d277fc3 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -1,4 +1,4 @@ -import { createServerTransport } from "@zitadel/client"; +import { createServerTransport } from "@zitadel/client/node"; import { createIdpServiceClient, createOIDCServiceClient, diff --git a/packages/zitadel-client/package.json b/packages/zitadel-client/package.json index fac78870ca..1ed9f5ff73 100644 --- a/packages/zitadel-client/package.json +++ b/packages/zitadel-client/package.json @@ -27,6 +27,11 @@ "types": "./dist/v3alpha.d.ts", "import": "./dist/v3alpha.js", "require": "./dist/v3alpha.cjs" + }, + "./node": { + "types": "./dist/node.d.ts", + "import": "./dist/node.js", + "require": "./dist/node.cjs" } }, "files": [ diff --git a/packages/zitadel-client/src/index.ts b/packages/zitadel-client/src/index.ts index 978279cb53..7cf14163bf 100644 --- a/packages/zitadel-client/src/index.ts +++ b/packages/zitadel-client/src/index.ts @@ -5,5 +5,3 @@ export { NewAuthorizationBearerInterceptor } from "./interceptors"; export { create, fromJson, toJson } from "@bufbuild/protobuf"; export { TimestampSchema, timestampDate, timestampFromDate, timestampFromMs, timestampMs } from "@bufbuild/protobuf/wkt"; export type { Duration, Timestamp } from "@bufbuild/protobuf/wkt"; - -export { createServerTransport, newSystemToken } from "./grpc"; diff --git a/packages/zitadel-client/src/grpc.ts b/packages/zitadel-client/src/node.ts similarity index 100% rename from packages/zitadel-client/src/grpc.ts rename to packages/zitadel-client/src/node.ts diff --git a/packages/zitadel-client/tsup.config.ts b/packages/zitadel-client/tsup.config.ts index 0293f4f242..bb1644b766 100644 --- a/packages/zitadel-client/tsup.config.ts +++ b/packages/zitadel-client/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig, Options } from "tsup"; export default defineConfig((options: Options) => ({ - entry: ["src/index.ts", "src/v1.ts", "src/v2.ts", "src/v3alpha.ts"], + entry: ["src/index.ts", "src/v1.ts", "src/v2.ts", "src/v3alpha.ts", "src/node.ts"], format: ["esm", "cjs"], treeshake: false, splitting: true, From cba234196f310b6df26aaa003af540c96c933da0 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 3 Jan 2025 09:04:22 +0100 Subject: [PATCH 637/640] cleanup skipsend --- apps/login/src/app/(login)/verify/page.tsx | 13 +++---------- apps/login/src/lib/server/register.ts | 1 - apps/login/src/lib/verify-helper.ts | 5 ----- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 959998c814..b47d9eed50 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -20,15 +20,8 @@ export default async function Page(props: { searchParams: Promise }) { const t = await getTranslations({ locale, namespace: "verify" }); const tError = await getTranslations({ locale, namespace: "error" }); - const { - userId, - loginName, - code, - organization, - authRequestId, - invite, - skipsend, - } = searchParams; + const { userId, loginName, code, organization, authRequestId, invite } = + searchParams; const branding = await getBrandingSettings(organization); @@ -37,7 +30,7 @@ export default async function Page(props: { searchParams: Promise }) { let human: HumanUser | undefined; let id: string | undefined; - const doSend = !skipsend && invite !== "true"; + const doSend = invite !== "true"; if ("loginName" in searchParams) { sessionFactors = await loadMostRecentSession({ diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index a73867deb0..2902d9ac60 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -92,7 +92,6 @@ export async function registerUser(command: RegisterUserCommand) { humanUser, session.factors.user.organizationId, command.authRequestId, - //true, // skip send as a mail was send during registration ); if (emailVerificationCheck?.redirect) { diff --git a/apps/login/src/lib/verify-helper.ts b/apps/login/src/lib/verify-helper.ts index 37600c0a7e..b37287a959 100644 --- a/apps/login/src/lib/verify-helper.ts +++ b/apps/login/src/lib/verify-helper.ts @@ -62,7 +62,6 @@ export function checkEmailVerification( humanUser?: HumanUser, organization?: string, authRequestId?: string, - skipSend?: boolean, ) { if ( !humanUser?.email?.isVerified && @@ -83,10 +82,6 @@ export function checkEmailVerification( ); } - if (skipSend) { - params.append("skipsend", "true"); - } - return { redirect: `/verify?` + params }; } } From 775f96e34337e562eaf1b5d12156a21656d7892c Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 3 Jan 2025 09:07:27 +0100 Subject: [PATCH 638/640] cleanup --- apps/login/src/lib/zitadel.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index e2484b64fc..d04366003f 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -497,8 +497,6 @@ export async function resendEmailCode( request = { ...request, verification: { case: "sendCode", value: medium } }; } - console.log(request); - return userService.resendEmailCode(request, {}); } From f1b8235633d8ecf9f33369de5ac2cd0185570d81 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 3 Jan 2025 10:42:35 +0100 Subject: [PATCH 639/640] change script names --- apps/login/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/login/package.json b/apps/login/package.json index 308abe2036..c4031458ca 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -9,10 +9,10 @@ "test:unit": "vitest", "test:unit:watch": "pnpm test:unit --watch", "test:integration": "pnpm mock:build && concurrently --names 'mock,test' --success command-test --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test start http://localhost:3000 \"test:integration:run\"'", - "test:integration:watch": "concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:run\\\"\"'", + "test:integration:watch:run": "concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:run\\\"\"'", + "test:integration:watch:open": "concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:open\\\"\"'", "test:integration:run": "cypress run --config-file ./cypress/cypress.config.ts --quiet", "test:integration:open": "cypress open --config-file ./cypress/cypress.config.ts", - "test:integration:runall": "concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:open\\\"\"'", "mock": "pnpm mock:build && pnpm mock:run", "mock:run": "pnpm mock:stop && docker run --rm --name zitadel-mock-grpc-server --publish 22220:22220 --publish 22222:22222 zitadel-mock-grpc-server", "mock:build": "DOCKER_BUILDKIT=1 docker build --tag zitadel-mock-grpc-server ./mock", From 41d8ab9bd9a14dfc86f517d3e2a30be066b0637e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 3 Jan 2025 11:33:14 +0100 Subject: [PATCH 640/640] change error messages --- acceptance/tests/loginname-screen.ts | 2 +- apps/login/src/app/(login)/verify/page.tsx | 4 ++-- apps/login/src/lib/server/idp.ts | 2 +- apps/login/src/lib/server/loginname.ts | 4 ++-- apps/login/src/lib/server/passkeys.ts | 2 +- apps/login/src/lib/server/password.ts | 2 +- apps/login/src/lib/server/register.ts | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/acceptance/tests/loginname-screen.ts b/acceptance/tests/loginname-screen.ts index 0a7e247f9f..be41a28eda 100644 --- a/acceptance/tests/loginname-screen.ts +++ b/acceptance/tests/loginname-screen.ts @@ -8,5 +8,5 @@ export async function loginnameScreen(page: Page, username: string) { export async function loginnameScreenExpect(page: Page, username: string) { await expect(page.getByTestId(usernameTextInput)).toHaveValue(username); - await expect(page.getByTestId("error").locator("div")).toContainText("Could not find user"); + await expect(page.getByTestId("error").locator("div")).toContainText("User not found in the system"); } diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index b47d9eed50..128623963b 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -44,7 +44,7 @@ export default async function Page(props: { searchParams: Promise }) { authRequestId, }).catch((error) => { console.error("Could not resend verification email", error); - throw Error("Could not request email"); + throw Error("Failed to send verification email"); }); } } else if ("userId" in searchParams && userId) { @@ -54,7 +54,7 @@ export default async function Page(props: { searchParams: Promise }) { authRequestId, }).catch((error) => { console.error("Could not resend verification email", error); - throw Error("Could not request email"); + throw Error("Failed to send verification email"); }); } diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index fb9cf66a4f..b48f796160 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -62,7 +62,7 @@ export async function createNewSessionFromIdpIntent( const userResponse = await getUserByID(command.userId); if (!userResponse || !userResponse.user) { - return { error: "Could not find user" }; + return { error: "User not found in the system" }; } const loginSettings = await getLoginSettings( diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index b380a10a0d..f33e3577d0 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -310,7 +310,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (resp) { return resp; } - return { error: "Could not find user" }; + return { error: "User not found in the system" }; } else if ( loginSettings?.allowRegister && loginSettings?.allowUsernamePassword @@ -371,5 +371,5 @@ export async function sendLoginname(command: SendLoginnameCommand) { // fallbackToPassword - return { error: "Could not find user" }; + return { error: "User not found in the system" }; } diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index ca27d310f3..c21076265c 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -167,7 +167,7 @@ export async function sendPasskey(command: SendPasskeyCommand) { const userResponse = await getUserByID(session?.factors?.user?.id); if (!userResponse.user) { - return { error: "Could not find user" }; + return { error: "User not found in the system" }; } const humanUser = diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 463db18e2a..3b7a24a718 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -123,7 +123,7 @@ export async function sendPassword(command: UpdateSessionCommand) { const userResponse = await getUserByID(session?.factors?.user?.id); if (!userResponse.user) { - return { error: "Could not find user" }; + return { error: "User not found in the system" }; } user = userResponse.user; diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 2902d9ac60..284689523a 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -79,7 +79,7 @@ export async function registerUser(command: RegisterUserCommand) { const userResponse = await getUserByID(session?.factors?.user?.id); if (!userResponse.user) { - return { error: "Could not find user" }; + return { error: "User not found in the system" }; } const humanUser =