From 63b3ed0bef6031d902905d48c02a6e9d68a81e5b Mon Sep 17 00:00:00 2001 From: Incorbador Date: Fri, 2 May 2025 16:44:01 +0200 Subject: [PATCH 01/60] wip --- .../components/append/AppendInitScreen.tsx | 1 - .../login-second-factor/InitScreen.tsx | 2 - .../passkeyList/PasskeyListScreen.tsx | 1 - .../app/passkey-list-wv/actions.ts | 38 +++++++++++++++++ .../connect-next/app/passkey-list-wv/page.tsx | 42 +++++++++++++++++++ 5 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 playground/connect-next/app/passkey-list-wv/actions.ts create mode 100644 playground/connect-next/app/passkey-list-wv/page.tsx diff --git a/packages/connect-react/src/components/append/AppendInitScreen.tsx b/packages/connect-react/src/components/append/AppendInitScreen.tsx index c53d96c35..8a6916eaf 100644 --- a/packages/connect-react/src/components/append/AppendInitScreen.tsx +++ b/packages/connect-react/src/components/append/AppendInitScreen.tsx @@ -154,7 +154,6 @@ const AppendInitScreen = () => { const handleSubmit = useCallback( async (attestationOptions: string, showErrorIfCancelled: boolean) => { - console.log('handleSubmit', attestationOptions); if (appendLoading || skipping) { return; } diff --git a/packages/connect-react/src/components/login-second-factor/InitScreen.tsx b/packages/connect-react/src/components/login-second-factor/InitScreen.tsx index 334a0f74f..1d6627d1f 100644 --- a/packages/connect-react/src/components/login-second-factor/InitScreen.tsx +++ b/packages/connect-react/src/components/login-second-factor/InitScreen.tsx @@ -113,8 +113,6 @@ const InitScreen = () => { return handleSituation(LoginSituationCode.CboApiNotAvailablePostAuthenticator); } - console.log('loginContinue', res.val); - try { await config.onComplete(res.val.session); } catch { diff --git a/packages/connect-react/src/components/passkeyList/PasskeyListScreen.tsx b/packages/connect-react/src/components/passkeyList/PasskeyListScreen.tsx index 72750265c..644198235 100644 --- a/packages/connect-react/src/components/passkeyList/PasskeyListScreen.tsx +++ b/packages/connect-react/src/components/passkeyList/PasskeyListScreen.tsx @@ -166,7 +166,6 @@ const PasskeyListScreen = () => { return handleSituation(PasskeyListSituationCode.CboApiNotAvailableDuringInitialLoad, passkeyList.val); } - console.log('passkeyList', passkeyList.val.passkeys); setPasskeyListToken(listTokenRes); setPasskeyList(passkeyList.val.passkeys); statefulLoader.current.finish(); diff --git a/playground/connect-next/app/passkey-list-wv/actions.ts b/playground/connect-next/app/passkey-list-wv/actions.ts new file mode 100644 index 000000000..6f333ba94 --- /dev/null +++ b/playground/connect-next/app/passkey-list-wv/actions.ts @@ -0,0 +1,38 @@ +'use server'; + +import { cookies } from 'next/headers'; +import { ConnectTokenType } from '@corbado/types'; + +export async function getCorbadoToken(tokenType: ConnectTokenType) { + const cookieStore = await cookies(); + const identifier = cookieStore.get('identifier'); + if (!identifier) { + return null; + } + + // call backend API to get token + const payload = { + type: tokenType, + data: { + identifier: identifier.value, + }, + }; + + const body = JSON.stringify(payload); + + const url = `${process.env.CORBADO_BACKEND_API_URL}/v2/connectTokens`; + const response = await fetch(url, { + method: 'POST', + headers: { + Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, + 'Content-Type': 'application/json', + }, + cache: 'no-cache', + body: body, + }); + + const out = await response.json(); + console.log(out); + + return out.secret; +} diff --git a/playground/connect-next/app/passkey-list-wv/page.tsx b/playground/connect-next/app/passkey-list-wv/page.tsx new file mode 100644 index 000000000..5cd4bba79 --- /dev/null +++ b/playground/connect-next/app/passkey-list-wv/page.tsx @@ -0,0 +1,42 @@ +'use client'; +import { useEffect, useState } from 'react'; + +export const runtime = 'edge'; + +import { CorbadoConnectPasskeyList } from '@corbado/connect-react'; +import { getCorbadoToken } from './actions'; +import { getAppendToken } from '../actions'; +import { useSearchParams } from 'next/navigation'; + +export default function PasskeyListPage() { + const searchParams = useSearchParams(); + const identifier = searchParams.get('identifier'); + const email = searchParams.get('email'); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (identifier) { + document.cookie = `identifier=${identifier}; path=/;`; + document.cookie = `displayName=${email}; path=/;`; + setLoading(false); + } + }, []); + + if (loading) { + return
Loading...
; + } + + return ( +
+
+
+ + tokenType === 'passkey-append' ? await getAppendToken() : await getCorbadoToken(tokenType) + } + /> +
+
+
+ ); +} From 7b3bd7a096915df3ab0b5740c643987d0ccbd4d0 Mon Sep 17 00:00:00 2001 From: Incorbador Date: Sun, 4 May 2025 14:07:58 +0200 Subject: [PATCH 02/60] Add post-login-wv --- .../connect-next/app/post-login-wv/actions.ts | 8 +++ .../connect-next/app/post-login-wv/page.tsx | 64 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 playground/connect-next/app/post-login-wv/actions.ts create mode 100644 playground/connect-next/app/post-login-wv/page.tsx diff --git a/playground/connect-next/app/post-login-wv/actions.ts b/playground/connect-next/app/post-login-wv/actions.ts new file mode 100644 index 000000000..9a86aef84 --- /dev/null +++ b/playground/connect-next/app/post-login-wv/actions.ts @@ -0,0 +1,8 @@ +'use server'; + +import { cookies } from 'next/headers'; + +export async function postPasskeyAppend(_: string, clientState: string) { + const cookieStore = await cookies(); + cookieStore.set({ name: 'cbo_client_state', value: clientState, httpOnly: true }); +} diff --git a/playground/connect-next/app/post-login-wv/page.tsx b/playground/connect-next/app/post-login-wv/page.tsx new file mode 100644 index 000000000..a667a0526 --- /dev/null +++ b/playground/connect-next/app/post-login-wv/page.tsx @@ -0,0 +1,64 @@ +'use client'; +import { postPasskeyAppend } from '@/app/post-login/actions'; +import { CorbadoConnectAppend } from '@corbado/connect-react'; +import { useSearchParams } from 'next/navigation'; +import { getAppendToken } from '../actions'; +import { useEffect, useState } from 'react'; + +export const runtime = 'edge'; + +export default function PostLoginPage() { + const searchParams = useSearchParams(); + const identifier = searchParams.get('identifier'); + const email = searchParams.get('email'); + const redirectUrl = searchParams.get('redirectUrl'); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (identifier) { + document.cookie = `identifier=${identifier}; path=/;`; + document.cookie = `displayName=${email}; path=/;`; + setLoading(false); + } + }, []); + + if (loading) { + return
Loading...
; + } + + return ( +
+
+
+ { + //const redirectUrl = `auth://callback?status=${status}`; + if (!redirectUrl) { + console.error('No redirect URL provided'); + return; + } + console.log('Redirecting to:', redirectUrl); + window.location.href = redirectUrl; + }} + appendTokenProvider={async () => { + const t = await getAppendToken(); + console.log(t); + + return t; + }} + onComplete={async (status, clientSideState: string) => { + await postPasskeyAppend('', clientSideState); + // const redirectUrl = `auth://callback?status=${status}`; + if (!redirectUrl) { + console.error('No redirect URL provided'); + return; + } + console.log('Redirecting to:', redirectUrl); + window.location.href = redirectUrl; + }} + /> +
+
+
+ ); +} From 470ff8b89d0a3e5bf91182b89f9115eb2f41a142 Mon Sep 17 00:00:00 2001 From: Incorbador Date: Sun, 4 May 2025 16:07:43 +0200 Subject: [PATCH 03/60] Before switching to aws-amplify --- playground/connect-next/app/actions.ts | 25 ++++------ playground/connect-next/app/login/actions.ts | 46 +++---------------- .../app/passkey-list-wv/actions.ts | 10 ++-- .../connect-next/app/passkey-list-wv/page.tsx | 23 +--------- .../connect-next/app/passkey-list/actions.ts | 10 ++-- playground/connect-next/app/redirect/page.ts | 24 ++++++++++ playground/connect-next/app/utils.ts | 19 ++++++++ 7 files changed, 73 insertions(+), 84 deletions(-) create mode 100644 playground/connect-next/app/redirect/page.ts diff --git a/playground/connect-next/app/actions.ts b/playground/connect-next/app/actions.ts index 7e81bf958..4d07141f7 100644 --- a/playground/connect-next/app/actions.ts +++ b/playground/connect-next/app/actions.ts @@ -1,27 +1,30 @@ 'use server'; import { cookies } from 'next/headers'; +import { getUserEmail, verifyToken } from '@/app/utils'; export async function getAppendToken() { const cookieStore = await cookies(); - const displayName = cookieStore.get('displayName'); - if (!displayName) { + const token = cookieStore.get('token'); + if (!token || !token.value) { return null; } - const identifier = cookieStore.get('identifier'); - if (!identifier) { + const decoded = await verifyToken(token.value); + const email = await getUserEmail(token.value); + if (!email) { return null; } - console.log(displayName, identifier); + const identifier = decoded.username; + console.log(email, identifier); // call backend API to get token const payload = { type: 'passkey-append', data: { - displayName: displayName.value, - identifier: identifier.value, + displayName: email, + identifier: identifier, }, }; @@ -42,11 +45,3 @@ export async function getAppendToken() { return out.secret; } - -export async function hello() { - return 'Hello, World!'; -} - -export async function hello2() { - return 'Hello, World2!'; -} diff --git a/playground/connect-next/app/login/actions.ts b/playground/connect-next/app/login/actions.ts index 9ccb62476..832627e53 100644 --- a/playground/connect-next/app/login/actions.ts +++ b/playground/connect-next/app/login/actions.ts @@ -1,45 +1,18 @@ 'use server'; import { cookies } from 'next/headers'; -import { - AdminGetUserCommand, - CognitoIdentityProviderClient, - InitiateAuthCommand, -} from '@aws-sdk/client-cognito-identity-provider'; +import { CognitoIdentityProviderClient, InitiateAuthCommand } from '@aws-sdk/client-cognito-identity-provider'; import crypto from 'crypto'; -import { TokenWrapper, verifyToken } from '@/app/utils'; +import { TokenWrapper, verifyToken } from '@/app/utils'; // Here we validate the JWT token (validation is too simple, don't use this in production) // Here we validate the JWT token (validation is too simple, don't use this in production) // Then we extract the cognitoID and retrieve the user's email from the user pool // Both values will then be set as a cookie export async function postPasskeyLogin(session: string) { - console.log('postPasskeyLogin', session); + const cookieStore = await cookies(); const tokenWrapper = JSON.parse(session) as TokenWrapper; - const decoded = await verifyToken(tokenWrapper.AccessToken); - const username = decoded.username; - - // create client that loads profile from ~/.aws/credentials or environment variables - const client = new CognitoIdentityProviderClient({ - region: process.env.AWS_REGION!, - credentials: { - accessKeyId: process.env.AWS_ACCESS_KEY_ID!, - secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, - }, - }); - - const command = new AdminGetUserCommand({ - UserPoolId: process.env.AWS_COGNITO_USER_POOL_ID!, - Username: username, - }); - - const response = await client.send(command); - - const email = response.UserAttributes?.find(attr => attr.Name === 'email')?.Value; - if (email) { - const cookieStore = await cookies(); - cookieStore.set('displayName', email); - cookieStore.set('identifier', username); - } + await verifyToken(tokenWrapper.AccessToken); + cookieStore.set('token', tokenWrapper.AccessToken); return; } @@ -83,8 +56,6 @@ export async function startConventionalLogin(email: string, password: string) { } const cookieStore = await cookies(); - cookieStore.set('displayName', email); - const client = new CognitoIdentityProviderClient({ region: process.env.AWS_REGION!, credentials: { @@ -113,11 +84,8 @@ export async function startConventionalLogin(email: string, password: string) { if (response.AuthenticationResult?.AccessToken) { // no MFA has been set up yet - const decoded = await verifyToken(response.AuthenticationResult.AccessToken); - const username = decoded.username; - if (email) { - cookieStore.set('identifier', username); - } + await verifyToken(response.AuthenticationResult.AccessToken); + cookieStore.set({ name: 'token', value: response.AuthenticationResult.AccessToken, httpOnly: true }); return { success: true }; } diff --git a/playground/connect-next/app/passkey-list-wv/actions.ts b/playground/connect-next/app/passkey-list-wv/actions.ts index 6f333ba94..44c6a35c7 100644 --- a/playground/connect-next/app/passkey-list-wv/actions.ts +++ b/playground/connect-next/app/passkey-list-wv/actions.ts @@ -2,19 +2,23 @@ import { cookies } from 'next/headers'; import { ConnectTokenType } from '@corbado/types'; +import { verifyToken } from '@/app/utils'; export async function getCorbadoToken(tokenType: ConnectTokenType) { const cookieStore = await cookies(); - const identifier = cookieStore.get('identifier'); - if (!identifier) { + const token = cookieStore.get('token'); + if (!token || !token.value) { return null; } + const decoded = await verifyToken(token.value); + console.log('validatedToken', token.value, decoded); + // call backend API to get token const payload = { type: tokenType, data: { - identifier: identifier.value, + identifier: decoded.username, }, }; diff --git a/playground/connect-next/app/passkey-list-wv/page.tsx b/playground/connect-next/app/passkey-list-wv/page.tsx index 5cd4bba79..409b35f39 100644 --- a/playground/connect-next/app/passkey-list-wv/page.tsx +++ b/playground/connect-next/app/passkey-list-wv/page.tsx @@ -1,31 +1,10 @@ 'use client'; -import { useEffect, useState } from 'react'; - -export const runtime = 'edge'; - +// export const runtime = 'edge'; import { CorbadoConnectPasskeyList } from '@corbado/connect-react'; import { getCorbadoToken } from './actions'; import { getAppendToken } from '../actions'; -import { useSearchParams } from 'next/navigation'; export default function PasskeyListPage() { - const searchParams = useSearchParams(); - const identifier = searchParams.get('identifier'); - const email = searchParams.get('email'); - const [loading, setLoading] = useState(true); - - useEffect(() => { - if (identifier) { - document.cookie = `identifier=${identifier}; path=/;`; - document.cookie = `displayName=${email}; path=/;`; - setLoading(false); - } - }, []); - - if (loading) { - return
Loading...
; - } - return (
diff --git a/playground/connect-next/app/passkey-list/actions.ts b/playground/connect-next/app/passkey-list/actions.ts index 6f333ba94..3b2df11e8 100644 --- a/playground/connect-next/app/passkey-list/actions.ts +++ b/playground/connect-next/app/passkey-list/actions.ts @@ -2,19 +2,20 @@ import { cookies } from 'next/headers'; import { ConnectTokenType } from '@corbado/types'; +import { verifyToken } from '@/app/utils'; export async function getCorbadoToken(tokenType: ConnectTokenType) { const cookieStore = await cookies(); - const identifier = cookieStore.get('identifier'); - if (!identifier) { + const token = cookieStore.get('token'); + if (!token || !token.value) { return null; } - // call backend API to get token + const decoded = await verifyToken(token.value); const payload = { type: tokenType, data: { - identifier: identifier.value, + identifier: decoded.username, }, }; @@ -32,7 +33,6 @@ export async function getCorbadoToken(tokenType: ConnectTokenType) { }); const out = await response.json(); - console.log(out); return out.secret; } diff --git a/playground/connect-next/app/redirect/page.ts b/playground/connect-next/app/redirect/page.ts new file mode 100644 index 000000000..cfec8ab18 --- /dev/null +++ b/playground/connect-next/app/redirect/page.ts @@ -0,0 +1,24 @@ +import { cookies } from 'next/headers'; +import { redirect } from 'next/navigation'; + +const Page = async ({ searchParams }: { searchParams: Promise<{ [key: string]: string }> }) => { + const cookieStore = await cookies(); + const params = await searchParams; + + if (!params) { + return null; + } + + const token = params['token']; + const redirectUrl = params['redirectUrl']; + + cookieStore.set({ + name: 'token', + value: token, + httpOnly: true, + }); + + redirect(redirectUrl); +}; + +export default Page; diff --git a/playground/connect-next/app/utils.ts b/playground/connect-next/app/utils.ts index e67c5f8f0..7a9eb98bf 100644 --- a/playground/connect-next/app/utils.ts +++ b/playground/connect-next/app/utils.ts @@ -1,5 +1,6 @@ import jwt from 'jsonwebtoken'; import jwksClient from 'jwks-rsa'; +import { GetUserCommand, CognitoIdentityProviderClient } from '@aws-sdk/client-cognito-identity-provider'; const jwksUrl = `https://cognito-idp.${process.env.AWS_REGION}.amazonaws.com/${process.env.AWS_COGNITO_USER_POOL_ID}/.well-known/jwks.json`; const client = jwksClient({ jwksUri: jwksUrl }); @@ -32,3 +33,21 @@ export const verifyToken = async (token: string): Promise => { }); }); }; + +export const getUserEmail = async (token: string): Promise => { + const client = new CognitoIdentityProviderClient({ + region: process.env.AWS_REGION!, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID!, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, + }, + }); + + const command = new GetUserCommand({ + AccessToken: token, + }); + + const response = await client.send(command); + + return response.UserAttributes?.find(attr => attr.Name === 'email')?.Value; +}; From 8e1e8d5d220203946b702ad82af96cbebff3c68e Mon Sep 17 00:00:00 2001 From: Incorbador Date: Sun, 4 May 2025 18:16:03 +0200 Subject: [PATCH 04/60] Switch to Amplify done --- .../app/{ => (auth-required)}/home/client.tsx | 10 +- .../app/{ => (auth-required)}/home/page.tsx | 2 +- .../app/(auth-required)/layout.tsx | 7 + .../passkey-list-wv/actions.ts | 20 +++ .../passkey-list-wv}/page.tsx | 12 +- .../(auth-required)/passkey-list/actions.ts | 17 ++ .../passkey-list}/page.tsx | 13 +- .../(auth-required)/post-login-wv/actions.ts | 34 ++++ .../(auth-required)/post-login-wv/page.tsx | 27 +++ .../app/(auth-required)/post-login/actions.ts | 32 ++++ .../app/(auth-required)/post-login/page.tsx | 33 ++++ playground/connect-next/app/actions.ts | 47 ----- playground/connect-next/app/globals.css | 69 ++++++++ playground/connect-next/app/layout.tsx | 10 +- .../app/login/ConventionalLogin.tsx | 166 +++++++++++------- .../connect-next/app/login/LoginComponent.tsx | 47 +++-- .../connect-next/app/login/PasswordForm.tsx | 73 ++++++++ playground/connect-next/app/login/actions.ts | 113 ++---------- .../app/mfa-software-token/actions.ts | 91 ---------- .../app/mfa-software-token/page.tsx | 80 --------- playground/connect-next/app/page.tsx | 2 - .../app/passkey-list-wv/actions.ts | 42 ----- .../connect-next/app/passkey-list/actions.ts | 38 ---- .../connect-next/app/post-login-wv/actions.ts | 8 - .../connect-next/app/post-login-wv/page.tsx | 64 ------- .../connect-next/app/post-login/actions.ts | 8 - .../connect-next/app/post-login/page.tsx | 35 ---- .../connect-next/app/redirect/actions.ts | 12 ++ playground/connect-next/app/redirect/page.ts | 24 --- playground/connect-next/app/redirect/page.tsx | 34 ++++ playground/connect-next/app/signup/actions.ts | 121 +------------ playground/connect-next/app/signup/page.tsx | 48 ++++- playground/connect-next/app/utils.ts | 53 ------ playground/connect-next/components.json | 21 +++ .../connect-next/components/ClientWrapper.tsx | 21 +++ .../connect-next/components/ConfirmOTP.tsx | 84 +++++++++ .../components/ProtectedRoute.tsx | 35 ++++ .../connect-next/components/ui/button.tsx | 57 ++++++ .../connect-next/components/ui/input-otp.tsx | 71 ++++++++ playground/connect-next/lib/amplify-config.ts | 12 ++ .../connect-next/{utils => lib}/random.ts | 0 playground/connect-next/lib/utils.ts | 70 ++++++++ playground/connect-next/package.json | 14 +- playground/connect-next/tailwind.config.ts | 64 ++++++- 44 files changed, 1017 insertions(+), 824 deletions(-) rename playground/connect-next/app/{ => (auth-required)}/home/client.tsx (88%) rename playground/connect-next/app/{ => (auth-required)}/home/page.tsx (81%) create mode 100644 playground/connect-next/app/(auth-required)/layout.tsx create mode 100644 playground/connect-next/app/(auth-required)/passkey-list-wv/actions.ts rename playground/connect-next/app/{passkey-list => (auth-required)/passkey-list-wv}/page.tsx (50%) create mode 100644 playground/connect-next/app/(auth-required)/passkey-list/actions.ts rename playground/connect-next/app/{passkey-list-wv => (auth-required)/passkey-list}/page.tsx (58%) create mode 100644 playground/connect-next/app/(auth-required)/post-login-wv/actions.ts create mode 100644 playground/connect-next/app/(auth-required)/post-login-wv/page.tsx create mode 100644 playground/connect-next/app/(auth-required)/post-login/actions.ts create mode 100644 playground/connect-next/app/(auth-required)/post-login/page.tsx delete mode 100644 playground/connect-next/app/actions.ts create mode 100644 playground/connect-next/app/login/PasswordForm.tsx delete mode 100644 playground/connect-next/app/mfa-software-token/actions.ts delete mode 100644 playground/connect-next/app/mfa-software-token/page.tsx delete mode 100644 playground/connect-next/app/passkey-list-wv/actions.ts delete mode 100644 playground/connect-next/app/passkey-list/actions.ts delete mode 100644 playground/connect-next/app/post-login-wv/actions.ts delete mode 100644 playground/connect-next/app/post-login-wv/page.tsx delete mode 100644 playground/connect-next/app/post-login/actions.ts delete mode 100644 playground/connect-next/app/post-login/page.tsx create mode 100644 playground/connect-next/app/redirect/actions.ts delete mode 100644 playground/connect-next/app/redirect/page.ts create mode 100644 playground/connect-next/app/redirect/page.tsx delete mode 100644 playground/connect-next/app/utils.ts create mode 100644 playground/connect-next/components.json create mode 100644 playground/connect-next/components/ClientWrapper.tsx create mode 100644 playground/connect-next/components/ConfirmOTP.tsx create mode 100644 playground/connect-next/components/ProtectedRoute.tsx create mode 100644 playground/connect-next/components/ui/button.tsx create mode 100644 playground/connect-next/components/ui/input-otp.tsx create mode 100644 playground/connect-next/lib/amplify-config.ts rename playground/connect-next/{utils => lib}/random.ts (100%) create mode 100644 playground/connect-next/lib/utils.ts diff --git a/playground/connect-next/app/home/client.tsx b/playground/connect-next/app/(auth-required)/home/client.tsx similarity index 88% rename from playground/connect-next/app/home/client.tsx rename to playground/connect-next/app/(auth-required)/home/client.tsx index 991fa5117..17fe2dacf 100644 --- a/playground/connect-next/app/home/client.tsx +++ b/playground/connect-next/app/(auth-required)/home/client.tsx @@ -1,6 +1,7 @@ 'use client'; import { useRouter } from 'next/navigation'; +import { signOut } from 'aws-amplify/auth'; type Props = { maybeSecretCode?: string; @@ -9,6 +10,11 @@ type Props = { export default function Home({ maybeSecretCode }: Props) { const router = useRouter(); + const logout = async () => { + await signOut(); + router.push('/login'); + }; + return ( <>
@@ -27,9 +33,7 @@ export default function Home({ maybeSecretCode }: Props) { diff --git a/playground/connect-next/app/home/page.tsx b/playground/connect-next/app/(auth-required)/home/page.tsx similarity index 81% rename from playground/connect-next/app/home/page.tsx rename to playground/connect-next/app/(auth-required)/home/page.tsx index 9a28dbb27..cbf7e565d 100644 --- a/playground/connect-next/app/home/page.tsx +++ b/playground/connect-next/app/(auth-required)/home/page.tsx @@ -1,5 +1,5 @@ import { cookies } from 'next/headers'; -import Home from '@/app/home/client'; +import Home from '@/app/(auth-required)/home/client'; export default async function Page() { const cookieStore = await cookies(); diff --git a/playground/connect-next/app/(auth-required)/layout.tsx b/playground/connect-next/app/(auth-required)/layout.tsx new file mode 100644 index 000000000..f36966c3f --- /dev/null +++ b/playground/connect-next/app/(auth-required)/layout.tsx @@ -0,0 +1,7 @@ +'use client'; + +import { ProtectedRoute } from '@/components/ProtectedRoute'; + +export default function AuthenticatedLayout({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/playground/connect-next/app/(auth-required)/passkey-list-wv/actions.ts b/playground/connect-next/app/(auth-required)/passkey-list-wv/actions.ts new file mode 100644 index 000000000..bedbc4f2a --- /dev/null +++ b/playground/connect-next/app/(auth-required)/passkey-list-wv/actions.ts @@ -0,0 +1,20 @@ +'use server'; + +import { cookies } from 'next/headers'; +import { ConnectTokenType } from '@corbado/types'; +import { getCorbadoConnectToken, verifyAmplifyToken } from '@/lib/utils'; + +export const getCorbadoToken = async (tokenType: ConnectTokenType) => { + const cookieStore = await cookies(); + const idToken = cookieStore.get('idToken'); + if (!idToken) { + throw new Error('idToken is required'); + } + + const { displayName, identifier } = await verifyAmplifyToken(idToken.value); + + return getCorbadoConnectToken(tokenType, { + displayName: displayName, + identifier: identifier, + }); +}; diff --git a/playground/connect-next/app/passkey-list/page.tsx b/playground/connect-next/app/(auth-required)/passkey-list-wv/page.tsx similarity index 50% rename from playground/connect-next/app/passkey-list/page.tsx rename to playground/connect-next/app/(auth-required)/passkey-list-wv/page.tsx index 93a731dbb..4e6703b18 100644 --- a/playground/connect-next/app/passkey-list/page.tsx +++ b/playground/connect-next/app/(auth-required)/passkey-list-wv/page.tsx @@ -1,23 +1,13 @@ 'use client'; -export const runtime = 'edge'; - import { CorbadoConnectPasskeyList } from '@corbado/connect-react'; -import { useRouter } from 'next/navigation'; import { getCorbadoToken } from './actions'; -import { getAppendToken } from '../actions'; export default function PasskeyListPage() { - const router = useRouter(); - return (
- - tokenType === 'passkey-append' ? await getAppendToken() : await getCorbadoToken(tokenType) - } - /> + getCorbadoToken(tokenType)} />
diff --git a/playground/connect-next/app/(auth-required)/passkey-list/actions.ts b/playground/connect-next/app/(auth-required)/passkey-list/actions.ts new file mode 100644 index 000000000..ae9546d3e --- /dev/null +++ b/playground/connect-next/app/(auth-required)/passkey-list/actions.ts @@ -0,0 +1,17 @@ +'use server'; + +import { ConnectTokenType } from '@corbado/types'; +import { getCorbadoConnectToken, verifyAmplifyToken } from '@/lib/utils'; + +export const getCorbadoToken = async (tokenType: ConnectTokenType, idToken?: string) => { + if (!idToken) { + throw new Error('idToken is required'); + } + + const { displayName, identifier } = await verifyAmplifyToken(idToken); + + return getCorbadoConnectToken(tokenType, { + displayName: displayName, + identifier: identifier, + }); +}; diff --git a/playground/connect-next/app/passkey-list-wv/page.tsx b/playground/connect-next/app/(auth-required)/passkey-list/page.tsx similarity index 58% rename from playground/connect-next/app/passkey-list-wv/page.tsx rename to playground/connect-next/app/(auth-required)/passkey-list/page.tsx index 409b35f39..03e85d21a 100644 --- a/playground/connect-next/app/passkey-list-wv/page.tsx +++ b/playground/connect-next/app/(auth-required)/passkey-list/page.tsx @@ -1,8 +1,8 @@ 'use client'; -// export const runtime = 'edge'; +import { fetchAuthSession } from 'aws-amplify/auth'; + import { CorbadoConnectPasskeyList } from '@corbado/connect-react'; import { getCorbadoToken } from './actions'; -import { getAppendToken } from '../actions'; export default function PasskeyListPage() { return ( @@ -10,9 +10,12 @@ export default function PasskeyListPage() {
- tokenType === 'passkey-append' ? await getAppendToken() : await getCorbadoToken(tokenType) - } + connectTokenProvider={async tokenType => { + const session = await fetchAuthSession(); + const idToken = session.tokens?.idToken?.toString(); + + return getCorbadoToken(tokenType, idToken); + }} />
diff --git a/playground/connect-next/app/(auth-required)/post-login-wv/actions.ts b/playground/connect-next/app/(auth-required)/post-login-wv/actions.ts new file mode 100644 index 000000000..6285d3f9a --- /dev/null +++ b/playground/connect-next/app/(auth-required)/post-login-wv/actions.ts @@ -0,0 +1,34 @@ +'use server'; + +import { AppendStatus, ConnectTokenType } from '@corbado/types'; +import { cookies } from 'next/headers'; +import { getCorbadoConnectToken, verifyAmplifyToken } from '@/lib/utils'; + +export const getCorbadoToken = async () => { + const cookieStore = await cookies(); + const idToken = cookieStore.get('idToken'); + if (!idToken) { + throw new Error('idToken is required'); + } + + const { displayName, identifier } = await verifyAmplifyToken(idToken.value); + + return getCorbadoConnectToken('passkey-append' as ConnectTokenType, { + displayName: displayName, + identifier: identifier, + }); +}; + +export async function postPasskeyAppend(appendStatus: AppendStatus, clientState: string) { + // update client side state + console.log(appendStatus); + if (appendStatus === 'complete' || appendStatus === 'complete-noop') { + const cookieStore = await cookies(); + cookieStore.set({ + name: 'cbo_client_state', + value: clientState, + httpOnly: true, + expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), + }); + } +} diff --git a/playground/connect-next/app/(auth-required)/post-login-wv/page.tsx b/playground/connect-next/app/(auth-required)/post-login-wv/page.tsx new file mode 100644 index 000000000..8580f2185 --- /dev/null +++ b/playground/connect-next/app/(auth-required)/post-login-wv/page.tsx @@ -0,0 +1,27 @@ +'use client'; +import { CorbadoConnectAppend } from '@corbado/connect-react'; +import { getCorbadoToken, postPasskeyAppend } from '@/app/(auth-required)/post-login-wv/actions'; +import { AppendStatus } from '@corbado/types'; + +export default function PostLoginPage() { + return ( +
+
+
+ { + window.location.href = `auth://callback?status=${status}`; + }} + appendTokenProvider={async () => { + return await getCorbadoToken(); + }} + onComplete={async (status: AppendStatus, clientSideState: string) => { + await postPasskeyAppend(status, clientSideState); + window.location.href = `auth://callback?status=${status}`; + }} + /> +
+
+
+ ); +} diff --git a/playground/connect-next/app/(auth-required)/post-login/actions.ts b/playground/connect-next/app/(auth-required)/post-login/actions.ts new file mode 100644 index 000000000..fc26a4b99 --- /dev/null +++ b/playground/connect-next/app/(auth-required)/post-login/actions.ts @@ -0,0 +1,32 @@ +'use server'; + +import { AppendStatus, ConnectTokenType } from '@corbado/types'; +import { cookies } from 'next/headers'; +import { getCorbadoConnectToken, verifyAmplifyToken } from '@/lib/utils'; + +export const getCorbadoToken = async (idToken?: string) => { + if (!idToken) { + throw new Error('idToken is required'); + } + + const { displayName, identifier } = await verifyAmplifyToken(idToken); + + return getCorbadoConnectToken('passkey-append' as ConnectTokenType, { + displayName: displayName, + identifier: identifier, + }); +}; + +export async function postPasskeyAppend(appendStatus: AppendStatus, clientState: string) { + // update client side state + console.log(appendStatus); + if (appendStatus === 'complete' || appendStatus === 'complete-noop') { + const cookieStore = await cookies(); + cookieStore.set({ + name: 'cbo_client_state', + value: clientState, + httpOnly: true, + expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), + }); + } +} diff --git a/playground/connect-next/app/(auth-required)/post-login/page.tsx b/playground/connect-next/app/(auth-required)/post-login/page.tsx new file mode 100644 index 000000000..7e0ba3c24 --- /dev/null +++ b/playground/connect-next/app/(auth-required)/post-login/page.tsx @@ -0,0 +1,33 @@ +'use client'; + +import { CorbadoConnectAppend } from '@corbado/connect-react'; +import { useRouter } from 'next/navigation'; +import { getCorbadoToken, postPasskeyAppend } from '@/app/(auth-required)/post-login/actions'; +import { fetchAuthSession } from 'aws-amplify/auth'; +import { AppendStatus } from '@corbado/types'; + +export default function Page() { + const router = useRouter(); + + return ( +
+
+
+ router.push('/home')} + appendTokenProvider={async () => { + const session = await fetchAuthSession(); + const idToken = session.tokens?.idToken?.toString(); + + return await getCorbadoToken(idToken); + }} + onComplete={async (appendStatus: AppendStatus, clientState: string) => { + await postPasskeyAppend(appendStatus, clientState); + router.push('/home'); + }} + /> +
+
+
+ ); +} diff --git a/playground/connect-next/app/actions.ts b/playground/connect-next/app/actions.ts deleted file mode 100644 index 4d07141f7..000000000 --- a/playground/connect-next/app/actions.ts +++ /dev/null @@ -1,47 +0,0 @@ -'use server'; - -import { cookies } from 'next/headers'; -import { getUserEmail, verifyToken } from '@/app/utils'; - -export async function getAppendToken() { - const cookieStore = await cookies(); - const token = cookieStore.get('token'); - if (!token || !token.value) { - return null; - } - - const decoded = await verifyToken(token.value); - const email = await getUserEmail(token.value); - if (!email) { - return null; - } - - const identifier = decoded.username; - console.log(email, identifier); - - // call backend API to get token - const payload = { - type: 'passkey-append', - data: { - displayName: email, - identifier: identifier, - }, - }; - - const body = JSON.stringify(payload); - - const url = `${process.env.CORBADO_BACKEND_API_URL}/v2/connectTokens`; - const response = await fetch(url, { - method: 'POST', - headers: { - Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, - 'Content-Type': 'application/json', - }, - cache: 'no-cache', - body: body, - }); - - const out = await response.json(); - - return out.secret; -} diff --git a/playground/connect-next/app/globals.css b/playground/connect-next/app/globals.css index 51f319668..f25d55959 100644 --- a/playground/connect-next/app/globals.css +++ b/playground/connect-next/app/globals.css @@ -11,3 +11,72 @@ html { font-family: Verdana, Helvetica, sans-serif; font-size: 14px; } + + + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} + + + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/playground/connect-next/app/layout.tsx b/playground/connect-next/app/layout.tsx index 96e27fc1e..52aaa8c10 100644 --- a/playground/connect-next/app/layout.tsx +++ b/playground/connect-next/app/layout.tsx @@ -2,7 +2,7 @@ import { Inter } from 'next/font/google'; import './globals.css'; -import { CorbadoConnectProvider } from '@corbado/connect-react'; +import WrappedCorbadoConnectProvider from '@/components/ClientWrapper'; const inter = Inter({ subsets: ['latin'] }); @@ -14,13 +14,7 @@ export default function RootLayout({ return ( - - {children} - + {children} ); diff --git a/playground/connect-next/app/login/ConventionalLogin.tsx b/playground/connect-next/app/login/ConventionalLogin.tsx index 69b01c4fa..7077b14f4 100644 --- a/playground/connect-next/app/login/ConventionalLogin.tsx +++ b/playground/connect-next/app/login/ConventionalLogin.tsx @@ -1,76 +1,122 @@ -import { useState } from 'react'; +import React, { useState } from 'react'; +import PasswordForm from '@/app/login/PasswordForm'; +import { confirmSignIn, signIn } from 'aws-amplify/auth'; import { useRouter } from 'next/navigation'; -import Link from 'next/link'; -import { startConventionalLogin } from './actions'; +import ConfirmOTP from '@/components/ConfirmOTP'; +import { cookies } from 'next/headers'; +import { TOTP } from 'totp-generator'; +import { autoFillTOTP } from '@/app/login/actions'; -export type Props = { - initialEmail: string; - initialError: string; +type Props = { + initialUserProvidedIdentifier: string; }; -export default function ConventionalLogin({ initialEmail, initialError }: Props) { - const [password, setPassword] = useState(''); - const [error, setError] = useState(initialError); - const [email, setEmail] = useState(initialEmail); +enum State { + ProvidePassword, + ProvideSMSCode, + ProvideTOTPCode, +} + +export const ConventionalLogin = ({ initialUserProvidedIdentifier }: Props) => { + const [state, setState] = useState(State.ProvidePassword); const router = useRouter(); - const onSubmit = async () => { - setError(''); - const res = await startConventionalLogin(email, password); - console.log(res); + const handleConventionalLogin = async (username: string, password: string): Promise => { + try { + const result = await signIn({ + username, + password, + }); + + switch (result.nextStep.signInStep) { + case 'CONFIRM_SIGN_IN_WITH_SMS_CODE': + setState(State.ProvideSMSCode); + break; + + case 'CONFIRM_SIGN_IN_WITH_TOTP_CODE': + setState(State.ProvideTOTPCode); + break; + + case 'DONE': + router.push('/post-login'); - if (!res.success) { - setError(res.message ?? 'An unknown error occurred. Please try again later.'); + break; - return; + default: + console.error('Unexpected next step', result.nextStep); + break; + } + } catch (e) { + if (e instanceof Error) { + return e.message; + } + + return 'An error occurred'; } + }; + + const handleConfirmCode = async (code: string): Promise => { + try { + const res = await confirmSignIn({ + challengeResponse: code, + }); + + if (res.isSignedIn) { + router.push('/post-login'); + } + } catch (e) { + if (e instanceof Error) { + return e.message; + } - if (res.screen === 'MFA_SOFTWARE_TOKEN') { - router.push('/mfa-software-token'); - } else { - router.push('/post-login'); + return 'An error occurred'; } }; + let headline, sub: string; + let content: React.ReactNode; + switch (state) { + case State.ProvidePassword: + headline = 'Login'; + sub = 'Use your email to log into you Example Corp account.'; + content = ( + + ); + + break; + case State.ProvideSMSCode: + headline = 'Check your phone'; + sub = 'We have sent an SMS to your phone.'; + content = ; + + break; + case State.ProvideTOTPCode: + headline = 'Check your authenticator'; + sub = 'Please enter the code from your authenticator app.'; + content = ( + + ); + + break; + default: + throw new Error(`Invalid state: ${state}`); + } + return ( -
-
Login
- {error &&
{error}
} - setEmail(e.target.value)} - /> - setPassword(e.target.value)} - /> -
- + <> +
+

{headline}

+

{sub}

-
- - Signup for an account - -
-
+
{content}
+ ); -} +}; + +export default ConventionalLogin; diff --git a/playground/connect-next/app/login/LoginComponent.tsx b/playground/connect-next/app/login/LoginComponent.tsx index ea2b4d336..9cbf5c4d7 100644 --- a/playground/connect-next/app/login/LoginComponent.tsx +++ b/playground/connect-next/app/login/LoginComponent.tsx @@ -4,30 +4,56 @@ import { useRouter } from 'next/navigation'; import { CorbadoConnectLogin } from '@corbado/connect-react'; import { useState } from 'react'; import ConventionalLogin from '@/app/login/ConventionalLogin'; -import { postPasskeyLoginNew } from '@/app/login/actions'; +import { postPasskeyLogin } from './actions'; +import { confirmSignIn, signIn } from 'aws-amplify/auth'; export type Props = { clientState: string | undefined; }; +const decodeJwt = (token: string) => { + const [, payload] = token.split('.'); + return JSON.parse(atob(payload)); +}; + +type WithWebauthnId = { + webauthnId: string; +}; + export default function LoginComponent({ clientState }: Props) { const router = useRouter(); const [conventionalLoginVisible, setConventionalLoginVisible] = useState(false); const [email, setEmail] = useState(''); const [fallbackErrorMessage, setFallbackErrorMessage] = useState(''); - console.log('conventionalLoginVisible', conventionalLoginVisible); + const postPasskeyLoginNew = async (signedPasskeyData: string, clientState: string) => { + // decode JWT + const decoded = decodeJwt(signedPasskeyData) as WithWebauthnId; + + try { + await signIn({ + username: decoded.webauthnId, + options: { authFlowType: 'CUSTOM_WITHOUT_SRP' }, + }); + + const resultConfirm = await confirmSignIn({ + challengeResponse: signedPasskeyData, + }); + console.log('resultConfirm', resultConfirm); + + await postPasskeyLogin(clientState); + + router.push('/post-login'); + } catch (e) { + console.error(e); + } + }; return (
- {conventionalLoginVisible ? ( - - ) : null} + {conventionalLoginVisible ? : null}
{ @@ -44,9 +70,8 @@ export default function LoginComponent({ clientState }: Props) { }} onError={(error: string) => console.log('error', error)} onLoaded={(msg: string) => console.log('component has loaded: ' + msg)} - onComplete={async (signedPasskeyData: string, clientState: string) => { - await postPasskeyLoginNew(signedPasskeyData, clientState); - router.push('/post-login'); + onComplete={async (signedPasskeyData: string, newClientState: string) => { + await postPasskeyLoginNew(signedPasskeyData, newClientState); }} onSignupClick={() => router.push('/signup')} onHelpClick={() => alert('help requested')} diff --git a/playground/connect-next/app/login/PasswordForm.tsx b/playground/connect-next/app/login/PasswordForm.tsx new file mode 100644 index 000000000..6bb4e18aa --- /dev/null +++ b/playground/connect-next/app/login/PasswordForm.tsx @@ -0,0 +1,73 @@ +import {FormEvent, useState} from "react"; + +type Props = { + initialUserProvidedIdentifier: string; + initialError?: string; + onClick: (username: string, password: string) => Promise; +} + +export const PasswordForm = ({onClick, initialUserProvidedIdentifier}: Props) => { + const [username, setUsername] = useState(initialUserProvidedIdentifier); + const [password, setPassword] = useState(''); + const [message, setMessage] = useState(''); + + const handleLogin = async (e: FormEvent) => { + e.preventDefault(); + setMessage('Loading...'); + const maybeError = await onClick(username, password); + if (maybeError) { + setMessage(maybeError); + } + } + + return ( +
+
+ + setUsername(e.target.value)} + /> +
+
+ + setPassword(e.target.value)} + /> +
+
{message}
+ +
+ ); +} + +export default PasswordForm; \ No newline at end of file diff --git a/playground/connect-next/app/login/actions.ts b/playground/connect-next/app/login/actions.ts index 832627e53..10379a604 100644 --- a/playground/connect-next/app/login/actions.ts +++ b/playground/connect-next/app/login/actions.ts @@ -1,109 +1,26 @@ 'use server'; import { cookies } from 'next/headers'; -import { CognitoIdentityProviderClient, InitiateAuthCommand } from '@aws-sdk/client-cognito-identity-provider'; -import crypto from 'crypto'; -import { TokenWrapper, verifyToken } from '@/app/utils'; // Here we validate the JWT token (validation is too simple, don't use this in production) +import { TOTP } from 'totp-generator'; -// Here we validate the JWT token (validation is too simple, don't use this in production) -// Then we extract the cognitoID and retrieve the user's email from the user pool -// Both values will then be set as a cookie -export async function postPasskeyLogin(session: string) { +export async function postPasskeyLogin(clientState: string) { const cookieStore = await cookies(); - const tokenWrapper = JSON.parse(session) as TokenWrapper; - await verifyToken(tokenWrapper.AccessToken); - cookieStore.set('token', tokenWrapper.AccessToken); - - return; -} - -export async function postPasskeyLoginNew(signedPasskeyData: string, clientState: string) { - const url = `${process.env.CORBADO_BACKEND_API_URL}/v2/passkey/postLogin`; - const body = JSON.stringify({ - signedPasskeyData: signedPasskeyData, + cookieStore.set({ + name: 'cbo_client_state', + value: clientState, + httpOnly: true, + expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), }); - - const response = await fetch(url, { - method: 'POST', - headers: { - Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, - 'Content-Type': 'application/json', - }, - cache: 'no-cache', - body: body, - }); - - const out = await response.json(); - - await postPasskeyLogin(out.session); - - // update client side state - const cookieStore = await cookies(); - cookieStore.set({ name: 'cbo_client_state', value: clientState, httpOnly: true }); } -function createSecretHash(username: string, clientId: string, clientSecret: string) { - return crypto - .createHmac('sha256', clientSecret) - .update(username + clientId) - .digest('base64'); -} - -export async function startConventionalLogin(email: string, password: string) { - try { - if (!email || !password) { - throw new Error('Email and password are required.'); - } - - const cookieStore = await cookies(); - const client = new CognitoIdentityProviderClient({ - region: process.env.AWS_REGION!, - credentials: { - accessKeyId: process.env.AWS_ACCESS_KEY_ID!, - secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, - }, - }); - - const command = new InitiateAuthCommand({ - AuthFlow: 'USER_PASSWORD_AUTH', - ClientId: process.env.AWS_COGNITO_CLIENT_ID!, - AuthParameters: { - USERNAME: email, - PASSWORD: password, - SECRET_HASH: createSecretHash( - email, - process.env.AWS_COGNITO_CLIENT_ID!, - process.env.AWS_COGNITO_CLIENT_SECRET!, - ), - }, - }); - - const response = await client.send(command); - console.log(response); - - if (response.AuthenticationResult?.AccessToken) { - // no MFA has been set up yet - - await verifyToken(response.AuthenticationResult.AccessToken); - cookieStore.set({ name: 'token', value: response.AuthenticationResult.AccessToken, httpOnly: true }); - - return { success: true }; - } - - if (response.Session && response.ChallengeName === 'SOFTWARE_TOKEN_MFA') { - cookieStore.set('mfa_session', response.Session); - - return { success: true, screen: 'MFA_SOFTWARE_TOKEN' }; - } - - return { success: false, message: 'An error occurred. Please try again later.' }; - } catch (err) { - if (err instanceof Error) { - if (err.name === 'NotAuthorizedException') return { success: false, message: 'Incorrect username or password.' }; +export async function autoFillTOTP() { + const cookieStore = await cookies(); + const maybeSecretCode = cookieStore.get('secretCode'); + if (!maybeSecretCode) { + return; + } - return { success: false, message: err.message }; - } + const { otp } = TOTP.generate(maybeSecretCode.value); - return { success: false, message: 'An error occurred. Please try again later.' }; - } + return otp; } diff --git a/playground/connect-next/app/mfa-software-token/actions.ts b/playground/connect-next/app/mfa-software-token/actions.ts deleted file mode 100644 index 743663b5c..000000000 --- a/playground/connect-next/app/mfa-software-token/actions.ts +++ /dev/null @@ -1,91 +0,0 @@ -'use server'; - -import { cookies } from 'next/headers'; -import { - CognitoIdentityProviderClient, - RespondToAuthChallengeCommand, -} from '@aws-sdk/client-cognito-identity-provider'; -import crypto from 'crypto'; -import { verifyToken } from '@/app/utils'; -import { TOTP } from 'totp-generator'; - -function createSecretHash(username: string, clientId: string, clientSecret: string) { - return crypto - .createHmac('sha256', clientSecret) - .update(username + clientId) - .digest('base64'); -} - -export async function startMFASoftwareToken(totp: string) { - try { - const cookieStore = await cookies(); - const session = cookieStore.get('mfa_session'); - const displayName = cookieStore.get('displayName'); - - if (!totp || !session || !displayName) { - throw new Error('Missing required fields.'); - } - - const client = new CognitoIdentityProviderClient({ - region: process.env.AWS_REGION!, - credentials: { - accessKeyId: process.env.AWS_ACCESS_KEY_ID!, - secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, - }, - }); - - const challengeResponseCommand = new RespondToAuthChallengeCommand({ - ClientId: process.env.AWS_COGNITO_CLIENT_ID!, - ChallengeName: 'SOFTWARE_TOKEN_MFA', - Session: session.value, - ChallengeResponses: { - USERNAME: displayName.value, - SOFTWARE_TOKEN_MFA_CODE: totp, - SECRET_HASH: createSecretHash( - displayName.value, - process.env.AWS_COGNITO_CLIENT_ID!, - process.env.AWS_COGNITO_CLIENT_SECRET!, - ), - }, - }); - - const mfaResult = await client.send(challengeResponseCommand); - console.log('MFA login complete', mfaResult); - - if (mfaResult.AuthenticationResult?.AccessToken) { - // no MFA has been set up yet - - const decoded = await verifyToken(mfaResult.AuthenticationResult.AccessToken); - if (decoded.username) { - return { success: true }; - } - - return { success: false, message: 'An error occurred. Please try again later.' }; - } - - return { success: true, screen: 'MFA_SOFTWARE_TOKEN' }; - } catch (err) { - if (err instanceof Error) { - if (err.name === 'NotAuthorizedException') return { success: false, message: 'Incorrect username or password.' }; - - return { success: false, message: err.message }; - } - - return { success: false, message: 'An error occurred. Please try again later.' }; - } -} - -export async function generateTOTP() { - const cookieStore = await cookies(); - const secretCode = cookieStore.get('secretCode'); - if (!secretCode) { - return { - success: false, - message: 'Secret code not found. Autofill only works as long as the cookie set during signup is still there.', - }; - } - - const { otp } = TOTP.generate(secretCode.value!); - - return { success: true, otp }; -} diff --git a/playground/connect-next/app/mfa-software-token/page.tsx b/playground/connect-next/app/mfa-software-token/page.tsx deleted file mode 100644 index f244a2029..000000000 --- a/playground/connect-next/app/mfa-software-token/page.tsx +++ /dev/null @@ -1,80 +0,0 @@ -'use client'; - -import { useRouter } from 'next/navigation'; -import { useState } from 'react'; -import { generateTOTP, startMFASoftwareToken } from '@/app/mfa-software-token/actions'; - -export default function LoginPage() { - const router = useRouter(); - const [conventionalLoginVisible, setConventionalLoginVisible] = useState(false); - const [totp, setTotp] = useState(''); - const [error, setError] = useState(''); - - const onSubmit = async () => { - setError(''); - const res = await startMFASoftwareToken(totp); - - if (!res.success) { - setError(res.message ?? 'An unknown error occurred. Please try again later.'); - - return; - } - - if (res.screen === 'MFA_SOFTWARE_TOKEN') { - router.push('/mfa-software-token'); - } else { - router.push('/post-login'); - } - }; - - const onAutofillTOTP = async () => { - setError(''); - const res = await generateTOTP(); - - if (!res.success) { - setError(res.message ?? 'An unknown error occurred. Please try again later.'); - - return; - } - - setTotp(res.otp ?? ''); - }; - - return ( -
-
-
-
MFA
- {error &&
{error}
} - setTotp(e.target.value)} - /> -
- -
-
- -
-
-
-
- ); -} diff --git a/playground/connect-next/app/page.tsx b/playground/connect-next/app/page.tsx index 9b0bb393e..576df74fd 100644 --- a/playground/connect-next/app/page.tsx +++ b/playground/connect-next/app/page.tsx @@ -1,8 +1,6 @@ 'use client'; -export const runtime = 'edge'; import Link from 'next/link'; -import { hello, hello2 } from '@/app/actions'; export default function Home() { return ( diff --git a/playground/connect-next/app/passkey-list-wv/actions.ts b/playground/connect-next/app/passkey-list-wv/actions.ts deleted file mode 100644 index 44c6a35c7..000000000 --- a/playground/connect-next/app/passkey-list-wv/actions.ts +++ /dev/null @@ -1,42 +0,0 @@ -'use server'; - -import { cookies } from 'next/headers'; -import { ConnectTokenType } from '@corbado/types'; -import { verifyToken } from '@/app/utils'; - -export async function getCorbadoToken(tokenType: ConnectTokenType) { - const cookieStore = await cookies(); - const token = cookieStore.get('token'); - if (!token || !token.value) { - return null; - } - - const decoded = await verifyToken(token.value); - console.log('validatedToken', token.value, decoded); - - // call backend API to get token - const payload = { - type: tokenType, - data: { - identifier: decoded.username, - }, - }; - - const body = JSON.stringify(payload); - - const url = `${process.env.CORBADO_BACKEND_API_URL}/v2/connectTokens`; - const response = await fetch(url, { - method: 'POST', - headers: { - Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, - 'Content-Type': 'application/json', - }, - cache: 'no-cache', - body: body, - }); - - const out = await response.json(); - console.log(out); - - return out.secret; -} diff --git a/playground/connect-next/app/passkey-list/actions.ts b/playground/connect-next/app/passkey-list/actions.ts deleted file mode 100644 index 3b2df11e8..000000000 --- a/playground/connect-next/app/passkey-list/actions.ts +++ /dev/null @@ -1,38 +0,0 @@ -'use server'; - -import { cookies } from 'next/headers'; -import { ConnectTokenType } from '@corbado/types'; -import { verifyToken } from '@/app/utils'; - -export async function getCorbadoToken(tokenType: ConnectTokenType) { - const cookieStore = await cookies(); - const token = cookieStore.get('token'); - if (!token || !token.value) { - return null; - } - - const decoded = await verifyToken(token.value); - const payload = { - type: tokenType, - data: { - identifier: decoded.username, - }, - }; - - const body = JSON.stringify(payload); - - const url = `${process.env.CORBADO_BACKEND_API_URL}/v2/connectTokens`; - const response = await fetch(url, { - method: 'POST', - headers: { - Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, - 'Content-Type': 'application/json', - }, - cache: 'no-cache', - body: body, - }); - - const out = await response.json(); - - return out.secret; -} diff --git a/playground/connect-next/app/post-login-wv/actions.ts b/playground/connect-next/app/post-login-wv/actions.ts deleted file mode 100644 index 9a86aef84..000000000 --- a/playground/connect-next/app/post-login-wv/actions.ts +++ /dev/null @@ -1,8 +0,0 @@ -'use server'; - -import { cookies } from 'next/headers'; - -export async function postPasskeyAppend(_: string, clientState: string) { - const cookieStore = await cookies(); - cookieStore.set({ name: 'cbo_client_state', value: clientState, httpOnly: true }); -} diff --git a/playground/connect-next/app/post-login-wv/page.tsx b/playground/connect-next/app/post-login-wv/page.tsx deleted file mode 100644 index a667a0526..000000000 --- a/playground/connect-next/app/post-login-wv/page.tsx +++ /dev/null @@ -1,64 +0,0 @@ -'use client'; -import { postPasskeyAppend } from '@/app/post-login/actions'; -import { CorbadoConnectAppend } from '@corbado/connect-react'; -import { useSearchParams } from 'next/navigation'; -import { getAppendToken } from '../actions'; -import { useEffect, useState } from 'react'; - -export const runtime = 'edge'; - -export default function PostLoginPage() { - const searchParams = useSearchParams(); - const identifier = searchParams.get('identifier'); - const email = searchParams.get('email'); - const redirectUrl = searchParams.get('redirectUrl'); - const [loading, setLoading] = useState(true); - - useEffect(() => { - if (identifier) { - document.cookie = `identifier=${identifier}; path=/;`; - document.cookie = `displayName=${email}; path=/;`; - setLoading(false); - } - }, []); - - if (loading) { - return
Loading...
; - } - - return ( -
-
-
- { - //const redirectUrl = `auth://callback?status=${status}`; - if (!redirectUrl) { - console.error('No redirect URL provided'); - return; - } - console.log('Redirecting to:', redirectUrl); - window.location.href = redirectUrl; - }} - appendTokenProvider={async () => { - const t = await getAppendToken(); - console.log(t); - - return t; - }} - onComplete={async (status, clientSideState: string) => { - await postPasskeyAppend('', clientSideState); - // const redirectUrl = `auth://callback?status=${status}`; - if (!redirectUrl) { - console.error('No redirect URL provided'); - return; - } - console.log('Redirecting to:', redirectUrl); - window.location.href = redirectUrl; - }} - /> -
-
-
- ); -} diff --git a/playground/connect-next/app/post-login/actions.ts b/playground/connect-next/app/post-login/actions.ts deleted file mode 100644 index 9a86aef84..000000000 --- a/playground/connect-next/app/post-login/actions.ts +++ /dev/null @@ -1,8 +0,0 @@ -'use server'; - -import { cookies } from 'next/headers'; - -export async function postPasskeyAppend(_: string, clientState: string) { - const cookieStore = await cookies(); - cookieStore.set({ name: 'cbo_client_state', value: clientState, httpOnly: true }); -} diff --git a/playground/connect-next/app/post-login/page.tsx b/playground/connect-next/app/post-login/page.tsx deleted file mode 100644 index 6d7e1d35a..000000000 --- a/playground/connect-next/app/post-login/page.tsx +++ /dev/null @@ -1,35 +0,0 @@ -'use client'; -import { postPasskeyAppend } from '@/app/post-login/actions'; - -export const runtime = 'edge'; - -import { CorbadoConnectAppend } from '@corbado/connect-react'; -import { useRouter } from 'next/navigation'; -import { getAppendToken } from '../actions'; - -export default function PostLoginPage() { - const router = useRouter(); - - return ( -
-
-
- router.push('/home')} - appendTokenProvider={async () => { - const t = await getAppendToken(); - console.log(t); - - return t; - }} - onComplete={async (_, clientSideState: string) => { - console.log('onComplete', clientSideState); - await postPasskeyAppend('', clientSideState); - router.push('/home'); - }} - /> -
-
-
- ); -} diff --git a/playground/connect-next/app/redirect/actions.ts b/playground/connect-next/app/redirect/actions.ts new file mode 100644 index 000000000..0c3ea693b --- /dev/null +++ b/playground/connect-next/app/redirect/actions.ts @@ -0,0 +1,12 @@ +'use server'; + +import { cookies } from 'next/headers'; + +export const setIdToken = async (token: string) => { + const cookieStore = await cookies(); + cookieStore.set({ + name: 'idToken', + value: token, + httpOnly: true, + }); +}; diff --git a/playground/connect-next/app/redirect/page.ts b/playground/connect-next/app/redirect/page.ts deleted file mode 100644 index cfec8ab18..000000000 --- a/playground/connect-next/app/redirect/page.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { cookies } from 'next/headers'; -import { redirect } from 'next/navigation'; - -const Page = async ({ searchParams }: { searchParams: Promise<{ [key: string]: string }> }) => { - const cookieStore = await cookies(); - const params = await searchParams; - - if (!params) { - return null; - } - - const token = params['token']; - const redirectUrl = params['redirectUrl']; - - cookieStore.set({ - name: 'token', - value: token, - httpOnly: true, - }); - - redirect(redirectUrl); -}; - -export default Page; diff --git a/playground/connect-next/app/redirect/page.tsx b/playground/connect-next/app/redirect/page.tsx new file mode 100644 index 000000000..3b779a692 --- /dev/null +++ b/playground/connect-next/app/redirect/page.tsx @@ -0,0 +1,34 @@ +'use client'; + +import { redirect, useRouter, useSearchParams } from 'next/navigation'; +import { setIdToken } from './actions'; +import { useEffect, useState } from 'react'; + +export default function Page() { + const searchParams = useSearchParams(); + const [loading, setLoading] = useState(true); + const router = useRouter(); + + useEffect(() => { + const init = async () => { + const token = searchParams.get('token'); + const redirectUrl = searchParams.get('redirectUrl'); + if (!token || !redirectUrl) { + return; + } + + await setIdToken(token); + setLoading(false); + console.log('pushing redirectUrl', redirectUrl); + router.push(redirectUrl); + }; + + init(); + }, []); + + if (loading) { + return
Loading...
; + } + + return
Redirecting...
; +} diff --git a/playground/connect-next/app/signup/actions.ts b/playground/connect-next/app/signup/actions.ts index 8e9338feb..e683bedae 100644 --- a/playground/connect-next/app/signup/actions.ts +++ b/playground/connect-next/app/signup/actions.ts @@ -1,125 +1,8 @@ 'use server'; import { cookies } from 'next/headers'; -import { generateRandomString } from '@/utils/random'; -import { - AdminCreateUserCommand, - AdminInitiateAuthCommand, - AdminSetUserMFAPreferenceCommand, - AdminSetUserPasswordCommand, - AssociateSoftwareTokenCommand, - CognitoIdentityProviderClient, - VerifySoftwareTokenCommand, -} from '@aws-sdk/client-cognito-identity-provider'; -import { TOTP } from 'totp-generator'; -import CryptoJS from 'crypto-js'; -const cognitoUserPoolId = process.env.AWS_COGNITO_USER_POOL_ID!; -const cognitoClientId = process.env.AWS_COGNITO_CLIENT_ID!; -const cognitoClientSecret = process.env.AWS_COGNITO_CLIENT_SECRET!; -const awsRegion = process.env.AWS_REGION!; -const awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID!; -const awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY!; - -export const createAccount = async (email: string, phone: string, password: string) => { - // of course this is not secure, but it's just a demo ;) - - const randomUsername = generateRandomString(10); +export const setTOTPSecretCode = async (secretCode: string) => { const cookieStore = await cookies(); - - cookieStore.set('displayName', email); - cookieStore.set('identifier', randomUsername); - - // create client that loads profile from ~/.aws/credentials or environment variables - const client = new CognitoIdentityProviderClient({ - region: awsRegion, - credentials: { - accessKeyId: awsAccessKeyId, - secretAccessKey: awsSecretAccessKey, - }, - }); - - const command = new AdminCreateUserCommand({ - UserPoolId: cognitoUserPoolId, - Username: randomUsername, - ForceAliasCreation: true, - MessageAction: 'SUPPRESS', - UserAttributes: [ - { - Name: 'email', - Value: email, - }, - { - Name: 'email_verified', - Value: 'true', - }, - { - Name: 'phone_number', - Value: phone, - }, - ], - }); - - await client.send(command); - - const passwordCommand = new AdminSetUserPasswordCommand({ - UserPoolId: cognitoUserPoolId, - Username: randomUsername, - Password: password, - Permanent: true, - }); - - await client.send(passwordCommand); - - const initiateAuthCommand = new AdminInitiateAuthCommand({ - AuthFlow: 'ADMIN_USER_PASSWORD_AUTH', - ClientId: cognitoClientId, - UserPoolId: cognitoUserPoolId, - AuthParameters: { - USERNAME: randomUsername, - PASSWORD: password, - SECRET_HASH: await createSecretHash(randomUsername, cognitoClientId, cognitoClientSecret), - }, - }); - - const initiateAuthRes = await client.send(initiateAuthCommand); - - const associateSoftwareTokenCommand = new AssociateSoftwareTokenCommand({ - Session: initiateAuthRes.Session, - AccessToken: initiateAuthRes.AuthenticationResult?.AccessToken, - }); - - const associateSoftwareTokenRes = await client.send(associateSoftwareTokenCommand); - console.log('associateSoftwareTokenRes', associateSoftwareTokenRes); - - cookieStore.set('secretCode', associateSoftwareTokenRes.SecretCode!); - - const { otp } = TOTP.generate(associateSoftwareTokenRes.SecretCode!); - console.log('otp', otp); - const verifySoftwareTokenCommand = new VerifySoftwareTokenCommand({ - Session: initiateAuthRes.Session, - AccessToken: initiateAuthRes.AuthenticationResult?.AccessToken, - UserCode: otp, - }); - - const verifySoftwareTokenRes = await client.send(verifySoftwareTokenCommand); - console.log('verifySoftwareTokenRes', verifySoftwareTokenRes); - - const setMfaPreferenceCommand = new AdminSetUserMFAPreferenceCommand({ - UserPoolId: cognitoUserPoolId, - Username: randomUsername, - SoftwareTokenMfaSettings: { - Enabled: true, - PreferredMfa: true, - }, - }); - const setMfaPreferenceCommandRes = await client.send(setMfaPreferenceCommand); - console.log('setMfaPreferenceCommandRes', setMfaPreferenceCommandRes); - - return; -}; - -const createSecretHash = async (username: string, clientId: string, clientSecret: string) => { - const hmac = CryptoJS.HmacSHA256(username + clientId, clientSecret); - return hmac.toString(CryptoJS.enc.Base64); + cookieStore.set('secretCode', secretCode); }; diff --git a/playground/connect-next/app/signup/page.tsx b/playground/connect-next/app/signup/page.tsx index 0a74d50bd..f2cf3c993 100644 --- a/playground/connect-next/app/signup/page.tsx +++ b/playground/connect-next/app/signup/page.tsx @@ -1,11 +1,12 @@ 'use client'; -export const runtime = 'edge'; +import { TOTP } from 'totp-generator'; import { useState } from 'react'; import { useRouter } from 'next/navigation'; -import { createAccount } from './actions'; -import { generateRandomString } from '@/utils/random'; +import { generateRandomString } from '@/lib/random'; +import { signUp, signIn, setUpTOTP, verifyTOTPSetup, updateMFAPreference } from 'aws-amplify/auth'; +import { setTOTPSecretCode } from '@/app/signup/actions'; export default function SignupPage() { const router = useRouter(); @@ -13,8 +14,43 @@ export default function SignupPage() { const [phone, setPhone] = useState(''); const [password, setPassword] = useState(''); - const signUp = async (email: string, phone: string, password: string) => { - await createAccount(email, phone, password); + const onClickSignUp = async (email: string, phone: string, password: string) => { + const username = generateRandomString(10); + + try { + const resSignUp = await signUp({ + username: username, + password, + options: { + userAttributes: { + email: email, + phone_number: phone, + }, + }, + }); + + console.log(resSignUp); + + const resLogin = await signIn({ username, password }); + console.log(resLogin); + + const setupRes = await setUpTOTP(); + console.log('setupRes', setupRes); + + await setTOTPSecretCode(setupRes.sharedSecret); + + const { otp } = TOTP.generate(setupRes.sharedSecret); + console.log('otp', otp); + + await verifyTOTPSetup({ code: otp }); + await updateMFAPreference({ + totp: 'PREFERRED', + }); + + router.push('/post-login'); + } catch (err) { + console.error('Error during signup:', err); + } }; return ( @@ -64,7 +100,7 @@ export default function SignupPage() { +
+ )} + {onCancel && ( +
+ +
+ )} +
+ ); +}; + +export default ConfirmOTP; diff --git a/playground/connect-next/components/ProtectedRoute.tsx b/playground/connect-next/components/ProtectedRoute.tsx new file mode 100644 index 000000000..164ba82c1 --- /dev/null +++ b/playground/connect-next/components/ProtectedRoute.tsx @@ -0,0 +1,35 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { fetchAuthSession } from 'aws-amplify/auth'; + +const isUserSignedIn = async (): Promise => { + try { + const session = await fetchAuthSession(); + return session?.tokens?.idToken != null; + } catch { + return false; + } +}; + +export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { + const router = useRouter(); + const [loading, setLoading] = useState(true); + + useEffect(() => { + isUserSignedIn().then(isSignedIn => { + if (!isSignedIn) { + router.replace('/login'); + } else { + setLoading(false); + } + }); + }, [router]); + + if (loading) return null; // or loading spinner + + return <>{children}; +}; + +export default ProtectedRoute; \ No newline at end of file diff --git a/playground/connect-next/components/ui/button.tsx b/playground/connect-next/components/ui/button.tsx new file mode 100644 index 000000000..65d4fcd9c --- /dev/null +++ b/playground/connect-next/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/playground/connect-next/components/ui/input-otp.tsx b/playground/connect-next/components/ui/input-otp.tsx new file mode 100644 index 000000000..eb2a1baea --- /dev/null +++ b/playground/connect-next/components/ui/input-otp.tsx @@ -0,0 +1,71 @@ +"use client" + +import * as React from "react" +import { OTPInput, OTPInputContext } from "input-otp" +import { Minus } from "lucide-react" + +import { cn } from "@/lib/utils" + +const InputOTP = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, containerClassName, ...props }, ref) => ( + +)) +InputOTP.displayName = "InputOTP" + +const InputOTPGroup = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> +>(({ className, ...props }, ref) => ( +
+)) +InputOTPGroup.displayName = "InputOTPGroup" + +const InputOTPSlot = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> & { index: number } +>(({ index, className, ...props }, ref) => { + const inputOTPContext = React.useContext(OTPInputContext) + const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index] + + return ( +
+ {char} + {hasFakeCaret && ( +
+
+
+ )} +
+ ) +}) +InputOTPSlot.displayName = "InputOTPSlot" + +const InputOTPSeparator = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> +>(({ ...props }, ref) => ( +
+ +
+)) +InputOTPSeparator.displayName = "InputOTPSeparator" + +export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } diff --git a/playground/connect-next/lib/amplify-config.ts b/playground/connect-next/lib/amplify-config.ts new file mode 100644 index 000000000..af8ec6aa0 --- /dev/null +++ b/playground/connect-next/lib/amplify-config.ts @@ -0,0 +1,12 @@ +import {Amplify} from 'aws-amplify'; + +export const configureAmplify = () => { + Amplify.configure({ + Auth: { + Cognito: { + userPoolId: process.env.NEXT_PUBLIC_AWS_COGNITO_USER_POOL_ID!, + userPoolClientId: process.env.NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID!, + } + } + }, {ssr: true}); +} diff --git a/playground/connect-next/utils/random.ts b/playground/connect-next/lib/random.ts similarity index 100% rename from playground/connect-next/utils/random.ts rename to playground/connect-next/lib/random.ts diff --git a/playground/connect-next/lib/utils.ts b/playground/connect-next/lib/utils.ts new file mode 100644 index 000000000..023af8bef --- /dev/null +++ b/playground/connect-next/lib/utils.ts @@ -0,0 +1,70 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; +import { CognitoJwtVerifier } from 'aws-jwt-verify'; +import { fetchUserAttributes, getCurrentUser } from 'aws-amplify/auth'; + +type TokenData = { + displayName: string; + identifier: string; +}; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +const verifier = CognitoJwtVerifier.create({ + userPoolId: process.env.NEXT_PUBLIC_AWS_COGNITO_USER_POOL_ID!, + tokenUse: 'id', + clientId: process.env.NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID!, +}); + +export const verifyAmplifyToken = async (idToken: string): Promise => { + const verifiedToken = await verifier.verify(idToken); + const displayName: string = verifiedToken.email as string; + const identifier = verifiedToken['cognito:username']; + + return { displayName, identifier }; +}; + +export type CognitoUserInfo = { + username: string; + email: string; + phoneNumber: string; + emailVerified: boolean; +}; + +export const getCognitoUserInfo = async (): Promise => { + const user = await getCurrentUser(); + const attributes = await fetchUserAttributes(); + + return { + username: user.username, + email: attributes.email, + phoneNumber: attributes.phone_number, + emailVerified: attributes.email_verified === 'true', + } as CognitoUserInfo; +}; + +export const getCorbadoConnectToken = async (connectTokenType: string, connectTokenData: any): Promise => { + const payload = { + type: connectTokenType, + data: connectTokenData, + }; + + const body = JSON.stringify(payload); + + const url = `${process.env.CORBADO_BACKEND_API_URL}/v2/connectTokens`; + const response = await fetch(url, { + method: 'POST', + headers: { + Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, + 'Content-Type': 'application/json', + }, + cache: 'no-cache', + body: body, + }); + + const out = await response.json(); + + return out.secret; +}; diff --git a/playground/connect-next/package.json b/playground/connect-next/package.json index 50c01549b..bbfb481db 100644 --- a/playground/connect-next/package.json +++ b/playground/connect-next/package.json @@ -9,25 +9,35 @@ "lint": "next lint" }, "dependencies": { - "@aws-sdk/client-cognito-identity-provider": "^3.423.0", + "@aws-sdk/client-cognito-identity-provider": "^3.799.0", "@aws-sdk/credential-providers": "^3.624.0", "@corbado/connect-react": "*", + "@radix-ui/react-slot": "^1.2.0", + "aws-amplify": "^6.14.4", + "aws-jwt-verify": "^5.0.0", "aws-sdk": "^2.1646.0", "axios": "^1.7.3", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "crypto-js": "^4.2.0", + "input-otp": "^1.4.2", + "jose": "^6.0.10", "jsonwebtoken": "^9.0.2", "jwks-rsa": "^3.1.0", + "lucide-react": "^0.507.0", "next": "15.2.4", "react": "^18", "react-dom": "^18", + "tailwind-merge": "^3.2.0", + "tailwindcss-animate": "^1.0.7", "totp-generator": "^1.0.0" }, "devDependencies": { "@tailwindcss/forms": "^0.5.7", + "@types/crypto-js": "^4.2.2", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", - "@types/crypto-js": "^4.2.2", "postcss": "^8", "tailwindcss": "^3.4.1", "typescript": "^5" diff --git a/playground/connect-next/tailwind.config.ts b/playground/connect-next/tailwind.config.ts index 4459e6386..7bffbaf05 100644 --- a/playground/connect-next/tailwind.config.ts +++ b/playground/connect-next/tailwind.config.ts @@ -1,19 +1,67 @@ import type { Config } from 'tailwindcss'; const config: Config = { - content: [ + darkMode: ['class'], + content: [ './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { - extend: { - backgroundImage: { - 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', - 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', - }, - }, + extend: { + backgroundImage: { + 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', + 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))' + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + } + } + } }, - plugins: [require('@tailwindcss/forms')], + plugins: [require('@tailwindcss/forms'), require("tailwindcss-animate")], }; export default config; From f593d4167419f69fe224a1446a4aab5bce6d22b0 Mon Sep 17 00:00:00 2001 From: Incorbador Date: Sun, 4 May 2025 18:18:39 +0200 Subject: [PATCH 05/60] prettier --- .../app/(auth-required)/layout.tsx | 2 +- playground/connect-next/app/globals.css | 4 - .../connect-next/app/login/PasswordForm.tsx | 129 +++++++++--------- playground/connect-next/components.json | 2 +- .../components/ProtectedRoute.tsx | 40 +++--- .../connect-next/components/ui/button.tsx | 56 ++++---- .../connect-next/components/ui/input-otp.tsx | 100 +++++++------- playground/connect-next/lib/amplify-config.ts | 23 ++-- playground/connect-next/tailwind.config.ts | 112 +++++++-------- 9 files changed, 232 insertions(+), 236 deletions(-) diff --git a/playground/connect-next/app/(auth-required)/layout.tsx b/playground/connect-next/app/(auth-required)/layout.tsx index f36966c3f..0228d012a 100644 --- a/playground/connect-next/app/(auth-required)/layout.tsx +++ b/playground/connect-next/app/(auth-required)/layout.tsx @@ -3,5 +3,5 @@ import { ProtectedRoute } from '@/components/ProtectedRoute'; export default function AuthenticatedLayout({ children }: { children: React.ReactNode }) { - return {children}; + return {children}; } diff --git a/playground/connect-next/app/globals.css b/playground/connect-next/app/globals.css index f25d55959..cdd296bfd 100644 --- a/playground/connect-next/app/globals.css +++ b/playground/connect-next/app/globals.css @@ -12,8 +12,6 @@ html { font-size: 14px; } - - @layer base { :root { --background: 0 0% 100%; @@ -70,8 +68,6 @@ html { } } - - @layer base { * { @apply border-border; diff --git a/playground/connect-next/app/login/PasswordForm.tsx b/playground/connect-next/app/login/PasswordForm.tsx index 6bb4e18aa..0c0f65e31 100644 --- a/playground/connect-next/app/login/PasswordForm.tsx +++ b/playground/connect-next/app/login/PasswordForm.tsx @@ -1,73 +1,72 @@ -import {FormEvent, useState} from "react"; +import { FormEvent, useState } from 'react'; type Props = { - initialUserProvidedIdentifier: string; - initialError?: string; - onClick: (username: string, password: string) => Promise; -} + initialUserProvidedIdentifier: string; + initialError?: string; + onClick: (username: string, password: string) => Promise; +}; -export const PasswordForm = ({onClick, initialUserProvidedIdentifier}: Props) => { - const [username, setUsername] = useState(initialUserProvidedIdentifier); - const [password, setPassword] = useState(''); - const [message, setMessage] = useState(''); +export const PasswordForm = ({ onClick, initialUserProvidedIdentifier }: Props) => { + const [username, setUsername] = useState(initialUserProvidedIdentifier); + const [password, setPassword] = useState(''); + const [message, setMessage] = useState(''); - const handleLogin = async (e: FormEvent) => { - e.preventDefault(); - setMessage('Loading...'); - const maybeError = await onClick(username, password); - if (maybeError) { - setMessage(maybeError); - } + const handleLogin = async (e: FormEvent) => { + e.preventDefault(); + setMessage('Loading...'); + const maybeError = await onClick(username, password); + if (maybeError) { + setMessage(maybeError); } + }; - return ( -
+
+ + setUsername(e.target.value)} + /> +
+
+ + setPassword(e.target.value)} + /> +
+
{message}
+ + + ); +}; -export default PasswordForm; \ No newline at end of file +export default PasswordForm; diff --git a/playground/connect-next/components.json b/playground/connect-next/components.json index dea737b85..639cbe5ea 100644 --- a/playground/connect-next/components.json +++ b/playground/connect-next/components.json @@ -18,4 +18,4 @@ "hooks": "@/hooks" }, "iconLibrary": "lucide" -} \ No newline at end of file +} diff --git a/playground/connect-next/components/ProtectedRoute.tsx b/playground/connect-next/components/ProtectedRoute.tsx index 164ba82c1..60ce1ca08 100644 --- a/playground/connect-next/components/ProtectedRoute.tsx +++ b/playground/connect-next/components/ProtectedRoute.tsx @@ -5,31 +5,31 @@ import { useRouter } from 'next/navigation'; import { fetchAuthSession } from 'aws-amplify/auth'; const isUserSignedIn = async (): Promise => { - try { - const session = await fetchAuthSession(); - return session?.tokens?.idToken != null; - } catch { - return false; - } + try { + const session = await fetchAuthSession(); + return session?.tokens?.idToken != null; + } catch { + return false; + } }; export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { - const router = useRouter(); - const [loading, setLoading] = useState(true); + const router = useRouter(); + const [loading, setLoading] = useState(true); - useEffect(() => { - isUserSignedIn().then(isSignedIn => { - if (!isSignedIn) { - router.replace('/login'); - } else { - setLoading(false); - } - }); - }, [router]); + useEffect(() => { + isUserSignedIn().then(isSignedIn => { + if (!isSignedIn) { + router.replace('/login'); + } else { + setLoading(false); + } + }); + }, [router]); - if (loading) return null; // or loading spinner + if (loading) return null; // or loading spinner - return <>{children}; + return <>{children}; }; -export default ProtectedRoute; \ No newline at end of file +export default ProtectedRoute; diff --git a/playground/connect-next/components/ui/button.tsx b/playground/connect-next/components/ui/button.tsx index 65d4fcd9c..6c51b8b01 100644 --- a/playground/connect-next/components/ui/button.tsx +++ b/playground/connect-next/components/ui/button.tsx @@ -1,57 +1,53 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import * as React from 'react'; +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', { variants: { variant: { - default: - "bg-primary text-primary-foreground shadow hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", - outline: - "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", - secondary: - "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", + default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', + destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', + outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', }, size: { - default: "h-9 px-4 py-2", - sm: "h-8 rounded-md px-3 text-xs", - lg: "h-10 rounded-md px-8", - icon: "h-9 w-9", + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'h-9 w-9', }, }, defaultVariants: { - variant: "default", - size: "default", + variant: 'default', + size: 'default', }, - } -) + }, +); export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { - asChild?: boolean + asChild?: boolean; } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" + const Comp = asChild ? Slot : 'button'; return ( - ) - } -) -Button.displayName = "Button" + ); + }, +); +Button.displayName = 'Button'; -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/playground/connect-next/components/ui/input-otp.tsx b/playground/connect-next/components/ui/input-otp.tsx index eb2a1baea..18c8fa106 100644 --- a/playground/connect-next/components/ui/input-otp.tsx +++ b/playground/connect-next/components/ui/input-otp.tsx @@ -1,71 +1,73 @@ -"use client" +'use client'; -import * as React from "react" -import { OTPInput, OTPInputContext } from "input-otp" -import { Minus } from "lucide-react" +import * as React from 'react'; +import { OTPInput, OTPInputContext } from 'input-otp'; +import { Minus } from 'lucide-react'; -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; -const InputOTP = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, containerClassName, ...props }, ref) => ( - -)) -InputOTP.displayName = "InputOTP" +const InputOTP = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ className, containerClassName, ...props }, ref) => ( + + ), +); +InputOTP.displayName = 'InputOTP'; -const InputOTPGroup = React.forwardRef< - React.ElementRef<"div">, - React.ComponentPropsWithoutRef<"div"> ->(({ className, ...props }, ref) => ( -
-)) -InputOTPGroup.displayName = "InputOTPGroup" +const InputOTPGroup = React.forwardRef, React.ComponentPropsWithoutRef<'div'>>( + ({ className, ...props }, ref) => ( +
+ ), +); +InputOTPGroup.displayName = 'InputOTPGroup'; const InputOTPSlot = React.forwardRef< - React.ElementRef<"div">, - React.ComponentPropsWithoutRef<"div"> & { index: number } + React.ElementRef<'div'>, + React.ComponentPropsWithoutRef<'div'> & { index: number } >(({ index, className, ...props }, ref) => { - const inputOTPContext = React.useContext(OTPInputContext) - const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index] + const inputOTPContext = React.useContext(OTPInputContext); + const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]; return (
{char} {hasFakeCaret && ( -
-
+
+
)}
- ) -}) -InputOTPSlot.displayName = "InputOTPSlot" + ); +}); +InputOTPSlot.displayName = 'InputOTPSlot'; -const InputOTPSeparator = React.forwardRef< - React.ElementRef<"div">, - React.ComponentPropsWithoutRef<"div"> ->(({ ...props }, ref) => ( -
- -
-)) -InputOTPSeparator.displayName = "InputOTPSeparator" +const InputOTPSeparator = React.forwardRef, React.ComponentPropsWithoutRef<'div'>>( + ({ ...props }, ref) => ( +
+ +
+ ), +); +InputOTPSeparator.displayName = 'InputOTPSeparator'; -export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } +export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }; diff --git a/playground/connect-next/lib/amplify-config.ts b/playground/connect-next/lib/amplify-config.ts index af8ec6aa0..e21887b9e 100644 --- a/playground/connect-next/lib/amplify-config.ts +++ b/playground/connect-next/lib/amplify-config.ts @@ -1,12 +1,15 @@ -import {Amplify} from 'aws-amplify'; +import { Amplify } from 'aws-amplify'; export const configureAmplify = () => { - Amplify.configure({ - Auth: { - Cognito: { - userPoolId: process.env.NEXT_PUBLIC_AWS_COGNITO_USER_POOL_ID!, - userPoolClientId: process.env.NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID!, - } - } - }, {ssr: true}); -} + Amplify.configure( + { + Auth: { + Cognito: { + userPoolId: process.env.NEXT_PUBLIC_AWS_COGNITO_USER_POOL_ID!, + userPoolClientId: process.env.NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID!, + }, + }, + }, + { ssr: true }, + ); +}; diff --git a/playground/connect-next/tailwind.config.ts b/playground/connect-next/tailwind.config.ts index 7bffbaf05..5d8b8db3c 100644 --- a/playground/connect-next/tailwind.config.ts +++ b/playground/connect-next/tailwind.config.ts @@ -1,67 +1,67 @@ import type { Config } from 'tailwindcss'; const config: Config = { - darkMode: ['class'], - content: [ + darkMode: ['class'], + content: [ './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { - extend: { - backgroundImage: { - 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', - 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))' - }, - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)' - }, - colors: { - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))' - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))' - }, - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))' - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))' - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))' - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))' - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))' - }, - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - chart: { - '1': 'hsl(var(--chart-1))', - '2': 'hsl(var(--chart-2))', - '3': 'hsl(var(--chart-3))', - '4': 'hsl(var(--chart-4))', - '5': 'hsl(var(--chart-5))' - } - } - } + extend: { + backgroundImage: { + 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', + 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)', + }, + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))', + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))', + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))', + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))', + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))', + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))', + }, + }, + }, }, - plugins: [require('@tailwindcss/forms'), require("tailwindcss-animate")], + plugins: [require('@tailwindcss/forms'), require('tailwindcss-animate')], }; export default config; From 479ed1380c978e8e85e807a5e938acd2c27d01f4 Mon Sep 17 00:00:00 2001 From: Incorbador Date: Sun, 4 May 2025 18:24:58 +0200 Subject: [PATCH 06/60] Fix build --- playground/connect-next/app/redirect/page.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/playground/connect-next/app/redirect/page.tsx b/playground/connect-next/app/redirect/page.tsx index 3b779a692..8e0382ac4 100644 --- a/playground/connect-next/app/redirect/page.tsx +++ b/playground/connect-next/app/redirect/page.tsx @@ -1,18 +1,26 @@ 'use client'; -import { redirect, useRouter, useSearchParams } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { setIdToken } from './actions'; -import { useEffect, useState } from 'react'; +import { Suspense, useEffect, useState } from 'react'; export default function Page() { + return ( + + + + ); +} + +function Redirecting() { const searchParams = useSearchParams(); + const token = searchParams.get('token'); + const redirectUrl = searchParams.get('redirectUrl'); const [loading, setLoading] = useState(true); const router = useRouter(); useEffect(() => { const init = async () => { - const token = searchParams.get('token'); - const redirectUrl = searchParams.get('redirectUrl'); if (!token || !redirectUrl) { return; } From ff4c3dae69c786f922d559ec2d968a803b4bb768 Mon Sep 17 00:00:00 2001 From: Incorbador Date: Sun, 4 May 2025 19:03:11 +0200 Subject: [PATCH 07/60] Change wv auth --- .../app/{(auth-required) => }/passkey-list-wv/actions.ts | 0 .../app/{(auth-required) => }/passkey-list-wv/page.tsx | 0 .../app/{(auth-required) => }/post-login-wv/actions.ts | 0 .../app/{(auth-required) => }/post-login-wv/page.tsx | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) rename playground/connect-next/app/{(auth-required) => }/passkey-list-wv/actions.ts (100%) rename playground/connect-next/app/{(auth-required) => }/passkey-list-wv/page.tsx (100%) rename playground/connect-next/app/{(auth-required) => }/post-login-wv/actions.ts (100%) rename playground/connect-next/app/{(auth-required) => }/post-login-wv/page.tsx (89%) diff --git a/playground/connect-next/app/(auth-required)/passkey-list-wv/actions.ts b/playground/connect-next/app/passkey-list-wv/actions.ts similarity index 100% rename from playground/connect-next/app/(auth-required)/passkey-list-wv/actions.ts rename to playground/connect-next/app/passkey-list-wv/actions.ts diff --git a/playground/connect-next/app/(auth-required)/passkey-list-wv/page.tsx b/playground/connect-next/app/passkey-list-wv/page.tsx similarity index 100% rename from playground/connect-next/app/(auth-required)/passkey-list-wv/page.tsx rename to playground/connect-next/app/passkey-list-wv/page.tsx diff --git a/playground/connect-next/app/(auth-required)/post-login-wv/actions.ts b/playground/connect-next/app/post-login-wv/actions.ts similarity index 100% rename from playground/connect-next/app/(auth-required)/post-login-wv/actions.ts rename to playground/connect-next/app/post-login-wv/actions.ts diff --git a/playground/connect-next/app/(auth-required)/post-login-wv/page.tsx b/playground/connect-next/app/post-login-wv/page.tsx similarity index 89% rename from playground/connect-next/app/(auth-required)/post-login-wv/page.tsx rename to playground/connect-next/app/post-login-wv/page.tsx index 8580f2185..bbbcb7788 100644 --- a/playground/connect-next/app/(auth-required)/post-login-wv/page.tsx +++ b/playground/connect-next/app/post-login-wv/page.tsx @@ -1,6 +1,6 @@ 'use client'; import { CorbadoConnectAppend } from '@corbado/connect-react'; -import { getCorbadoToken, postPasskeyAppend } from '@/app/(auth-required)/post-login-wv/actions'; +import { getCorbadoToken, postPasskeyAppend } from '@/app/post-login-wv/actions'; import { AppendStatus } from '@corbado/types'; export default function PostLoginPage() { From eb7bdc6b641d73e5b96e26b7345cbb21921ff151 Mon Sep 17 00:00:00 2001 From: Incorbador Date: Wed, 7 May 2025 21:30:01 +0200 Subject: [PATCH 08/60] Add connectToken endpoint (connect-next playground) --- .../app/(api)/connectToken/route.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 playground/connect-next/app/(api)/connectToken/route.ts diff --git a/playground/connect-next/app/(api)/connectToken/route.ts b/playground/connect-next/app/(api)/connectToken/route.ts new file mode 100644 index 000000000..891a0b6d4 --- /dev/null +++ b/playground/connect-next/app/(api)/connectToken/route.ts @@ -0,0 +1,25 @@ +import { NextRequest } from 'next/server'; +import { getCorbadoConnectToken, verifyAmplifyToken } from '@/lib/utils'; + +type Payload = { + idToken: string; + connectTokenType: string; +}; + +export async function POST(req: NextRequest) { + const body = (await req.json()) as Payload; + + const { idToken, connectTokenType } = body; + + const { displayName, identifier } = await verifyAmplifyToken(idToken); + + const connectToken = await getCorbadoConnectToken(connectTokenType, { + displayName: displayName, + identifier: identifier, + }); + + return new Response(JSON.stringify({ token: connectToken }), { + status: 201, + headers: { 'Content-Type': 'application/json' }, + }); +} From 7134d50fbd0e016252a9c5dc0252275fc2bcded7 Mon Sep 17 00:00:00 2001 From: Incorbador Date: Sun, 18 May 2025 18:02:47 +0200 Subject: [PATCH 09/60] Add connectTokenExternal route --- .../app/(api)/connectTokenExternal/route.ts | 34 +++++++++++++ playground/connect-next/lib/utils.ts | 48 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 playground/connect-next/app/(api)/connectTokenExternal/route.ts diff --git a/playground/connect-next/app/(api)/connectTokenExternal/route.ts b/playground/connect-next/app/(api)/connectTokenExternal/route.ts new file mode 100644 index 000000000..1ef175ab1 --- /dev/null +++ b/playground/connect-next/app/(api)/connectTokenExternal/route.ts @@ -0,0 +1,34 @@ +import { NextRequest } from 'next/server'; +import { getCorbadoConnectTokenExternal, verifyAmplifyTokenExternal } from '@/lib/utils'; + +type Payload = { + idToken: string; + connectTokenType: string; +}; + +export async function POST(req: NextRequest) { + const body = (await req.json()) as Payload; + + const { idToken, connectTokenType } = body; + + try { + const { displayName, identifier } = await verifyAmplifyTokenExternal(idToken); + + const connectToken = await getCorbadoConnectTokenExternal(connectTokenType, { + displayName: displayName, + identifier: identifier, + }); + + return new Response(JSON.stringify({ token: connectToken }), { + status: 201, + headers: { 'Content-Type': 'application/json' }, + }); + } catch (e) { + console.error('Error verifying token or getting connect token', e); + + return new Response(JSON.stringify({ error: 'Failed to verify token or get connect token' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } +} diff --git a/playground/connect-next/lib/utils.ts b/playground/connect-next/lib/utils.ts index 023af8bef..98e6b4264 100644 --- a/playground/connect-next/lib/utils.ts +++ b/playground/connect-next/lib/utils.ts @@ -26,6 +26,27 @@ export const verifyAmplifyToken = async (idToken: string): Promise => return { displayName, identifier }; }; +export const verifyAmplifyTokenExternal = async (idToken: string): Promise => { + const verifier = CognitoJwtVerifier.create({ + userPoolId: process.env.AWS_COGNITO_USER_POOL_ID_EXTERNAL!, + tokenUse: 'id', + clientId: process.env.AWS_COGNITO_CLIENT_ID_EXTERNAL!, + }); + + console.log( + 'verifying token with external verifier', + idToken, + process.env.AWS_COGNITO_USER_POOL_ID_EXTERNAL, + process.env.AWS_COGNITO_CLIENT_ID_EXTERNAL, + ); + + const verifiedToken = await verifier.verify(idToken); + const displayName: string = verifiedToken.email as string; + const identifier = verifiedToken['cognito:username']; + + return { displayName, identifier }; +}; + export type CognitoUserInfo = { username: string; email: string; @@ -68,3 +89,30 @@ export const getCorbadoConnectToken = async (connectTokenType: string, connectTo return out.secret; }; + +export const getCorbadoConnectTokenExternal = async ( + connectTokenType: string, + connectTokenData: any, +): Promise => { + const payload = { + type: connectTokenType, + data: connectTokenData, + }; + + const body = JSON.stringify(payload); + + const url = `${process.env.CORBADO_BACKEND_API_URL_EXTERNAL}/v2/connectTokens`; + const response = await fetch(url, { + method: 'POST', + headers: { + Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH_EXTERNAL}`, + 'Content-Type': 'application/json', + }, + cache: 'no-cache', + body: body, + }); + + const out = await response.json(); + + return out.secret; +}; From 009078c73f8bb4782acb36ed55ffed8aa3516efa Mon Sep 17 00:00:00 2001 From: Incorbador Date: Tue, 3 Jun 2025 13:46:56 +0200 Subject: [PATCH 10/60] Add static well-known for connect-next playground --- .../connect-next/public/.well-known/apple-app-site-association | 1 + 1 file changed, 1 insertion(+) create mode 100644 playground/connect-next/public/.well-known/apple-app-site-association diff --git a/playground/connect-next/public/.well-known/apple-app-site-association b/playground/connect-next/public/.well-known/apple-app-site-association new file mode 100644 index 000000000..d1561dfdd --- /dev/null +++ b/playground/connect-next/public/.well-known/apple-app-site-association @@ -0,0 +1 @@ +{"appclips":{"apps":[]},"applinks":{"details":[{"appID":"T9A667JL6T.com.corbado.ios.ConnectExample","paths":["*"]}]},"webcredentials":{"apps":["T9A667JL6T.com.corbado.ios.ConnectExample"]}} From 1802473295e3a8a43b9314e413651976f0941312 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 10 Jun 2025 13:11:29 +0200 Subject: [PATCH 11/60] start react playground in playwright --- package-lock.json | 34 +++++++++- packages/tests-e2e/package.json | 4 +- .../tests-e2e/playwright.config.complete.ts | 29 +++++++- .../src/complete/models/CorbadoAuthModel.ts | 4 +- .../src/complete/models/PasskeyListModel.ts | 4 +- .../EmailVerifyBlockModel.ts | 8 +-- ...il-username-verification-at-signup.spec.ts | 11 ++- .../email-verification-at-login.spec.ts | 21 ++++-- .../email-verification-at-signup.spec.ts | 21 ++++-- .../email-verification-none.spec.ts | 21 ++++-- .../phone-verification-at-signup.spec.ts | 21 ++++-- .../username.spec.ts | 15 ++++- .../email-link-verify-obfuscation.spec.ts | 17 +++-- .../email-otp-verify-general.spec.ts | 24 ++++--- .../login-init-email.spec.ts | 25 ++++--- .../login-init-no-public-signup.spec.ts | 17 ++++- .../phone-otp-verify-obfuscation.spec.ts | 15 ++++- .../corbado-auth-general/routing.spec.ts | 15 ++++- .../corbado-auth-general/signup-init.spec.ts | 17 +++-- .../corbado-auth-general/socials.spec.ts | 31 ++++++--- .../passkey-list-general/general.spec.ts | 15 ++++- .../src/complete/utils/playground.ts | 67 +++++++++++++++++++ playground/react/package.json | 1 + 23 files changed, 350 insertions(+), 87 deletions(-) create mode 100644 packages/tests-e2e/src/complete/utils/playground.ts diff --git a/package-lock.json b/package-lock.json index 51de54f94..2bf13c43f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21746,6 +21746,34 @@ "node": ">=10" } }, + "node_modules/wait-port": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.1.0.tgz", + "integrity": "sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "commander": "^9.3.0", + "debug": "^4.3.4" + }, + "bin": { + "wait-port": "bin/wait-port.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wait-port/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/walk-up-path": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", @@ -22460,7 +22488,7 @@ }, "packages/shared-util": { "name": "@corbado/shared-util", - "version": "1.0.10", + "version": "1.0.11", "license": "MIT", "devDependencies": { "@babel/core": "^7.24.0", @@ -22919,9 +22947,11 @@ "devDependencies": { "@playwright/test": "^1.47.0", "@types/node": "^20.10.5", + "get-port": "^5.1.1", "ngrok": "^5.0.0-beta.2", "node-2fa": "^2.0.3", - "playwright-slack-report": "^1.1.72" + "playwright-slack-report": "^1.1.72", + "wait-port": "^1.1.0" } }, "packages/types": { diff --git a/packages/tests-e2e/package.json b/packages/tests-e2e/package.json index aa87efa6b..c869b6e0f 100644 --- a/packages/tests-e2e/package.json +++ b/packages/tests-e2e/package.json @@ -24,9 +24,11 @@ "devDependencies": { "@playwright/test": "^1.47.0", "@types/node": "^20.10.5", + "get-port": "^5.1.1", "ngrok": "^5.0.0-beta.2", "node-2fa": "^2.0.3", - "playwright-slack-report": "^1.1.72" + "playwright-slack-report": "^1.1.72", + "wait-port": "^1.1.0" }, "dependencies": { "dotenv": "^16.4.5" diff --git a/packages/tests-e2e/playwright.config.complete.ts b/packages/tests-e2e/playwright.config.complete.ts index 5f3163afa..dc5a2269e 100644 --- a/packages/tests-e2e/playwright.config.complete.ts +++ b/packages/tests-e2e/playwright.config.complete.ts @@ -13,6 +13,27 @@ if (process.env.CI) { dotenv.config({ path: path.resolve(__dirname, '.env.complete.local'), override: true }); } +// type PlaygroundType = 'react' | 'web-js' | 'web-js-script'; +// +// const PLAYGROUND_TYPE: PlaygroundType = +// (process.env.PLAYGROUND_TYPE as PlaygroundType) || 'react'; +// +// let webServerCommand: string; +// +// switch (PLAYGROUND_TYPE) { +// case 'react': +// webServerCommand = 'cd ../../playground/react && npm i && npm run build && npm run preview'; +// break; +// case 'web-js': +// webServerCommand = 'npm run serve:web-js-playground'; +// break; +// case 'web-js-script': +// webServerCommand = 'npm run serve:web-js-script-playground'; +// break; +// default: +// throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); +// } + export default defineConfig({ testDir: './src/complete', // fullyParallel: true, @@ -53,7 +74,7 @@ export default defineConfig({ use: { actionTimeout: operationTimeout, // default: none navigationTimeout: operationTimeout, // default: none - baseURL: process.env.PLAYWRIGHT_TEST_URL, + // baseURL: process.env.PLAYWRIGHT_TEST_URL, screenshot: 'only-on-failure', video: 'retain-on-failure', trace: 'retain-on-failure', @@ -72,4 +93,10 @@ export default defineConfig({ testMatch: ['scenarios/passkey-list-general/*.ts'], }, ], + // webServer: { + // command: webServerCommand, + // url: 'http://localhost:4173', + // reuseExistingServer: !process.env.CI, + // timeout: 15 * 1000, + // }, }); diff --git a/packages/tests-e2e/src/complete/models/CorbadoAuthModel.ts b/packages/tests-e2e/src/complete/models/CorbadoAuthModel.ts index b9a854142..479953f21 100644 --- a/packages/tests-e2e/src/complete/models/CorbadoAuthModel.ts +++ b/packages/tests-e2e/src/complete/models/CorbadoAuthModel.ts @@ -34,10 +34,10 @@ export class CorbadoAuthModel { this.phoneVerify = new PhoneVerifyBlockModel(page); } - async load(projectId: string, passkeySupport?: boolean, hashCode?: string) { + async load(projectId: string, port: number, passkeySupport?: boolean, hashCode?: string) { this.projectId = projectId; - let url = `/${this.projectId}/auth`; + let url = `http://localhost:${port.toString()}/${this.projectId}/auth`; if (hashCode) { url += `#${hashCode}`; } diff --git a/packages/tests-e2e/src/complete/models/PasskeyListModel.ts b/packages/tests-e2e/src/complete/models/PasskeyListModel.ts index ac14fc440..1fc9dbed5 100644 --- a/packages/tests-e2e/src/complete/models/PasskeyListModel.ts +++ b/packages/tests-e2e/src/complete/models/PasskeyListModel.ts @@ -21,14 +21,14 @@ export class PasskeyListModel { this.virtualAuthenticator = virtualAuthenticator; } - async load(projectId: string, passkeySupport?: boolean) { + async load(projectId: string, port: number, passkeySupport?: boolean) { this.projectId = projectId; if (passkeySupport !== undefined) { await this.virtualAuthenticator.addWebAuthn(passkeySupport); } - const url = `/${this.projectId}/auth#signup-init`; + const url = `http://localhost:${port.toString()}/${this.projectId}/auth#signup-init`; await this.page.goto(url); await this.page.waitForSelector('.cb-container-body'); diff --git a/packages/tests-e2e/src/complete/models/corbado-auth-blocks/EmailVerifyBlockModel.ts b/packages/tests-e2e/src/complete/models/corbado-auth-blocks/EmailVerifyBlockModel.ts index acb6916cb..17291b31d 100644 --- a/packages/tests-e2e/src/complete/models/corbado-auth-blocks/EmailVerifyBlockModel.ts +++ b/packages/tests-e2e/src/complete/models/corbado-auth-blocks/EmailVerifyBlockModel.ts @@ -27,8 +27,8 @@ export class EmailVerifyBlockModel { } } - async clickEmailLink(projectID: string, email: string, authType: AuthType, type: LinkType) { - const link = await this.#generateEmailLink(projectID, email, authType, type); + async clickEmailLink(projectID: string, port: number, email: string, authType: AuthType, type: LinkType) { + const link = await this.#generateEmailLink(projectID, port, email, authType, type); await this.page.goto(link); } @@ -64,7 +64,7 @@ export class EmailVerifyBlockModel { ).toBeVisible(); } - async #generateEmailLink(projectID: string, email: string, authType: AuthType, linkType: LinkType) { + async #generateEmailLink(projectID: string, port: number, email: string, authType: AuthType, linkType: LinkType) { const key = `cbo_auth_process-${projectID}`; const cboAuthProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); if (!cboAuthProcessRaw) { @@ -89,6 +89,6 @@ export class EmailVerifyBlockModel { const serializedBlock = btoa(JSON.stringify(urlBlock)); - return `${process.env.PLAYWRIGHT_TEST_URL}/${projectID}/auth?corbadoEmailLinkID=${serializedBlock}&corbadoToken=${linkType}`; + return `http://localhost:${port.toString()}/${projectID}/auth?corbadoEmailLinkID=${serializedBlock}&corbadoToken=${linkType}`; } } diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-username-verification-at-signup.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-username-verification-at-signup.spec.ts index fe964f40a..f1ad5c1d7 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-username-verification-at-signup.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-username-verification-at-signup.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { OtpCodeType } from '../../models/corbado-auth-blocks/EmailVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -8,9 +10,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('tests that focus on these identifiers: email (verification at signup) + username', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -21,14 +26,18 @@ test.describe('tests that focus on these identifiers: email (verification at sig ]), makeIdentifier(IdentifierType.Username, IdentifierEnforceVerification.None, true, []), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with passkey (happy path)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); const username = SignupInitBlockModel.generateRandomUsername(); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-login.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-login.spec.ts index eec905dd0..21c52c5d4 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-login.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-login.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { OtpCodeType } from '../../models/corbado-auth-blocks/EmailVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -8,9 +10,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('tests that focus on these identifiers: email (verification at login)', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -20,14 +25,18 @@ test.describe('tests that focus on these identifiers: email (verification at log IdentifierVerification.EmailOtp, ]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with passkey (happy path)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -44,7 +53,7 @@ test.describe('tests that focus on these identifiers: email (verification at log }); test('signup with passkey (one passkey retry)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -58,7 +67,7 @@ test.describe('tests that focus on these identifiers: email (verification at log }); test('signup with explicit fallback', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -70,7 +79,7 @@ test.describe('tests that focus on these identifiers: email (verification at log await model.logout(); // no passkey is available => we expect an emailVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); await model.emailVerify.fillOtpCode(OtpCodeType.Correct); @@ -82,7 +91,7 @@ test.describe('tests that focus on these identifiers: email (verification at log // during signup, the fallback is initiated automatically (user does not have to click on the fallback button) // during login, we don't ask the user to append a passkey test('signup with fallback (passkeys not available)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -93,7 +102,7 @@ test.describe('tests that focus on these identifiers: email (verification at log await model.logout(); // no passkey is available => we expect an emailVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); await model.emailVerify.fillOtpCode(OtpCodeType.Correct); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-signup.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-signup.spec.ts index 0c2978d98..8f6a1b88c 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-signup.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-signup.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { OtpCodeType } from '../../models/corbado-auth-blocks/EmailVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -8,10 +10,13 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; // this is the default component config that we offer test.describe('tests that focus on these identifiers: email (verification at signup)', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -21,14 +26,18 @@ test.describe('tests that focus on these identifiers: email (verification at sig IdentifierVerification.EmailOtp, ]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with passkey (happy path)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -45,7 +54,7 @@ test.describe('tests that focus on these identifiers: email (verification at sig }); test('signup with passkey (one passkey retry)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -61,7 +70,7 @@ test.describe('tests that focus on these identifiers: email (verification at sig }); test('signup with explicit fallback', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -73,7 +82,7 @@ test.describe('tests that focus on these identifiers: email (verification at sig await model.logout(); // no passkey is available => we expect an emailVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); await model.emailVerify.fillOtpCode(OtpCodeType.Correct); @@ -85,7 +94,7 @@ test.describe('tests that focus on these identifiers: email (verification at sig // during signup, the fallback is initiated automatically (user does not have to click on the fallback button) // during login, we don't ask the user to append a passkey test('signup with fallback (passkeys not available)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -96,7 +105,7 @@ test.describe('tests that focus on these identifiers: email (verification at sig await model.logout(); // no passkey is available => we expect an emailVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); await model.emailVerify.fillOtpCode(OtpCodeType.Correct); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-none.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-none.spec.ts index a69569d4b..0622178ba 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-none.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-none.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { OtpCodeType } from '../../models/corbado-auth-blocks/EmailVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -8,9 +10,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('tests that focus on these identifiers: email (verification none)', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -18,14 +23,18 @@ test.describe('tests that focus on these identifiers: email (verification none)' await setComponentConfig(projectId, [ makeIdentifier(IdentifierType.Email, IdentifierEnforceVerification.None, true, [IdentifierVerification.EmailOtp]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with passkey (happy path)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -41,7 +50,7 @@ test.describe('tests that focus on these identifiers: email (verification none)' }); test('signup with passkey (one passkey retry)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -55,7 +64,7 @@ test.describe('tests that focus on these identifiers: email (verification none)' }); test('signup with explicit fallback', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -67,7 +76,7 @@ test.describe('tests that focus on these identifiers: email (verification none)' await model.logout(); // no passkey is available => we expect an emailVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); await model.emailVerify.fillOtpCode(OtpCodeType.Correct); @@ -79,7 +88,7 @@ test.describe('tests that focus on these identifiers: email (verification none)' // during signup, the fallback is initiated automatically (user does not have to click on the fallback button) // during login, we don't ask the user to append a passkey test('signup with fallback (passkeys not available)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -90,7 +99,7 @@ test.describe('tests that focus on these identifiers: email (verification none)' await model.logout(); // no passkey is available => we expect an emailVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); await model.emailVerify.fillOtpCode(OtpCodeType.Correct); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/phone-verification-at-signup.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/phone-verification-at-signup.spec.ts index 8dbb7e022..3465c7421 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/phone-verification-at-signup.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/phone-verification-at-signup.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { OtpCodeType } from '../../models/corbado-auth-blocks/PhoneVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -8,9 +10,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('tests that focus on these identifiers: phone (verification at signup)', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -20,14 +25,18 @@ test.describe('tests that focus on these identifiers: phone (verification at sig IdentifierVerification.PhoneOtp, ]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with passkey (happy path)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomPhone(); await model.signupInit.fillPhone(identifier); @@ -44,7 +53,7 @@ test.describe('tests that focus on these identifiers: phone (verification at sig }); test('signup with passkey (one passkey retry)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomPhone(); await model.signupInit.fillPhone(identifier); @@ -60,7 +69,7 @@ test.describe('tests that focus on these identifiers: phone (verification at sig }); test('signup with explicit fallback', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomPhone(); await model.signupInit.fillPhone(identifier); @@ -72,7 +81,7 @@ test.describe('tests that focus on these identifiers: phone (verification at sig await model.logout(); // no passkey is available => we expect an phoneVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillPhone(identifier); await model.loginInit.submitPrimary(); await model.phoneVerify.fillOtpCode(OtpCodeType.Correct); @@ -84,7 +93,7 @@ test.describe('tests that focus on these identifiers: phone (verification at sig // during signup, the fallback is initiated automatically (user does not have to click on the fallback button) // during login, we don't ask the user to append a passkey test('signup with fallback (passkeys not available)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomPhone(); await model.signupInit.fillPhone(identifier); @@ -95,7 +104,7 @@ test.describe('tests that focus on these identifiers: phone (verification at sig await model.logout(); // no passkey is available => we expect an phoneVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillPhone(identifier); await model.loginInit.submitPrimary(); await model.phoneVerify.fillOtpCode(OtpCodeType.Correct); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/username.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/username.spec.ts index b45301f80..9bb45fce9 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/username.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/username.spec.ts @@ -1,10 +1,15 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; import { IdentifierEnforceVerification, IdentifierType, ScreenNames } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('tests that focus on these identifiers: username', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -12,14 +17,18 @@ test.describe('tests that focus on these identifiers: username', () => { await setComponentConfig(projectId, [ makeIdentifier(IdentifierType.Username, IdentifierEnforceVerification.None, true, []), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with passkey (happy path)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomUsername(); await model.signupInit.fillUsername(identifier); @@ -35,7 +44,7 @@ test.describe('tests that focus on these identifiers: username', () => { }); test('signup with passkey (one passkey retry)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomUsername(); await model.signupInit.fillUsername(identifier); @@ -51,7 +60,7 @@ test.describe('tests that focus on these identifiers: username', () => { // this tests a dead end scenario where passkeys are not available but the user can not signup // TODO: fix test.skip('signup with fallback (passkeys not available)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomUsername(); await model.signupInit.fillUsername(identifier); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-link-verify-obfuscation.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-link-verify-obfuscation.spec.ts index abd51326b..1ca5dc63e 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-link-verify-obfuscation.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-link-verify-obfuscation.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { expect, test } from '../../fixtures/CorbadoAuth'; import { LinkType } from '../../models/corbado-auth-blocks/EmailVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -9,9 +11,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('email-verify block should obfuscate email addresses if they have not been provided by the user during login', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -22,14 +27,18 @@ test.describe('email-verify block should obfuscate email addresses if they have ]), makeIdentifier(IdentifierType.Username, IdentifierEnforceVerification.None, true, []), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('email is obfuscated during login if the login is started with username', async ({ model, page }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); const username = SignupInitBlockModel.generateRandomUsername(); @@ -38,18 +47,18 @@ test.describe('email-verify block should obfuscate email addresses if they have await model.signupInit.submitPrimary(); await model.expectScreen(ScreenNames.EmailLinkSentSignup); - await model.emailVerify.clickEmailLink(projectId, email, AuthType.Login, LinkType.Correct); + await model.emailVerify.clickEmailLink(projectId, port, email, AuthType.Login, LinkType.Correct); await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, false, 'login-init'); + await model.load(projectId, port, false, 'login-init'); await model.loginInit.fillEmailUsername(username); await model.loginInit.submitPrimary(); await model.expectScreen(ScreenNames.EmailLinkSentLogin); await expect(page.getByText(email)).toHaveCount(0); - await model.load(projectId, false, 'login-init'); + await model.load(projectId, port, false, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-otp-verify-general.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-otp-verify-general.spec.ts index 1d5f8ce6e..c06064719 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-otp-verify-general.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-otp-verify-general.spec.ts @@ -8,10 +8,14 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; +import type { ChildProcess } from 'node:child_process'; // Here we test everything on LoginInit screen test.describe('general email-verify functionalities', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -19,14 +23,18 @@ test.describe('general email-verify functionalities', () => { await setComponentConfig(projectId, [ makeIdentifier(IdentifierType.Email, IdentifierEnforceVerification.None, true, [IdentifierVerification.EmailOtp]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with fallback (one retry)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -41,7 +49,7 @@ test.describe('general email-verify functionalities', () => { }); test('signup with fallback + identifier change', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email1 = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email1); @@ -58,7 +66,7 @@ test.describe('general email-verify functionalities', () => { await model.logout(); // only login with email2 should be possible - await model.load(projectId, false, 'login-init'); + await model.load(projectId, port, false, 'login-init'); await model.loginInit.fillEmailUsername(email1); await model.loginInit.submitPrimary(); await model.loginInit.expectTextError("Couldn't find your account."); @@ -69,7 +77,7 @@ test.describe('general email-verify functionalities', () => { }); test('signup with fallback + identifier change (abort + continue with email 1)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email1 = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email1); @@ -86,7 +94,7 @@ test.describe('general email-verify functionalities', () => { await model.logout(); // only login with email1 should be possible - await model.load(projectId, false, 'login-init'); + await model.load(projectId, port, false, 'login-init'); await model.loginInit.fillEmailUsername(email2); await model.loginInit.submitPrimary(); await model.loginInit.expectTextError("Couldn't find your account."); @@ -100,7 +108,7 @@ test.describe('general email-verify functionalities', () => { // then we start a new process and signup with email 2 // during verification we try to switch back to email 1 => this must fail test('signup with fallback + identifier change (email already exists)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email1 = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email1); @@ -109,7 +117,7 @@ test.describe('general email-verify functionalities', () => { await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email2 = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email2); @@ -123,7 +131,7 @@ test.describe('general email-verify functionalities', () => { // TODO: fix test.skip('signup with explicit fallback (too many wrong codes)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-email.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-email.spec.ts index 5a74ba044..932b848cb 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-email.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-email.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { IdentifierEnforceVerification, @@ -6,10 +8,13 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; // Here we test everything on LoginInit screen test.describe('login-init', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -17,14 +22,18 @@ test.describe('login-init', () => { await setComponentConfig(projectId, [ makeIdentifier(IdentifierType.Email, IdentifierEnforceVerification.None, true, [IdentifierVerification.EmailOtp]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('bad user input: empty email', async ({ model }) => { - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.loginInit.fillEmailUsername(''); await model.loginInit.submitPrimary(); @@ -33,7 +42,7 @@ test.describe('login-init', () => { // TODO: fix and enable test.skip('bad user input: invalid email', async ({ model }) => { - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.loginInit.fillEmailUsername('a@a'); await model.loginInit.submitPrimary(); @@ -41,7 +50,7 @@ test.describe('login-init', () => { }); test('bad user input: non-existing email', async ({ model }) => { - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.loginInit.fillEmailUsername('integration-test+notexist@corbado.com'); await model.loginInit.submitPrimary(); @@ -53,7 +62,7 @@ test.describe('login-init', () => { // TODO: bad user input: non-existing phone test('switch to signup', async ({ model }) => { - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.expectScreen(ScreenNames.InitLogin); await model.loginInit.navigateToSignup(); @@ -63,7 +72,7 @@ test.describe('login-init', () => { // after a signup with passkey a passkey button should be shown // the button should disappear after when one explicitly switches the identifier test('passkey button should be shown', async ({ model }) => { - await model.load(projectId, true); + await model.load(projectId, port, true); await model.defaultSignupWithPasskey(); @@ -71,7 +80,7 @@ test.describe('login-init', () => { await model.expectScreen(ScreenNames.InitLogin); await model.loginInit.expectPasskeyButton(true); - await model.load(projectId); + await model.load(projectId, port); await model.loginInit.expectPasskeyButton(true); await model.passkeyVerify.performAutomaticPasskeyVerification(() => model.loginInit.submitPasskeyButton()); @@ -86,10 +95,10 @@ test.describe('login-init', () => { // after a signup without passkey no passkey button should be shown test('passkey button should not be shown', async ({ model }) => { - await model.load(projectId, true); + await model.load(projectId, port, true); await model.defaultSignupWithFallback(); - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); // after a signup with passkey a passkey button should be shown => we use it to login await model.expectScreen(ScreenNames.InitLogin); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-no-public-signup.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-no-public-signup.spec.ts index 284c4f43d..18532b3a9 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-no-public-signup.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-no-public-signup.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { expect, test } from '../../fixtures/CorbadoAuth'; import { IdentifierEnforceVerification, @@ -6,11 +8,14 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; // Developers can disable public signup // In that case UI components no longer will allow a signup test.describe('login-init no public signup', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -26,19 +31,25 @@ test.describe('login-init no public signup', () => { false, false, ); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); - test.afterAll(async () => deleteProjectNew(projectId)); + test.afterAll(async () => { + await deleteProjectNew(projectId); + + killPlaygroundNew(server); + }); test('switch to signup should not be possible (button)', async ({ model, page }) => { - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.expectScreen(ScreenNames.InitLogin); await expect(page.getByRole('button', { name: 'Sign up' })).toBeHidden(); }); test('switch to signup should not be possible (hashCode)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); await model.expectScreen(ScreenNames.InitLogin); }); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/phone-otp-verify-obfuscation.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/phone-otp-verify-obfuscation.spec.ts index 0344f3da0..15fa69962 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/phone-otp-verify-obfuscation.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/phone-otp-verify-obfuscation.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { expect, test } from '../../fixtures/CorbadoAuth'; import { OtpCodeType } from '../../models/corbado-auth-blocks/PhoneVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -8,9 +10,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('phone-verify block should obfuscate phone numbers if they have not been provided by the user during login', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -19,14 +24,18 @@ test.describe('phone-verify block should obfuscate phone numbers if they have no makeIdentifier(IdentifierType.Phone, IdentifierEnforceVerification.None, true, [IdentifierVerification.PhoneOtp]), makeIdentifier(IdentifierType.Username, IdentifierEnforceVerification.None, true, []), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('phone number is obfuscated during login if the login is started with username', async ({ model, page }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const phone = SignupInitBlockModel.generateRandomPhone(); const username = SignupInitBlockModel.generateRandomUsername(); @@ -37,14 +46,14 @@ test.describe('phone-verify block should obfuscate phone numbers if they have no await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, false, 'login-init'); + await model.load(projectId, port, false, 'login-init'); await model.loginInit.fillEmailUsername(username); await model.loginInit.submitPrimary(); await model.expectScreen(ScreenNames.PhoneOtpLogin); await expect(page.getByText(phone)).toHaveCount(0); - await model.load(projectId, false, 'login-init'); + await model.load(projectId, port, false, 'login-init'); await model.loginInit.switchToInputPhone(); await model.loginInit.fillPhone(phone); await model.loginInit.submitPrimary(); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/routing.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/routing.spec.ts index 597caf893..77f6f2f65 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/routing.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/routing.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; import { @@ -7,9 +9,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('routing', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -17,26 +22,30 @@ test.describe('routing', () => { await setComponentConfig(projectId, [ makeIdentifier(IdentifierType.Email, IdentifierEnforceVerification.None, true, [IdentifierVerification.EmailOtp]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('initial routing should happen depending on the hashCode (signup-init)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); await model.expectScreen(ScreenNames.InitSignup); }); test('initial routing should happen depending on the hashCode (login-init)', async ({ model }) => { - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.expectScreen(ScreenNames.InitLogin); }); test('initial routing should happen depending on local storage', async ({ model }) => { - await model.load(projectId, true); + await model.load(projectId, port, true); // by default the signup screen is loaded await model.expectScreen(ScreenNames.InitSignup); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/signup-init.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/signup-init.spec.ts index a78ec0896..659c4db49 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/signup-init.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/signup-init.spec.ts @@ -1,10 +1,15 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; import { IdentifierEnforceVerification, IdentifierType, IdentifierVerification } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('signup-init', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -14,14 +19,18 @@ test.describe('signup-init', () => { makeIdentifier(IdentifierType.Phone, IdentifierEnforceVerification.None, true, [IdentifierVerification.PhoneOtp]), makeIdentifier(IdentifierType.Username, IdentifierEnforceVerification.None, true, []), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('bad user input: all fields empty', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); await model.signupInit.fillEmail(''); await model.signupInit.fillPhone(''); @@ -34,7 +43,7 @@ test.describe('signup-init', () => { }); test('bad user input: invalid fields (simple)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); await model.signupInit.fillEmail('asdf@asdf'); await model.signupInit.fillPhone('1234'); @@ -47,7 +56,7 @@ test.describe('signup-init', () => { }); test('bad user input: duplicate fields', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); const phone = SignupInitBlockModel.generateRandomPhone(); @@ -60,7 +69,7 @@ test.describe('signup-init', () => { await model.passkeyAppend.startPasskeyOperation(true); await model.logout(); - await model.load(projectId, undefined, 'signup-init'); + await model.load(projectId, port, undefined, 'signup-init'); await model.signupInit.fillEmail(email); await model.signupInit.fillPhone(phone); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/socials.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/socials.spec.ts index 402ab93bd..f1ce8784d 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/socials.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/socials.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { IdentifierEnforceVerification, @@ -15,9 +17,12 @@ import { makeSocialProvider, setComponentConfig, } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('social logins', () => { let projectId: string; + let server: ChildProcess; + let port: number; // Google social login requires longer timeout test.describe.configure({ timeout: socialTotalTimeout }); @@ -42,14 +47,18 @@ test.describe('social logins', () => { makeSocialProvider(SocialProviderType.Google), ], ); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterEach(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('socials should be rendered on UI component if they are activated', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); // by default the signup screen is loaded await model.expectScreen(ScreenNames.InitSignup); @@ -70,7 +79,7 @@ test.describe('social logins', () => { // Reason for skip: https://www.notion.so/Issues-related-to-email-and-social-login-in-tests-javascript-complete-1bceeb954aa280148c34d10aac8c2117 test.skip('signup with socials should be possible (account does not exist)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; @@ -81,7 +90,7 @@ test.describe('social logins', () => { }); test.skip('signup with social should be possible (account exists, social has been linked)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; @@ -93,7 +102,7 @@ test.describe('social logins', () => { await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); await model.signupInit.resubmitSocialGoogle(); // TODO: should successfully log in, but gets redirected to login-init instead. @@ -104,7 +113,7 @@ test.describe('social logins', () => { test.skip('signup with social should not be possible (account exists, social has not been linked)', async ({ model, }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; @@ -116,7 +125,7 @@ test.describe('social logins', () => { await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); await model.signupInit.submitSocialGoogle(email, password, secret); await model.expectScreen(ScreenNames.InitLogin); @@ -124,7 +133,7 @@ test.describe('social logins', () => { test.skip('login with social should be possible (account does not exist)', async ({ model }) => { // redirects to passkey append screen - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; @@ -135,7 +144,7 @@ test.describe('social logins', () => { }); test.skip('login with social should be possible (account exists, social has been linked)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; @@ -147,7 +156,7 @@ test.describe('social logins', () => { await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.signupInit.resubmitSocialGoogle(); await model.expectScreen(ScreenNames.End); @@ -157,7 +166,7 @@ test.describe('social logins', () => { test.skip('login with social should not be possible (account exists, social has not been linked)', async ({ model, }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; @@ -169,7 +178,7 @@ test.describe('social logins', () => { await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.signupInit.submitSocialGoogle(email, password, secret); // TODO: should redirect to login-init screen, but gets successfully logged in insteaad. diff --git a/packages/tests-e2e/src/complete/scenarios/passkey-list-general/general.spec.ts b/packages/tests-e2e/src/complete/scenarios/passkey-list-general/general.spec.ts index e1ebff189..55f67ec69 100644 --- a/packages/tests-e2e/src/complete/scenarios/passkey-list-general/general.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/passkey-list-general/general.spec.ts @@ -1,9 +1,14 @@ +import type { ChildProcess } from 'node:child_process'; + +import { passkeyListTest } from '../../fixtures/PasskeyList'; import { IdentifierEnforceVerification, IdentifierType, IdentifierVerification } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; -import { passkeyListTest } from '../../fixtures/PasskeyList'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; passkeyListTest.describe('passkey list - general', () => { let projectId: string; + let server: ChildProcess; + let port: number; passkeyListTest.beforeAll(async () => { projectId = await createProjectNew(); @@ -13,14 +18,18 @@ passkeyListTest.describe('passkey list - general', () => { IdentifierVerification.EmailOtp, ]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); passkeyListTest.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); passkeyListTest('passkey list allows adding and deleting passkeys (passkeys are supported)', async ({ model }) => { - await model.load(projectId, true); + await model.load(projectId, port, true); await model.expectPasskeys(0); await model.appendNewPasskey(true); @@ -31,7 +40,7 @@ passkeyListTest.describe('passkey list - general', () => { // currently it seems impossible to test for duplicate passkeys with virtual authenticator passkeyListTest('passkey list error handling (cancel, duplicate passkeys)', async ({ model }) => { - await model.load(projectId, true); + await model.load(projectId, port, true); await model.expectPasskeys(0); await model.appendNewPasskey(false); diff --git a/packages/tests-e2e/src/complete/utils/playground.ts b/packages/tests-e2e/src/complete/utils/playground.ts new file mode 100644 index 000000000..835c54d90 --- /dev/null +++ b/packages/tests-e2e/src/complete/utils/playground.ts @@ -0,0 +1,67 @@ +import { ChildProcess, spawn } from 'node:child_process'; + +import getPort from 'get-port'; +import path from 'path'; +import waitPort from 'wait-port'; + +type PlaygroundType = 'react' | 'web-js' | 'web-js-script'; +const PLAYGROUND_TYPE: PlaygroundType = (process.env.PLAYGROUND_TYPE as PlaygroundType) || 'react'; + +function getPlaygroundDir(): string { + switch (PLAYGROUND_TYPE) { + case 'react': + return path.resolve(__dirname, '../../../../../playground/react'); + case 'web-js': + return path.resolve(__dirname, '../../playground/web-js'); + case 'web-js-script': + return path.resolve(__dirname, '../../playground/web-js-script'); + default: + throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); + } +} + +function getPlaygroundArgs(port: number): string[] { + switch (PLAYGROUND_TYPE) { + case 'react': + return ['run', 'build-and-preview', '--', '--port', port.toString()]; + case 'web-js': + return ['run', 'build-and-preview']; + case 'web-js-script': + return ['run', 'build-and-preview']; + default: + throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); + } +} + +export async function spawnPlaygroundNew(projectId: string): Promise<{ + server: ChildProcess; + port: number; +}> { + const port = await getPort(); + + const playgroundDir = getPlaygroundDir(); + const server = spawn( + 'npm', + getPlaygroundArgs(port), + { + cwd: playgroundDir, + env: { + ...process.env, + VITE_CORBADO_PROJECT_ID_ManualTesting: projectId, + }, + stdio: 'inherit', + shell: true, + } + ); + const ok = await waitPort({ host: 'localhost', port, timeout: 15_000, output: 'silent' }); + if (!ok) { + server.kill(); + throw new Error(`Server never came up on port ${port}`); + } + + return { server, port }; +} + +export function killPlaygroundNew(server: ChildProcess) { + server.kill(); +} diff --git a/playground/react/package.json b/playground/react/package.json index 4c887dd85..f6c95968f 100644 --- a/playground/react/package.json +++ b/playground/react/package.json @@ -8,6 +8,7 @@ "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", + "build-and-preview": "tsc && vite build && vite preview", "test": "vitest", "test:coverage": "vitest run --coverage" }, From d0ba13cb676ab7406c3247146df8b7aeccd13c98 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 10 Jun 2025 14:29:01 +0200 Subject: [PATCH 12/60] run web-js and web-js-script playgrounds in playwright --- package-lock.json | 667 +++++++++++++++++- .../src/complete/utils/playground.ts | 11 +- playground/web-js-script/package.json | 16 + playground/web-js/package.json | 6 +- 4 files changed, 692 insertions(+), 8 deletions(-) create mode 100644 playground/web-js-script/package.json diff --git a/package-lock.json b/package-lock.json index 2bf13c43f..3c29bf140 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8271,6 +8271,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@zeit/schemas": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", + "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "dev": true, + "license": "MIT" + }, "node_modules/@zkochan/js-yaml": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", @@ -8301,6 +8308,20 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -8452,6 +8473,16 @@ "dev": true, "license": "MIT" }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -8533,6 +8564,27 @@ "dev": true, "license": "ISC" }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/arg": { "version": "4.1.3", "dev": true, @@ -8985,6 +9037,153 @@ "version": "2.11.0", "license": "MIT" }, + "node_modules/boxen": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/boxen/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/boxen/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "dev": true, @@ -9110,6 +9309,16 @@ "node": ">=12.17" } }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -9412,6 +9621,22 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -9524,6 +9749,19 @@ "node": ">=6" } }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -9566,6 +9804,48 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clipboardy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, "node_modules/cliui": { "version": "8.0.1", "dev": true, @@ -9751,6 +10031,62 @@ "dot-prop": "^5.1.0" } }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "dev": true, @@ -9790,6 +10126,16 @@ "dev": true, "license": "ISC" }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/conventional-changelog-angular": { "version": "7.0.0", "dev": true, @@ -10360,6 +10706,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "dev": true, @@ -13280,6 +13636,19 @@ "node": ">=0.10.0" } }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "dev": true, @@ -16003,6 +16372,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "dev": true, @@ -16408,6 +16787,13 @@ "node": ">=0.10.0" } }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, "node_modules/path-key": { "version": "3.1.1", "dev": true, @@ -16441,6 +16827,13 @@ "dev": true, "license": "ISC" }, + "node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-type": { "version": "4.0.0", "dev": true, @@ -17111,6 +17504,42 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -17636,6 +18065,30 @@ "node": ">=4" } }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", @@ -18110,6 +18563,115 @@ "randombytes": "^2.1.0" } }, + "node_modules/serve": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.4.tgz", + "integrity": "sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zeit/schemas": "2.36.0", + "ajv": "8.12.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.7.4", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.6", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/serve-handler": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/serve/node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve/node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/serve/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -19793,6 +20355,17 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/update-check": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "dev": true, @@ -19885,6 +20458,16 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", @@ -21809,6 +22392,10 @@ "resolved": "playground/web-js", "link": true }, + "node_modules/web-js-script": { + "resolved": "playground/web-js-script", + "link": true + }, "node_modules/web-streams-polyfill": { "version": "3.3.2", "dev": true, @@ -22061,6 +22648,76 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wildcard": { "version": "2.0.1", "dev": true, @@ -24088,7 +24745,15 @@ }, "devDependencies": { "dotenv-webpack": "^8.0.1", - "html-webpack-plugin": "^5.6.3" + "html-webpack-plugin": "^5.6.3", + "serve": "^14.2.4" + } + }, + "playground/web-js-script": { + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "serve": "^14.2.4" } } } diff --git a/packages/tests-e2e/src/complete/utils/playground.ts b/packages/tests-e2e/src/complete/utils/playground.ts index 835c54d90..0e979f501 100644 --- a/packages/tests-e2e/src/complete/utils/playground.ts +++ b/packages/tests-e2e/src/complete/utils/playground.ts @@ -1,4 +1,5 @@ -import { ChildProcess, spawn } from 'node:child_process'; +import type { ChildProcess } from 'node:child_process'; +import { spawn } from 'node:child_process'; import getPort from 'get-port'; import path from 'path'; @@ -12,9 +13,9 @@ function getPlaygroundDir(): string { case 'react': return path.resolve(__dirname, '../../../../../playground/react'); case 'web-js': - return path.resolve(__dirname, '../../playground/web-js'); + return path.resolve(__dirname, '../../../../../playground/web-js'); case 'web-js-script': - return path.resolve(__dirname, '../../playground/web-js-script'); + return path.resolve(__dirname, '../../../../../playground/web-js-script'); default: throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); } @@ -25,9 +26,9 @@ function getPlaygroundArgs(port: number): string[] { case 'react': return ['run', 'build-and-preview', '--', '--port', port.toString()]; case 'web-js': - return ['run', 'build-and-preview']; + return ['run', 'build-and-preview', '--', '-l', port.toString()]; case 'web-js-script': - return ['run', 'build-and-preview']; + return ['run', 'build-and-preview', '--', '-l', port.toString()]; default: throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); } diff --git a/playground/web-js-script/package.json b/playground/web-js-script/package.json new file mode 100644 index 000000000..6017e1272 --- /dev/null +++ b/playground/web-js-script/package.json @@ -0,0 +1,16 @@ +{ + "name": "web-js-script", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build-and-preview": "cd ../.. && npm run build:bundler:local && cd playground/web-js-script && npx serve --single" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "devDependencies": { + "serve": "^14.2.4" + } +} diff --git a/playground/web-js/package.json b/playground/web-js/package.json index 2984265d2..a900197a0 100644 --- a/playground/web-js/package.json +++ b/playground/web-js/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "start": "cross-env NODE_ENV=local webpack-dev-server --open --mode development", - "build": "cross-env NODE_ENV=vercel webpack --mode production" + "build": "cross-env NODE_ENV=vercel webpack --mode production", + "build-and-preview": "cross-env NODE_ENV=vercel webpack --mode production && npx serve dist --single" }, "keywords": [], "author": "", @@ -15,6 +16,7 @@ }, "devDependencies": { "dotenv-webpack": "^8.0.1", - "html-webpack-plugin": "^5.6.3" + "html-webpack-plugin": "^5.6.3", + "serve": "^14.2.4" } } From 57925a506d4a5d1576a82666b1b8eb3f2fc0f87a Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 10 Jun 2025 14:37:50 +0200 Subject: [PATCH 13/60] update workflows --- .github/workflows/deploy-playground-and-test.yml | 2 -- .github/workflows/test-all.yml | 6 ------ 2 files changed, 8 deletions(-) diff --git a/.github/workflows/deploy-playground-and-test.yml b/.github/workflows/deploy-playground-and-test.yml index f93b75da1..d48187cb2 100644 --- a/.github/workflows/deploy-playground-and-test.yml +++ b/.github/workflows/deploy-playground-and-test.yml @@ -171,8 +171,6 @@ jobs: - name: Run Complete tests for react run: | cd packages/tests-e2e - BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') - export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.react.playground.corbado.io" set +e npx playwright test --config=playwright.config.complete.ts EXIT_CODE=$? diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index eb4ea9ea0..82b4869ed 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -79,8 +79,6 @@ jobs: - name: Run Complete tests for react run: | cd packages/tests-e2e - BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') - export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.react.playground.corbado.io" set +e npx playwright test --config=playwright.config.complete.ts EXIT_CODE=$? @@ -125,8 +123,6 @@ jobs: - name: Run Complete tests for web-js run: | cd packages/tests-e2e - BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') - export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.web-js.playground.corbado.io" set +e npx playwright test --config=playwright.config.complete.ts EXIT_CODE=$? @@ -171,8 +167,6 @@ jobs: - name: Run Complete tests for web-js-script run: | cd packages/tests-e2e - BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') - export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.web-js-script.playground.corbado.io" set +e npx playwright test --config=playwright.config.complete.ts EXIT_CODE=$? From fb9eaeb51c64a5d9cacbcfb69d18a52567a8156d Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 10 Jun 2025 14:40:25 +0200 Subject: [PATCH 14/60] prettier --- .../src/complete/utils/playground.ts | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/tests-e2e/src/complete/utils/playground.ts b/packages/tests-e2e/src/complete/utils/playground.ts index 0e979f501..9a41dd2bc 100644 --- a/packages/tests-e2e/src/complete/utils/playground.ts +++ b/packages/tests-e2e/src/complete/utils/playground.ts @@ -41,19 +41,15 @@ export async function spawnPlaygroundNew(projectId: string): Promise<{ const port = await getPort(); const playgroundDir = getPlaygroundDir(); - const server = spawn( - 'npm', - getPlaygroundArgs(port), - { - cwd: playgroundDir, - env: { - ...process.env, - VITE_CORBADO_PROJECT_ID_ManualTesting: projectId, - }, - stdio: 'inherit', - shell: true, - } - ); + const server = spawn('npm', getPlaygroundArgs(port), { + cwd: playgroundDir, + env: { + ...process.env, + VITE_CORBADO_PROJECT_ID_ManualTesting: projectId, + }, + stdio: 'inherit', + shell: true, + }); const ok = await waitPort({ host: 'localhost', port, timeout: 15_000, output: 'silent' }); if (!ok) { server.kill(); From 4cff36fc2f62fc3a96a8726f06758cb5e107ef20 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 10 Jun 2025 15:09:54 +0200 Subject: [PATCH 15/60] install playground deps in the beginning of playwright test --- .../tests-e2e/playwright.config.complete.ts | 1 + .../src/complete/utils/playground.ts | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/packages/tests-e2e/playwright.config.complete.ts b/packages/tests-e2e/playwright.config.complete.ts index dc5a2269e..e6803c41e 100644 --- a/packages/tests-e2e/playwright.config.complete.ts +++ b/packages/tests-e2e/playwright.config.complete.ts @@ -99,4 +99,5 @@ export default defineConfig({ // reuseExistingServer: !process.env.CI, // timeout: 15 * 1000, // }, + globalSetup: 'src/complete/utils/playground.ts', }); diff --git a/packages/tests-e2e/src/complete/utils/playground.ts b/packages/tests-e2e/src/complete/utils/playground.ts index 9a41dd2bc..54ac80541 100644 --- a/packages/tests-e2e/src/complete/utils/playground.ts +++ b/packages/tests-e2e/src/complete/utils/playground.ts @@ -62,3 +62,27 @@ export async function spawnPlaygroundNew(projectId: string): Promise<{ export function killPlaygroundNew(server: ChildProcess) { server.kill(); } + +export default async function installPlaygroundDeps() { + const playgroundDir = getPlaygroundDir(); + + const installProcess = spawn('npm', ['install'], { + cwd: playgroundDir, + stdio: 'inherit', + shell: true, + }); + + await new Promise((resolve, reject) => { + installProcess.on('close', (code: number) => { + if (code === 0) { + console.log(`[Global Setup] Dependencies installed successfully in ${playgroundDir}.`); + resolve(); + } else { + reject(new Error(`[Global Setup] npm install failed in ${playgroundDir} with code ${code}`)); + } + }); + installProcess.on('error', (err: Error) => { + reject(new Error(`[Global Setup] Failed to start npm install process: ${err.message}`)); + }); + }); +} From 7b8c53caf72ff3492c33eea5bb7dd8d432cd7340 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 10 Jun 2025 15:23:41 +0200 Subject: [PATCH 16/60] install root dependencies for test stage --- .../workflows/deploy-playground-and-test.yml | 28 +++++++++++++++++++ .../src/complete/utils/playground.ts | 26 +++++++++++++++++ playground/web-js-script/package.json | 2 +- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-playground-and-test.yml b/.github/workflows/deploy-playground-and-test.yml index d48187cb2..d27da8ae0 100644 --- a/.github/workflows/deploy-playground-and-test.yml +++ b/.github/workflows/deploy-playground-and-test.yml @@ -112,6 +112,34 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 + - name: Cache node modules + id: cache-npm + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + path: | + ~/.npm + ./node_modules + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-force-1 + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + name: Install Dependencies + run: | + npm i + npm install lerna + npm install vercel@33.2.0 + npm list + + - name: Build SDKs + run: | + npm run build + npm run build:bundler:local + - name: Get installed Playwright version id: playwright-version run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')" diff --git a/packages/tests-e2e/src/complete/utils/playground.ts b/packages/tests-e2e/src/complete/utils/playground.ts index 54ac80541..0c35596b1 100644 --- a/packages/tests-e2e/src/complete/utils/playground.ts +++ b/packages/tests-e2e/src/complete/utils/playground.ts @@ -8,6 +8,10 @@ import waitPort from 'wait-port'; type PlaygroundType = 'react' | 'web-js' | 'web-js-script'; const PLAYGROUND_TYPE: PlaygroundType = (process.env.PLAYGROUND_TYPE as PlaygroundType) || 'react'; +function getRootDir(): string { + return path.resolve(__dirname, '../../../../..'); +} + function getPlaygroundDir(): string { switch (PLAYGROUND_TYPE) { case 'react': @@ -64,6 +68,28 @@ export function killPlaygroundNew(server: ChildProcess) { } export default async function installPlaygroundDeps() { + const rootDir = getRootDir(); + + const lernaBuildProcess = spawn('npm', ['run', 'build'], { + cwd: rootDir, + stdio: 'inherit', + shell: true, + }); + + await new Promise((resolve, reject) => { + lernaBuildProcess.on('close', (code: number) => { + if (code === 0) { + console.log(`[Global Setup] 'lerna run build' completed successfully.`); + resolve(); + } else { + reject(new Error(`[Global Setup] 'lerna run build' failed with code ${code}`)); + } + }); + lernaBuildProcess.on('error', (err: Error) => { + reject(new Error(`[Global Setup] Failed to start 'lerna run build' process: ${err.message}`)); + }); + }); + const playgroundDir = getPlaygroundDir(); const installProcess = spawn('npm', ['install'], { diff --git a/playground/web-js-script/package.json b/playground/web-js-script/package.json index 6017e1272..ece48ddfa 100644 --- a/playground/web-js-script/package.json +++ b/playground/web-js-script/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "build-and-preview": "cd ../.. && npm run build:bundler:local && cd playground/web-js-script && npx serve --single" + "build-and-preview": "npx serve --single" }, "keywords": [], "author": "", From 0484cb241e55eb3fe0cd2f67be675273e2023f35 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 10 Jun 2025 15:36:50 +0200 Subject: [PATCH 17/60] unique cache step id --- .github/workflows/deploy-playground-and-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-playground-and-test.yml b/.github/workflows/deploy-playground-and-test.yml index d27da8ae0..97eceabc6 100644 --- a/.github/workflows/deploy-playground-and-test.yml +++ b/.github/workflows/deploy-playground-and-test.yml @@ -22,7 +22,7 @@ jobs: uses: actions/setup-node@v4 - name: Cache node modules - id: cache-npm + id: cache-npm-deploy uses: actions/cache@v3 env: cache-name: cache-node-modules @@ -36,7 +36,7 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + - if: ${{ steps.cache-npm-deploy.outputs.cache-hit != 'true' }} name: Install Dependencies run: | npm i @@ -113,7 +113,7 @@ jobs: uses: actions/setup-node@v4 - name: Cache node modules - id: cache-npm + id: cache-npm-test uses: actions/cache@v3 env: cache-name: cache-node-modules @@ -127,7 +127,7 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + - if: ${{ steps.cache-npm-test.outputs.cache-hit != 'true' }} name: Install Dependencies run: | npm i From c96d854901ff9e175e0f703a4acf2c2cd990aa3d Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 10 Jun 2025 15:51:38 +0200 Subject: [PATCH 18/60] remove root build from playwright (assume it's already done in local or CI --- .../workflows/deploy-playground-and-test.yml | 12 +++--- .../src/complete/utils/playground.ts | 42 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/deploy-playground-and-test.yml b/.github/workflows/deploy-playground-and-test.yml index 97eceabc6..21b886a1d 100644 --- a/.github/workflows/deploy-playground-and-test.yml +++ b/.github/workflows/deploy-playground-and-test.yml @@ -22,7 +22,7 @@ jobs: uses: actions/setup-node@v4 - name: Cache node modules - id: cache-npm-deploy + id: cache-npm uses: actions/cache@v3 env: cache-name: cache-node-modules @@ -36,7 +36,7 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - - if: ${{ steps.cache-npm-deploy.outputs.cache-hit != 'true' }} + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} name: Install Dependencies run: | npm i @@ -113,7 +113,7 @@ jobs: uses: actions/setup-node@v4 - name: Cache node modules - id: cache-npm-test + id: cache-npm uses: actions/cache@v3 env: cache-name: cache-node-modules @@ -127,7 +127,7 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - - if: ${{ steps.cache-npm-test.outputs.cache-hit != 'true' }} + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} name: Install Dependencies run: | npm i @@ -145,7 +145,7 @@ jobs: run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')" - name: Cache node modules - id: cache-npm + id: cache-npm-test uses: actions/cache@v3 env: cache-name: cache-node-modules @@ -158,7 +158,7 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + - if: ${{ steps.cache-npm-test.outputs.cache-hit != 'true' }} name: List the state of node modules continue-on-error: true run: | diff --git a/packages/tests-e2e/src/complete/utils/playground.ts b/packages/tests-e2e/src/complete/utils/playground.ts index 0c35596b1..b2692737b 100644 --- a/packages/tests-e2e/src/complete/utils/playground.ts +++ b/packages/tests-e2e/src/complete/utils/playground.ts @@ -68,27 +68,27 @@ export function killPlaygroundNew(server: ChildProcess) { } export default async function installPlaygroundDeps() { - const rootDir = getRootDir(); - - const lernaBuildProcess = spawn('npm', ['run', 'build'], { - cwd: rootDir, - stdio: 'inherit', - shell: true, - }); - - await new Promise((resolve, reject) => { - lernaBuildProcess.on('close', (code: number) => { - if (code === 0) { - console.log(`[Global Setup] 'lerna run build' completed successfully.`); - resolve(); - } else { - reject(new Error(`[Global Setup] 'lerna run build' failed with code ${code}`)); - } - }); - lernaBuildProcess.on('error', (err: Error) => { - reject(new Error(`[Global Setup] Failed to start 'lerna run build' process: ${err.message}`)); - }); - }); + // const rootDir = getRootDir(); + // + // const lernaBuildProcess = spawn('npm', ['run', 'build'], { + // cwd: rootDir, + // stdio: 'inherit', + // shell: true, + // }); + // + // await new Promise((resolve, reject) => { + // lernaBuildProcess.on('close', (code: number) => { + // if (code === 0) { + // console.log(`[Global Setup] 'lerna run build' completed successfully.`); + // resolve(); + // } else { + // reject(new Error(`[Global Setup] 'lerna run build' failed with code ${code}`)); + // } + // }); + // lernaBuildProcess.on('error', (err: Error) => { + // reject(new Error(`[Global Setup] Failed to start 'lerna run build' process: ${err.message}`)); + // }); + // }); const playgroundDir = getPlaygroundDir(); From 32a51847d03f5eebe78e2d99ec5ec740f44c63e0 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 10 Jun 2025 15:52:52 +0200 Subject: [PATCH 19/60] remove unused function --- packages/tests-e2e/src/complete/utils/playground.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/tests-e2e/src/complete/utils/playground.ts b/packages/tests-e2e/src/complete/utils/playground.ts index b2692737b..78e473d36 100644 --- a/packages/tests-e2e/src/complete/utils/playground.ts +++ b/packages/tests-e2e/src/complete/utils/playground.ts @@ -8,9 +8,9 @@ import waitPort from 'wait-port'; type PlaygroundType = 'react' | 'web-js' | 'web-js-script'; const PLAYGROUND_TYPE: PlaygroundType = (process.env.PLAYGROUND_TYPE as PlaygroundType) || 'react'; -function getRootDir(): string { - return path.resolve(__dirname, '../../../../..'); -} +// function getRootDir(): string { +// return path.resolve(__dirname, '../../../../..'); +// } function getPlaygroundDir(): string { switch (PLAYGROUND_TYPE) { From ce3b68936c89bcb33e0dcc4a20b97a1718be1181 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 10 Jun 2025 16:29:50 +0200 Subject: [PATCH 20/60] remove unnecessary cache step --- .github/workflows/deploy-playground-and-test.yml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/.github/workflows/deploy-playground-and-test.yml b/.github/workflows/deploy-playground-and-test.yml index 21b886a1d..293c5c65e 100644 --- a/.github/workflows/deploy-playground-and-test.yml +++ b/.github/workflows/deploy-playground-and-test.yml @@ -144,21 +144,7 @@ jobs: id: playwright-version run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')" - - name: Cache node modules - id: cache-npm-test - uses: actions/cache@v3 - env: - cache-name: cache-node-modules - with: - path: | - ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - - if: ${{ steps.cache-npm-test.outputs.cache-hit != 'true' }} + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} name: List the state of node modules continue-on-error: true run: | From 89f8a1376b14d0913b246357913d87eb14cfc8c3 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 10 Jun 2025 16:45:21 +0200 Subject: [PATCH 21/60] clean up stdout --- .../workflows/deploy-playground-and-test.yml | 4 +-- .github/workflows/test-all.yml | 26 ++++++++++++++----- .../src/complete/utils/playground.ts | 2 +- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy-playground-and-test.yml b/.github/workflows/deploy-playground-and-test.yml index 293c5c65e..6f57df33f 100644 --- a/.github/workflows/deploy-playground-and-test.yml +++ b/.github/workflows/deploy-playground-and-test.yml @@ -128,7 +128,7 @@ jobs: ${{ runner.os }}- - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} - name: Install Dependencies + name: Install root dependencies run: | npm i npm install lerna @@ -151,7 +151,7 @@ jobs: cd packages/tests-e2e npm list - - name: Install Dependencies + - name: Install dependencies run: | cd packages/tests-e2e npm install diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 82b4869ed..74c418f23 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -20,10 +20,6 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 - - name: Get installed Playwright version - id: playwright-version - run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')" - - name: Cache node modules id: cache-npm uses: actions/cache@v3 @@ -32,12 +28,30 @@ jobs: with: path: | ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + ./node_modules + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-force-1 restore-keys: | ${{ runner.os }}-build-${{ env.cache-name }}- ${{ runner.os }}-build- ${{ runner.os }}- + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + name: Install root dependencies + run: | + npm i + npm install lerna + npm install vercel@33.2.0 + npm list + + - name: Build SDKs + run: | + npm run build + npm run build:bundler:local + + - name: Get installed Playwright version + id: playwright-version + run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')" + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} name: List the state of node modules continue-on-error: true @@ -45,7 +59,7 @@ jobs: cd packages/tests-e2e npm list - - name: Install Dependencies + - name: Install dependencies run: | cd packages/tests-e2e npm install diff --git a/packages/tests-e2e/src/complete/utils/playground.ts b/packages/tests-e2e/src/complete/utils/playground.ts index 78e473d36..04ae084c6 100644 --- a/packages/tests-e2e/src/complete/utils/playground.ts +++ b/packages/tests-e2e/src/complete/utils/playground.ts @@ -51,7 +51,7 @@ export async function spawnPlaygroundNew(projectId: string): Promise<{ ...process.env, VITE_CORBADO_PROJECT_ID_ManualTesting: projectId, }, - stdio: 'inherit', + stdio: 'ignore', shell: true, }); const ok = await waitPort({ host: 'localhost', port, timeout: 15_000, output: 'silent' }); From 10ce3864bdd88cc6d337c239771f2511a978b216 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 10 Jun 2025 16:51:32 +0200 Subject: [PATCH 22/60] rpid is localhost --- packages/tests-e2e/src/complete/utils/developerpanel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tests-e2e/src/complete/utils/developerpanel.ts b/packages/tests-e2e/src/complete/utils/developerpanel.ts index 36713e016..32673dce9 100644 --- a/packages/tests-e2e/src/complete/utils/developerpanel.ts +++ b/packages/tests-e2e/src/complete/utils/developerpanel.ts @@ -34,7 +34,7 @@ export const createProjectNew = async () => { body: JSON.stringify({ name, allowStaticChallenges: true, - webauthnRPID: process.env.CI ? 'playground.corbado.io' : 'localhost', + webauthnRPID: process.env.CI ? 'localhost' : 'localhost', }), }); expect(createRes.ok).toBeTruthy(); From f0df7ccb87b8f51bba660ad21f96f24812612ef5 Mon Sep 17 00:00:00 2001 From: Incorbador Date: Wed, 11 Jun 2025 12:01:17 +0200 Subject: [PATCH 23/60] Extend wv-connect-token endpoint with simulation capas --- package-lock.json | 7806 +++++++++++++++-- .../app/(api)/connectTokenExternal/route.ts | 23 + 2 files changed, 7063 insertions(+), 766 deletions(-) diff --git a/package-lock.json b/package-lock.json index 12a7c9a55..1dc3de407 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,6 +63,382 @@ "node": ">=6.0.0" } }, + "node_modules/@aws-amplify/analytics": { + "version": "7.0.81", + "resolved": "https://registry.npmjs.org/@aws-amplify/analytics/-/analytics-7.0.81.tgz", + "integrity": "sha512-X2+gI8ek0wyK+c9Z+6AYSb3SNlXyS7CDPdpLEAEb6w4zVKAUX8CykBxIXZGagMX7rFO6oWOB7Zz7vAfAq9yFlg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-firehose": "3.621.0", + "@aws-sdk/client-kinesis": "3.621.0", + "@aws-sdk/client-personalize-events": "3.621.0", + "@smithy/util-utf8": "2.0.0", + "tslib": "^2.5.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0" + } + }, + "node_modules/@aws-amplify/analytics/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-amplify/analytics/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-amplify/analytics/node_modules/@smithy/util-utf8": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.0.tgz", + "integrity": "sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-amplify/api": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@aws-amplify/api/-/api-6.3.12.tgz", + "integrity": "sha512-MBvSn9gtM1w1I8r06Q3ZfXo643mAjgVxxDiQ1IBjotJLOIKv9jckjJqCDO+3Mhpd4PaOQfpVsDB7E/a0FLNDYw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/api-graphql": "4.7.16", + "@aws-amplify/api-rest": "4.1.5", + "@aws-amplify/data-schema": "^1.7.0", + "rxjs": "^7.8.1", + "tslib": "^2.5.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0" + } + }, + "node_modules/@aws-amplify/api-graphql": { + "version": "4.7.16", + "resolved": "https://registry.npmjs.org/@aws-amplify/api-graphql/-/api-graphql-4.7.16.tgz", + "integrity": "sha512-o3nddNJMuTNTRnG58Mt+tlo2nYq7YMeheqnPPPBltZ0kYhMz5hnLKlOkZznf5n6brBtE4hPnBtxtlCKFDFIsRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/api-rest": "4.1.5", + "@aws-amplify/core": "6.12.0", + "@aws-amplify/data-schema": "^1.7.0", + "@aws-sdk/types": "3.387.0", + "graphql": "15.8.0", + "rxjs": "^7.8.1", + "tslib": "^2.5.0", + "uuid": "^9.0.0" + } + }, + "node_modules/@aws-amplify/api-graphql/node_modules/@aws-sdk/types": { + "version": "3.387.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.387.0.tgz", + "integrity": "sha512-YTjFabNwjTF+6yl88f0/tWff018qmmgMmjlw45s6sdVKueWxdxV68U7gepNLF2nhaQPZa6FDOBoA51NaviVs0Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-amplify/api-graphql/node_modules/@smithy/types": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", + "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-amplify/api-rest": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@aws-amplify/api-rest/-/api-rest-4.1.5.tgz", + "integrity": "sha512-rrFCVqD2Zip/4HpByyzShLEPIqJ6I1eUPDYf/QTl+/pqP3x8JBHlkNDVE/UmjjyPLEuDRQkRO/6APXfeMTpPAA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0" + } + }, + "node_modules/@aws-amplify/auth": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@aws-amplify/auth/-/auth-6.13.0.tgz", + "integrity": "sha512-PTzCzvSgjjnbkas1C0DLeBvJl+7FAJ9mLoU25AwghyUi5lODvfqcGN7t5T8eYVpd92ICiolasXxA4wJmyOmO+A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "5.2.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.5.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0" + } + }, + "node_modules/@aws-amplify/core": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@aws-amplify/core/-/core-6.12.0.tgz", + "integrity": "sha512-/QyfEmHdYtGI6AETvuYyFZ9ZCFVBin2X1Jbim6BaVd9S2HH5U9AqUrKMSJbSPoswmYfJTWxPFeW7KrBeuUUz7Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/types": "3.398.0", + "@smithy/util-hex-encoding": "2.0.0", + "@types/uuid": "^9.0.0", + "js-cookie": "^3.0.5", + "rxjs": "^7.8.1", + "tslib": "^2.5.0", + "uuid": "^9.0.0" + } + }, + "node_modules/@aws-amplify/core/node_modules/@aws-sdk/types": { + "version": "3.398.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.398.0.tgz", + "integrity": "sha512-r44fkS+vsEgKCuEuTV+TIk0t0m5ZlXHNjSDYEUvzLStbbfUFiNus/YG4UCa0wOk9R7VuQI67badsvvPeVPCGDQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-amplify/core/node_modules/@smithy/types": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", + "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-amplify/core/node_modules/@smithy/util-hex-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz", + "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-amplify/data-schema": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@aws-amplify/data-schema/-/data-schema-1.21.0.tgz", + "integrity": "sha512-3SU6zHrvMVOLUiD574aWcVYPVdZ3sPVwQNTPBg5B3DEOkkZ4bGyuiCCKUQiV7AbdSv/1OcZVnPuRFdv+8CW34A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/data-schema-types": "*", + "@smithy/util-base64": "^3.0.0", + "@types/aws-lambda": "^8.10.134", + "@types/json-schema": "^7.0.15", + "rxjs": "^7.8.1" + } + }, + "node_modules/@aws-amplify/data-schema-types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@aws-amplify/data-schema-types/-/data-schema-types-1.2.0.tgz", + "integrity": "sha512-1hy2r7jl3hQ5J/CGjhmPhFPcdGSakfme1ZLjlTMJZILfYifZLSlGRKNCelMb3J5N9203hyeT5XDi5yR47JL1TQ==", + "license": "Apache-2.0", + "dependencies": { + "graphql": "15.8.0", + "rxjs": "^7.8.1" + } + }, + "node_modules/@aws-amplify/datastore": { + "version": "5.0.83", + "resolved": "https://registry.npmjs.org/@aws-amplify/datastore/-/datastore-5.0.83.tgz", + "integrity": "sha512-4W8rJO7QnWV9Kw2OrhXQ1dRQkbbxYX7zDLYyCOC9GncqfK08l7Dh9/C31GllybPwDKqHNy7mc6CkqlC6AFuujg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/api": "6.3.12", + "@aws-amplify/api-graphql": "4.7.16", + "buffer": "4.9.2", + "idb": "5.0.6", + "immer": "9.0.6", + "rxjs": "^7.8.1", + "ulid": "^2.3.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0" + } + }, + "node_modules/@aws-amplify/datastore/node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/@aws-amplify/datastore/node_modules/idb": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/idb/-/idb-5.0.6.tgz", + "integrity": "sha512-/PFvOWPzRcEPmlDt5jEvzVZVs0wyd/EvGvkDIcbBpGuMMLQKrTPG0TxvE2UJtgZtCQCmOtM2QD7yQJBVEjKGOw==", + "license": "ISC" + }, + "node_modules/@aws-amplify/datastore/node_modules/immer": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.6.tgz", + "integrity": "sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/@aws-amplify/datastore/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/@aws-amplify/notifications": { + "version": "2.0.81", + "resolved": "https://registry.npmjs.org/@aws-amplify/notifications/-/notifications-2.0.81.tgz", + "integrity": "sha512-YVScErhCs3AIOFn3431uB0VhjiwoKmmg6DitAkwVcDRKM0mqOtZHamIfuB/fJUwMrrigkAjlNhJoOlrB8hQo5g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.398.0", + "lodash": "^4.17.21", + "tslib": "^2.5.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0" + } + }, + "node_modules/@aws-amplify/notifications/node_modules/@aws-sdk/types": { + "version": "3.398.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.398.0.tgz", + "integrity": "sha512-r44fkS+vsEgKCuEuTV+TIk0t0m5ZlXHNjSDYEUvzLStbbfUFiNus/YG4UCa0wOk9R7VuQI67badsvvPeVPCGDQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-amplify/notifications/node_modules/@smithy/types": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", + "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-amplify/storage": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@aws-amplify/storage/-/storage-6.9.0.tgz", + "integrity": "sha512-GgRosKF19CbIYJltwz30grB4Cp1qOnGzYOl7Cea6zZmEULzNBKDzbdGBjxOc2LBFrd9BP+Tz2bQ9Ov39DsNIeQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.398.0", + "@smithy/md5-js": "2.0.7", + "buffer": "4.9.2", + "crc-32": "1.2.2", + "fast-xml-parser": "^4.4.1", + "tslib": "^2.5.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0" + } + }, + "node_modules/@aws-amplify/storage/node_modules/@aws-sdk/types": { + "version": "3.398.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.398.0.tgz", + "integrity": "sha512-r44fkS+vsEgKCuEuTV+TIk0t0m5ZlXHNjSDYEUvzLStbbfUFiNus/YG4UCa0wOk9R7VuQI67badsvvPeVPCGDQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-amplify/storage/node_modules/@smithy/types": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", + "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-amplify/storage/node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/@aws-amplify/storage/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "license": "Apache-2.0", @@ -219,850 +595,604 @@ } }, "node_modules/@aws-sdk/client-cognito-identity-provider": { - "version": "3.600.0", + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity-provider/-/client-cognito-identity-provider-3.821.0.tgz", + "integrity": "sha512-xDcgAmT0DSRUYQodeYbDI7vnectrgUIy/0W7COSvpcrtwn8H/N9kGneiUkfR+5GBpQr5rpHaWWKUmjS9beniWA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.600.0", - "@aws-sdk/client-sts": "3.600.0", - "@aws-sdk/core": "3.598.0", - "@aws-sdk/credential-provider-node": "3.600.0", - "@aws-sdk/middleware-host-header": "3.598.0", - "@aws-sdk/middleware-logger": "3.598.0", - "@aws-sdk/middleware-recursion-detection": "3.598.0", - "@aws-sdk/middleware-user-agent": "3.598.0", - "@aws-sdk/region-config-resolver": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@aws-sdk/util-endpoints": "3.598.0", - "@aws-sdk/util-user-agent-browser": "3.598.0", - "@aws-sdk/util-user-agent-node": "3.598.0", - "@smithy/config-resolver": "^3.0.2", - "@smithy/core": "^2.2.1", - "@smithy/fetch-http-handler": "^3.0.2", - "@smithy/hash-node": "^3.0.1", - "@smithy/invalid-dependency": "^3.0.1", - "@smithy/middleware-content-length": "^3.0.1", - "@smithy/middleware-endpoint": "^3.0.2", - "@smithy/middleware-retry": "^3.0.4", - "@smithy/middleware-serde": "^3.0.1", - "@smithy/middleware-stack": "^3.0.1", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/node-http-handler": "^3.0.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/smithy-client": "^3.1.2", - "@smithy/types": "^3.1.0", - "@smithy/url-parser": "^3.0.1", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.4", - "@smithy/util-defaults-mode-node": "^3.0.4", - "@smithy/util-endpoints": "^2.0.2", - "@smithy/util-middleware": "^3.0.1", - "@smithy/util-retry": "^3.0.1", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/core": "3.821.0", + "@aws-sdk/credential-provider-node": "3.821.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.821.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.1", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.17", + "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/client-sso": { - "version": "3.624.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.624.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.620.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.614.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.3.2", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.14", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.1.12", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.14", - "@smithy/util-defaults-mode-node": "^3.0.14", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.624.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.624.0", - "@aws-sdk/credential-provider-node": "3.624.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.620.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.614.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.3.2", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.14", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.1.12", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.14", - "@smithy/util-defaults-mode-node": "^3.0.14", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.624.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/client-sts": { - "version": "3.624.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.1.tgz", + "integrity": "sha512-xSw7bZEFKwOKrm/iv8e2BLt2ur98YZdrRD6nII8ditQeUsY2Q1JmIQ0rpILOhaLKYxxG2ivnoOpokzr9qLyDWA==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.624.0", - "@aws-sdk/core": "3.624.0", - "@aws-sdk/credential-provider-node": "3.624.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.620.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.614.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.3.2", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.14", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.1.12", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.14", - "@smithy/util-defaults-mode-node": "^3.0.14", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/core": { - "version": "3.624.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^2.3.2", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/signature-v4": "^4.1.0", - "@smithy/smithy-client": "^3.1.12", - "@smithy/types": "^3.3.0", - "@smithy/util-middleware": "^3.0.3", - "fast-xml-parser": "4.4.1", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.620.1", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.622.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.1.12", - "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.624.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.620.1", - "@aws-sdk/credential-provider-http": "3.622.0", - "@aws-sdk/credential-provider-process": "3.620.1", - "@aws-sdk/credential-provider-sso": "3.624.0", - "@aws-sdk/credential-provider-web-identity": "3.621.0", - "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.624.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.624.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.620.1", - "@aws-sdk/credential-provider-http": "3.622.0", - "@aws-sdk/credential-provider-ini": "3.624.0", - "@aws-sdk/credential-provider-process": "3.620.1", - "@aws-sdk/credential-provider-sso": "3.624.0", - "@aws-sdk/credential-provider-web-identity": "3.621.0", - "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.620.1", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.624.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.9.tgz", + "integrity": "sha512-AjDgX4UjORLltD/LZCBQTwjQqEfyrx/GeDTHcYLzIgf87pIT70tMWnN87NQpJru1K4ITirY2htSOxNECZJCBOg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.624.0", - "@aws-sdk/token-providers": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@smithy/core": "^3.5.1", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.621.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/middleware-retry": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.10.tgz", + "integrity": "sha512-RyhcA3sZIIvAo6r48b2Nx2qfg0OnyohlaV0fw415xrQyx5HQ2bvHl9vs/WBiDXIP49mCfws5wX4308c9Pi/isw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.5", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.621.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.620.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-logger": { - "version": "3.609.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.620.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.620.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.614.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.614.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/token-providers": { - "version": "3.614.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.614.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-endpoints": { - "version": "3.614.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", - "@smithy/util-endpoints": "^2.0.5", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.609.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/service-error-classification": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.5.tgz", + "integrity": "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "@smithy/types": "^4.3.1" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.614.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/signature-v4": { - "version": "4.1.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/smithy-client": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.1.tgz", + "integrity": "sha512-XPbcHRfd0iwx8dY5XCBCGyI7uweMW0oezYezxXcG8ANgvZ5YPuC6Ylh+n0bTHpdU3SCMZOnhzgVklYz+p3fIhw==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-uri-escape": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/core": "^3.5.1", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.598.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.598.0", - "@aws-sdk/middleware-host-header": "3.598.0", - "@aws-sdk/middleware-logger": "3.598.0", - "@aws-sdk/middleware-recursion-detection": "3.598.0", - "@aws-sdk/middleware-user-agent": "3.598.0", - "@aws-sdk/region-config-resolver": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@aws-sdk/util-endpoints": "3.598.0", - "@aws-sdk/util-user-agent-browser": "3.598.0", - "@aws-sdk/util-user-agent-node": "3.598.0", - "@smithy/config-resolver": "^3.0.2", - "@smithy/core": "^2.2.1", - "@smithy/fetch-http-handler": "^3.0.2", - "@smithy/hash-node": "^3.0.1", - "@smithy/invalid-dependency": "^3.0.1", - "@smithy/middleware-content-length": "^3.0.1", - "@smithy/middleware-endpoint": "^3.0.2", - "@smithy/middleware-retry": "^3.0.4", - "@smithy/middleware-serde": "^3.0.1", - "@smithy/middleware-stack": "^3.0.1", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/node-http-handler": "^3.0.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/smithy-client": "^3.1.2", - "@smithy/types": "^3.1.0", - "@smithy/url-parser": "^3.0.1", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.4", - "@smithy/util-defaults-mode-node": "^3.0.4", - "@smithy/util-endpoints": "^2.0.2", - "@smithy/util-middleware": "^3.0.1", - "@smithy/util-retry": "^3.0.1", - "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.600.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sts": "3.600.0", - "@aws-sdk/core": "3.598.0", - "@aws-sdk/credential-provider-node": "3.600.0", - "@aws-sdk/middleware-host-header": "3.598.0", - "@aws-sdk/middleware-logger": "3.598.0", - "@aws-sdk/middleware-recursion-detection": "3.598.0", - "@aws-sdk/middleware-user-agent": "3.598.0", - "@aws-sdk/region-config-resolver": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@aws-sdk/util-endpoints": "3.598.0", - "@aws-sdk/util-user-agent-browser": "3.598.0", - "@aws-sdk/util-user-agent-node": "3.598.0", - "@smithy/config-resolver": "^3.0.2", - "@smithy/core": "^2.2.1", - "@smithy/fetch-http-handler": "^3.0.2", - "@smithy/hash-node": "^3.0.1", - "@smithy/invalid-dependency": "^3.0.1", - "@smithy/middleware-content-length": "^3.0.1", - "@smithy/middleware-endpoint": "^3.0.2", - "@smithy/middleware-retry": "^3.0.4", - "@smithy/middleware-serde": "^3.0.1", - "@smithy/middleware-stack": "^3.0.1", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/node-http-handler": "^3.0.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/smithy-client": "^3.1.2", - "@smithy/types": "^3.1.0", - "@smithy/url-parser": "^3.0.1", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.4", - "@smithy/util-defaults-mode-node": "^3.0.4", - "@smithy/util-endpoints": "^2.0.2", - "@smithy/util-middleware": "^3.0.1", - "@smithy/util-retry": "^3.0.1", - "@smithy/util-utf8": "^3.0.0", + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.600.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.600.0", - "@aws-sdk/core": "3.598.0", - "@aws-sdk/credential-provider-node": "3.600.0", - "@aws-sdk/middleware-host-header": "3.598.0", - "@aws-sdk/middleware-logger": "3.598.0", - "@aws-sdk/middleware-recursion-detection": "3.598.0", - "@aws-sdk/middleware-user-agent": "3.598.0", - "@aws-sdk/region-config-resolver": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@aws-sdk/util-endpoints": "3.598.0", - "@aws-sdk/util-user-agent-browser": "3.598.0", - "@aws-sdk/util-user-agent-node": "3.598.0", - "@smithy/config-resolver": "^3.0.2", - "@smithy/core": "^2.2.1", - "@smithy/fetch-http-handler": "^3.0.2", - "@smithy/hash-node": "^3.0.1", - "@smithy/invalid-dependency": "^3.0.1", - "@smithy/middleware-content-length": "^3.0.1", - "@smithy/middleware-endpoint": "^3.0.2", - "@smithy/middleware-retry": "^3.0.4", - "@smithy/middleware-serde": "^3.0.1", - "@smithy/middleware-stack": "^3.0.1", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/node-http-handler": "^3.0.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/smithy-client": "^3.1.2", - "@smithy/types": "^3.1.0", - "@smithy/url-parser": "^3.0.1", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.4", - "@smithy/util-defaults-mode-node": "^3.0.4", - "@smithy/util-endpoints": "^2.0.2", - "@smithy/util-middleware": "^3.0.1", - "@smithy/util-retry": "^3.0.1", - "@smithy/util-utf8": "^3.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/core": { - "version": "3.598.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^2.2.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/signature-v4": "^3.1.0", - "@smithy/smithy-client": "^3.1.2", - "@smithy/types": "^3.1.0", - "fast-xml-parser": "4.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/fast-xml-parser": { - "version": "4.2.5", - "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "license": "Apache-2.0", "dependencies": { - "strnum": "^1.0.5" + "tslib": "^2.6.2" }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.624.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-cognito-identity": "3.624.0", - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", + "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.598.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.17.tgz", + "integrity": "sha512-HXq5181qnXmIwB7VrwqwP8rsJybHMoYuJnNoXy4PROs2pfSI4sWDMASF2i+7Lo+u64Y6xowhegcdxczowgJtZg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/property-provider": "^3.1.1", - "@smithy/types": "^3.1.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.598.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.17.tgz", + "integrity": "sha512-RfU2A5LjFhEHw4Nwl1GZNitK4AUWu5jGtigAUDoQtfDUvYHpQxcuLw2QGAdKDtKRflIiHSZ8wXBDR36H9R2Ang==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/fetch-http-handler": "^3.0.2", - "@smithy/node-http-handler": "^3.0.1", - "@smithy/property-provider": "^3.1.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/smithy-client": "^3.1.2", - "@smithy/types": "^3.1.0", - "@smithy/util-stream": "^3.0.2", + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.598.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.598.0", - "@aws-sdk/credential-provider-http": "3.598.0", - "@aws-sdk/credential-provider-process": "3.598.0", - "@aws-sdk/credential-provider-sso": "3.598.0", - "@aws-sdk/credential-provider-web-identity": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@smithy/credential-provider-imds": "^3.1.1", - "@smithy/property-provider": "^3.1.1", - "@smithy/shared-ini-file-loader": "^3.1.1", - "@smithy/types": "^3.1.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.598.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.600.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.598.0", - "@aws-sdk/credential-provider-http": "3.598.0", - "@aws-sdk/credential-provider-ini": "3.598.0", - "@aws-sdk/credential-provider-process": "3.598.0", - "@aws-sdk/credential-provider-sso": "3.598.0", - "@aws-sdk/credential-provider-web-identity": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@smithy/credential-provider-imds": "^3.1.1", - "@smithy/property-provider": "^3.1.1", - "@smithy/shared-ini-file-loader": "^3.1.1", - "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.598.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/property-provider": "^3.1.1", - "@smithy/shared-ini-file-loader": "^3.1.1", - "@smithy/types": "^3.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.598.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-retry": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.5.tgz", + "integrity": "sha512-V7MSjVDTlEt/plmOFBn1762Dyu5uqMrV2Pl2X0dYk4XvWfdWJNe9Bs5Bzb56wkCuiWjSfClVMGcsuKrGj7S/yg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.598.0", - "@aws-sdk/token-providers": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@smithy/property-provider": "^3.1.1", - "@smithy/shared-ini-file-loader": "^3.1.1", - "@smithy/types": "^3.1.0", + "@smithy/service-error-classification": "^4.0.5", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.598.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/property-provider": "^3.1.1", - "@smithy/types": "^3.1.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.598.0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers": { - "version": "3.624.0", + "node_modules/@aws-sdk/client-cognito-identity-provider/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-cognito-identity": "3.624.0", - "@aws-sdk/client-sso": "3.624.0", - "@aws-sdk/client-sts": "3.624.0", - "@aws-sdk/credential-provider-cognito-identity": "3.624.0", - "@aws-sdk/credential-provider-env": "3.620.1", - "@aws-sdk/credential-provider-http": "3.622.0", - "@aws-sdk/credential-provider-ini": "3.624.0", - "@aws-sdk/credential-provider-node": "3.624.0", - "@aws-sdk/credential-provider-process": "3.620.1", - "@aws-sdk/credential-provider-sso": "3.624.0", - "@aws-sdk/credential-provider-web-identity": "3.621.0", - "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", + "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/client-sso": { "version": "3.624.0", "license": "Apache-2.0", "dependencies": { @@ -1109,7 +1239,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-sso-oidc": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.624.0", "license": "Apache-2.0", "dependencies": { @@ -1160,7 +1290,7 @@ "@aws-sdk/client-sts": "^3.624.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-sts": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/client-sts": { "version": "3.624.0", "license": "Apache-2.0", "dependencies": { @@ -1209,7 +1339,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/core": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/core": { "version": "3.624.0", "license": "Apache-2.0", "dependencies": { @@ -1227,7 +1357,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-env": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-env": { "version": "3.620.1", "license": "Apache-2.0", "dependencies": { @@ -1240,7 +1370,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-http": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-http": { "version": "3.622.0", "license": "Apache-2.0", "dependencies": { @@ -1258,7 +1388,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-ini": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.624.0", "license": "Apache-2.0", "dependencies": { @@ -1281,7 +1411,7 @@ "@aws-sdk/client-sts": "^3.624.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-node": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-node": { "version": "3.624.0", "license": "Apache-2.0", "dependencies": { @@ -1302,7 +1432,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-process": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-process": { "version": "3.620.1", "license": "Apache-2.0", "dependencies": { @@ -1316,7 +1446,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-sso": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-sso": { "version": "3.624.0", "license": "Apache-2.0", "dependencies": { @@ -1332,7 +1462,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-web-identity": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.621.0", "license": "Apache-2.0", "dependencies": { @@ -1348,7 +1478,7 @@ "@aws-sdk/client-sts": "^3.621.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-host-header": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-host-header": { "version": "3.620.0", "license": "Apache-2.0", "dependencies": { @@ -1361,7 +1491,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-logger": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-logger": { "version": "3.609.0", "license": "Apache-2.0", "dependencies": { @@ -1370,246 +1500,5872 @@ "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.620.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.620.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/token-providers": { + "version": "3.614.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.614.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-endpoints": { + "version": "3.614.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.609.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.614.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/signature-v4": { + "version": "4.1.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-firehose/-/client-firehose-3.621.0.tgz", + "integrity": "sha512-XAjAkXdb35PDvBYph609Fxn4g00HYH/U6N4+KjF9gLQrdTU+wkjf3D9YD02DZNbApJVcu4eIxWh/8M25YkW02A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.621.0", + "@aws-sdk/client-sts": "3.621.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/credential-provider-node": "3.621.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/client-sso": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.621.0.tgz", + "integrity": "sha512-xpKfikN4u0BaUYZA9FGUMkkDmfoIP0Q03+A86WjqDWhcOoqNA1DkHsE4kZ+r064ifkPUfcNuUvlkVTEoBZoFjA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/core": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.621.0.tgz", + "integrity": "sha512-CtOwWmDdEiINkGXD93iGfXjN0WmCp9l45cDWHHGa8lRgEDyhuL7bwd/pH5aSzj0j8SiQBG2k0S7DHbd5RaqvbQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.3.1", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.621.0.tgz", + "integrity": "sha512-/jc2tEsdkT1QQAI5Dvoci50DbSxtJrevemwFsm0B73pwCcOQZ5ZwwSdVqGsPutzYzUVx3bcXg3LRL7jLACqRIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.621.0.tgz", + "integrity": "sha512-4JqpccUgz5Snanpt2+53hbOBbJQrSFq7E1sAAbgY6BKVQUsW5qyXqnjvSF32kDeKa5JpBl3bBWLZl04IadcPHw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.621.0", + "@aws-sdk/credential-provider-ini": "3.621.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.621.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.621.0.tgz", + "integrity": "sha512-0EWVnSc+JQn5HLnF5Xv405M8n4zfdx9gyGdpnCmAmFqEDHA8LmBdxJdpUk1Ovp/I5oPANhjojxabIW5f1uU0RA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.621.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.621.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.621.0.tgz", + "integrity": "sha512-Kza0jcFeA/GEL6xJlzR2KFf1PfZKMFnxfGzJzl5yN7EjoGdMijl34KaRyVnfRjnCWcsUpBWKNIDk9WZVMY9yiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.621.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.614.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/middleware-logger": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz", + "integrity": "sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/util-endpoints": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz", + "integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@smithy/signature-v4": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.4.tgz", + "integrity": "sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@smithy/util-middleware": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.11.tgz", + "integrity": "sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-kinesis/-/client-kinesis-3.621.0.tgz", + "integrity": "sha512-53Omt/beFmTQPjQNpMuPMk5nMzYVsXCRiO+MeqygZEKYG1fWw/UGluCWVbi7WjClOHacsW8lQcsqIRvkPDFNag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.621.0", + "@aws-sdk/client-sts": "3.621.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/credential-provider-node": "3.621.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/eventstream-serde-browser": "^3.0.5", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.4", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/client-sso": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.621.0.tgz", + "integrity": "sha512-xpKfikN4u0BaUYZA9FGUMkkDmfoIP0Q03+A86WjqDWhcOoqNA1DkHsE4kZ+r064ifkPUfcNuUvlkVTEoBZoFjA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/core": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.621.0.tgz", + "integrity": "sha512-CtOwWmDdEiINkGXD93iGfXjN0WmCp9l45cDWHHGa8lRgEDyhuL7bwd/pH5aSzj0j8SiQBG2k0S7DHbd5RaqvbQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.3.1", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.621.0.tgz", + "integrity": "sha512-/jc2tEsdkT1QQAI5Dvoci50DbSxtJrevemwFsm0B73pwCcOQZ5ZwwSdVqGsPutzYzUVx3bcXg3LRL7jLACqRIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.621.0.tgz", + "integrity": "sha512-4JqpccUgz5Snanpt2+53hbOBbJQrSFq7E1sAAbgY6BKVQUsW5qyXqnjvSF32kDeKa5JpBl3bBWLZl04IadcPHw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.621.0", + "@aws-sdk/credential-provider-ini": "3.621.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.621.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.621.0.tgz", + "integrity": "sha512-0EWVnSc+JQn5HLnF5Xv405M8n4zfdx9gyGdpnCmAmFqEDHA8LmBdxJdpUk1Ovp/I5oPANhjojxabIW5f1uU0RA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.621.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.621.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.621.0.tgz", + "integrity": "sha512-Kza0jcFeA/GEL6xJlzR2KFf1PfZKMFnxfGzJzl5yN7EjoGdMijl34KaRyVnfRjnCWcsUpBWKNIDk9WZVMY9yiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.621.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.614.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/middleware-logger": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz", + "integrity": "sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/util-endpoints": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz", + "integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@smithy/signature-v4": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.4.tgz", + "integrity": "sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@smithy/util-middleware": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.11.tgz", + "integrity": "sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-personalize-events/-/client-personalize-events-3.621.0.tgz", + "integrity": "sha512-qkVkqYvOe3WVuVNL/gRITGYFfHJCx2ijGFK7H3hNUJH3P4AwskmouAd1pWf+3cbGedRnj2is7iw7E602LeJIHA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.621.0", + "@aws-sdk/client-sts": "3.621.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/credential-provider-node": "3.621.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/client-sso": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.621.0.tgz", + "integrity": "sha512-xpKfikN4u0BaUYZA9FGUMkkDmfoIP0Q03+A86WjqDWhcOoqNA1DkHsE4kZ+r064ifkPUfcNuUvlkVTEoBZoFjA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/core": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.621.0.tgz", + "integrity": "sha512-CtOwWmDdEiINkGXD93iGfXjN0WmCp9l45cDWHHGa8lRgEDyhuL7bwd/pH5aSzj0j8SiQBG2k0S7DHbd5RaqvbQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.3.1", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.621.0.tgz", + "integrity": "sha512-/jc2tEsdkT1QQAI5Dvoci50DbSxtJrevemwFsm0B73pwCcOQZ5ZwwSdVqGsPutzYzUVx3bcXg3LRL7jLACqRIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.621.0.tgz", + "integrity": "sha512-4JqpccUgz5Snanpt2+53hbOBbJQrSFq7E1sAAbgY6BKVQUsW5qyXqnjvSF32kDeKa5JpBl3bBWLZl04IadcPHw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.621.0", + "@aws-sdk/credential-provider-ini": "3.621.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.621.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.621.0.tgz", + "integrity": "sha512-0EWVnSc+JQn5HLnF5Xv405M8n4zfdx9gyGdpnCmAmFqEDHA8LmBdxJdpUk1Ovp/I5oPANhjojxabIW5f1uU0RA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.621.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.621.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.621.0.tgz", + "integrity": "sha512-Kza0jcFeA/GEL6xJlzR2KFf1PfZKMFnxfGzJzl5yN7EjoGdMijl34KaRyVnfRjnCWcsUpBWKNIDk9WZVMY9yiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.621.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.614.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/middleware-logger": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz", + "integrity": "sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/util-endpoints": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz", + "integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@smithy/signature-v4": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.4.tgz", + "integrity": "sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@smithy/util-middleware": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.11.tgz", + "integrity": "sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.821.0.tgz", + "integrity": "sha512-aDEBZUKUd/+Tvudi0d9KQlqt2OW2P27LATZX0jkNC8yVk4145bAPS04EYoqdKLuyUn/U33DibEOgKUpxZB12jQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.821.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.821.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.1", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.17", + "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.621.0.tgz", + "integrity": "sha512-mMjk3mFUwV2Y68POf1BQMTF+F6qxt5tPu6daEUCNGC9Cenk3h2YXQQoS4/eSyYzuBiYk3vx49VgleRvdvkg8rg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/credential-provider-node": "3.621.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sso": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.621.0.tgz", + "integrity": "sha512-xpKfikN4u0BaUYZA9FGUMkkDmfoIP0Q03+A86WjqDWhcOoqNA1DkHsE4kZ+r064ifkPUfcNuUvlkVTEoBZoFjA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/core": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.621.0.tgz", + "integrity": "sha512-CtOwWmDdEiINkGXD93iGfXjN0WmCp9l45cDWHHGa8lRgEDyhuL7bwd/pH5aSzj0j8SiQBG2k0S7DHbd5RaqvbQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.3.1", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.621.0.tgz", + "integrity": "sha512-/jc2tEsdkT1QQAI5Dvoci50DbSxtJrevemwFsm0B73pwCcOQZ5ZwwSdVqGsPutzYzUVx3bcXg3LRL7jLACqRIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.621.0.tgz", + "integrity": "sha512-0EWVnSc+JQn5HLnF5Xv405M8n4zfdx9gyGdpnCmAmFqEDHA8LmBdxJdpUk1Ovp/I5oPANhjojxabIW5f1uU0RA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.621.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.621.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.621.0.tgz", + "integrity": "sha512-4JqpccUgz5Snanpt2+53hbOBbJQrSFq7E1sAAbgY6BKVQUsW5qyXqnjvSF32kDeKa5JpBl3bBWLZl04IadcPHw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.621.0", + "@aws-sdk/credential-provider-ini": "3.621.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.621.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.621.0.tgz", + "integrity": "sha512-Kza0jcFeA/GEL6xJlzR2KFf1PfZKMFnxfGzJzl5yN7EjoGdMijl34KaRyVnfRjnCWcsUpBWKNIDk9WZVMY9yiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.621.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-logger": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz", + "integrity": "sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/token-providers": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.614.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-endpoints": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz", + "integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/signature-v4": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.4.tgz", + "integrity": "sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-middleware": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.11.tgz", + "integrity": "sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.1.tgz", + "integrity": "sha512-xSw7bZEFKwOKrm/iv8e2BLt2ur98YZdrRD6nII8ditQeUsY2Q1JmIQ0rpILOhaLKYxxG2ivnoOpokzr9qLyDWA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.9.tgz", + "integrity": "sha512-AjDgX4UjORLltD/LZCBQTwjQqEfyrx/GeDTHcYLzIgf87pIT70tMWnN87NQpJru1K4ITirY2htSOxNECZJCBOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.1", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-retry": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.10.tgz", + "integrity": "sha512-RyhcA3sZIIvAo6r48b2Nx2qfg0OnyohlaV0fw415xrQyx5HQ2bvHl9vs/WBiDXIP49mCfws5wX4308c9Pi/isw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.5", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/service-error-classification": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.5.tgz", + "integrity": "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/smithy-client": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.1.tgz", + "integrity": "sha512-XPbcHRfd0iwx8dY5XCBCGyI7uweMW0oezYezxXcG8ANgvZ5YPuC6Ylh+n0bTHpdU3SCMZOnhzgVklYz+p3fIhw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.1", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.17.tgz", + "integrity": "sha512-HXq5181qnXmIwB7VrwqwP8rsJybHMoYuJnNoXy4PROs2pfSI4sWDMASF2i+7Lo+u64Y6xowhegcdxczowgJtZg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.17.tgz", + "integrity": "sha512-RfU2A5LjFhEHw4Nwl1GZNitK4AUWu5jGtigAUDoQtfDUvYHpQxcuLw2QGAdKDtKRflIiHSZ8wXBDR36H9R2Ang==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-retry": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.5.tgz", + "integrity": "sha512-V7MSjVDTlEt/plmOFBn1762Dyu5uqMrV2Pl2X0dYk4XvWfdWJNe9Bs5Bzb56wkCuiWjSfClVMGcsuKrGj7S/yg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.0.5", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.621.0.tgz", + "integrity": "sha512-707uiuReSt+nAx6d0c21xLjLm2lxeKc7padxjv92CIrIocnQSlJPxSCM7r5zBhwiahJA6MNQwmTl2xznU67KgA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.621.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/credential-provider-node": "3.621.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/client-sso": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.621.0.tgz", + "integrity": "sha512-xpKfikN4u0BaUYZA9FGUMkkDmfoIP0Q03+A86WjqDWhcOoqNA1DkHsE4kZ+r064ifkPUfcNuUvlkVTEoBZoFjA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/core": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.621.0.tgz", + "integrity": "sha512-CtOwWmDdEiINkGXD93iGfXjN0WmCp9l45cDWHHGa8lRgEDyhuL7bwd/pH5aSzj0j8SiQBG2k0S7DHbd5RaqvbQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.3.1", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.621.0.tgz", + "integrity": "sha512-/jc2tEsdkT1QQAI5Dvoci50DbSxtJrevemwFsm0B73pwCcOQZ5ZwwSdVqGsPutzYzUVx3bcXg3LRL7jLACqRIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.621.0.tgz", + "integrity": "sha512-0EWVnSc+JQn5HLnF5Xv405M8n4zfdx9gyGdpnCmAmFqEDHA8LmBdxJdpUk1Ovp/I5oPANhjojxabIW5f1uU0RA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.621.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.621.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.621.0.tgz", + "integrity": "sha512-4JqpccUgz5Snanpt2+53hbOBbJQrSFq7E1sAAbgY6BKVQUsW5qyXqnjvSF32kDeKa5JpBl3bBWLZl04IadcPHw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.621.0", + "@aws-sdk/credential-provider-ini": "3.621.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.621.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.621.0.tgz", + "integrity": "sha512-Kza0jcFeA/GEL6xJlzR2KFf1PfZKMFnxfGzJzl5yN7EjoGdMijl34KaRyVnfRjnCWcsUpBWKNIDk9WZVMY9yiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.621.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.614.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-logger": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz", + "integrity": "sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-endpoints": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz", + "integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/signature-v4": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.4.tgz", + "integrity": "sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-middleware": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.11.tgz", + "integrity": "sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.821.0.tgz", + "integrity": "sha512-8eB3wKbmfciQFmxFq7hAjy7mXdUs7vBOR5SwT0ZtQBg0Txc18Lc9tMViqqdO6/KU7OukA6ib2IAVSjIJJEN7FQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/core": "^3.5.1", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.1.tgz", + "integrity": "sha512-xSw7bZEFKwOKrm/iv8e2BLt2ur98YZdrRD6nII8ditQeUsY2Q1JmIQ0rpILOhaLKYxxG2ivnoOpokzr9qLyDWA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.9.tgz", + "integrity": "sha512-AjDgX4UjORLltD/LZCBQTwjQqEfyrx/GeDTHcYLzIgf87pIT70tMWnN87NQpJru1K4ITirY2htSOxNECZJCBOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.1", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/smithy-client": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.1.tgz", + "integrity": "sha512-XPbcHRfd0iwx8dY5XCBCGyI7uweMW0oezYezxXcG8ANgvZ5YPuC6Ylh+n0bTHpdU3SCMZOnhzgVklYz+p3fIhw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.1", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity": { + "version": "3.624.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.624.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.821.0.tgz", + "integrity": "sha512-C+s/A72pd7CXwEsJj9+Uq9T726iIfIF18hGRY8o82xcIEfOyakiPnlisku8zZOaAu+jm0CihbbYN4NyYNQ+HZQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.821.0.tgz", + "integrity": "sha512-gIRzTLnAsRfRSNarCag7G7rhcHagz4x5nNTWRihQs5cwTOghEExDy7Tj5m4TEkv3dcTAsNn+l4tnR4nZXo6R+Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.1.tgz", + "integrity": "sha512-xSw7bZEFKwOKrm/iv8e2BLt2ur98YZdrRD6nII8ditQeUsY2Q1JmIQ0rpILOhaLKYxxG2ivnoOpokzr9qLyDWA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.9.tgz", + "integrity": "sha512-AjDgX4UjORLltD/LZCBQTwjQqEfyrx/GeDTHcYLzIgf87pIT70tMWnN87NQpJru1K4ITirY2htSOxNECZJCBOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.1", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/smithy-client": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.1.tgz", + "integrity": "sha512-XPbcHRfd0iwx8dY5XCBCGyI7uweMW0oezYezxXcG8ANgvZ5YPuC6Ylh+n0bTHpdU3SCMZOnhzgVklYz+p3fIhw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.1", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.821.0.tgz", + "integrity": "sha512-VRTrmsca8kBHtY1tTek1ce+XkK/H0fzodBKcilM/qXjTyumMHPAzVAxKZfSvGC+28/pXyQzhOEyxZfw7giCiWA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/credential-provider-env": "3.821.0", + "@aws-sdk/credential-provider-http": "3.821.0", + "@aws-sdk/credential-provider-process": "3.821.0", + "@aws-sdk/credential-provider-sso": "3.821.0", + "@aws-sdk/credential-provider-web-identity": "3.821.0", + "@aws-sdk/nested-clients": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.821.0.tgz", + "integrity": "sha512-oBgbcgOXWMgknAfhIdTeHSSVIv+k2LXN9oTbxu1r++o4WWBWrEQ8mHU0Zo9dfr7Uaoqi3pezYZznsBkXnMLEOg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.821.0", + "@aws-sdk/credential-provider-http": "3.821.0", + "@aws-sdk/credential-provider-ini": "3.821.0", + "@aws-sdk/credential-provider-process": "3.821.0", + "@aws-sdk/credential-provider-sso": "3.821.0", + "@aws-sdk/credential-provider-web-identity": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.821.0.tgz", + "integrity": "sha512-e18ucfqKB3ICNj5RP/FEdvUfhVK6E9MALOsl8pKP13mwegug46p/1BsZWACD5n+Zf9ViiiHxIO7td03zQixfwA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.821.0.tgz", + "integrity": "sha512-Dt+pheBLom4O/egO4L75/72k9C1qtUOLl0F0h6lmqZe4Mvhz+wDtjoO/MdGC/P1q0kcIX/bBKr0NQ3cIvAH8pA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.821.0", + "@aws-sdk/core": "3.821.0", + "@aws-sdk/token-providers": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.821.0.tgz", + "integrity": "sha512-FF5wnRJkxSQaCVVvWNv53K1MhTMgH8d+O+MHTbkv51gVIgVATrtfFQMKBLcEAxzXrgAliIO3LiNv+1TqqBZ+BA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/nested-clients": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers": { + "version": "3.624.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.624.0", + "@aws-sdk/client-sso": "3.624.0", + "@aws-sdk/client-sts": "3.624.0", + "@aws-sdk/credential-provider-cognito-identity": "3.624.0", + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.622.0", + "@aws-sdk/credential-provider-ini": "3.624.0", + "@aws-sdk/credential-provider-node": "3.624.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.624.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-sso": { + "version": "3.624.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.624.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.624.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.624.0", + "@aws-sdk/credential-provider-node": "3.624.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.624.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-sts": { + "version": "3.624.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.624.0", + "@aws-sdk/core": "3.624.0", + "@aws-sdk/credential-provider-node": "3.624.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/core": { + "version": "3.624.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.3.2", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.620.1", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.622.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.624.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.622.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.624.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.624.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.624.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.622.0", + "@aws-sdk/credential-provider-ini": "3.624.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.624.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.620.1", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.624.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.624.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.621.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.620.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-logger": { + "version": "3.609.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.620.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.620.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/token-providers": { + "version": "3.614.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.614.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-endpoints": { + "version": "3.614.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.609.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.614.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/signature-v4": { + "version": "4.1.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", + "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", + "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", + "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.821.0.tgz", + "integrity": "sha512-rw8q3TxygMg3VrofN04QyWVCCyGwz3bVthYmBZZseENPWG3Krz1OCKcyqjkTcAxMQlEywOske+GIiOasGKnJ3w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@smithy/core": "^3.5.1", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.1.tgz", + "integrity": "sha512-xSw7bZEFKwOKrm/iv8e2BLt2ur98YZdrRD6nII8ditQeUsY2Q1JmIQ0rpILOhaLKYxxG2ivnoOpokzr9qLyDWA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.821.0.tgz", + "integrity": "sha512-2IuHcUsWw44ftSEDYU4dvktTEqgyDvkOcfpoGC/UmT4Qo6TVCP3U5tWEGpNK9nN+7nLvekruxxG/jaMt5/oWVw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.821.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.821.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.1", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.17", + "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.1.tgz", + "integrity": "sha512-xSw7bZEFKwOKrm/iv8e2BLt2ur98YZdrRD6nII8ditQeUsY2Q1JmIQ0rpILOhaLKYxxG2ivnoOpokzr9qLyDWA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.9.tgz", + "integrity": "sha512-AjDgX4UjORLltD/LZCBQTwjQqEfyrx/GeDTHcYLzIgf87pIT70tMWnN87NQpJru1K4ITirY2htSOxNECZJCBOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.1", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-retry": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.10.tgz", + "integrity": "sha512-RyhcA3sZIIvAo6r48b2Nx2qfg0OnyohlaV0fw415xrQyx5HQ2bvHl9vs/WBiDXIP49mCfws5wX4308c9Pi/isw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.5", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/service-error-classification": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.5.tgz", + "integrity": "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/smithy-client": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.1.tgz", + "integrity": "sha512-XPbcHRfd0iwx8dY5XCBCGyI7uweMW0oezYezxXcG8ANgvZ5YPuC6Ylh+n0bTHpdU3SCMZOnhzgVklYz+p3fIhw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.1", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.17.tgz", + "integrity": "sha512-HXq5181qnXmIwB7VrwqwP8rsJybHMoYuJnNoXy4PROs2pfSI4sWDMASF2i+7Lo+u64Y6xowhegcdxczowgJtZg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.17.tgz", + "integrity": "sha512-RfU2A5LjFhEHw4Nwl1GZNitK4AUWu5jGtigAUDoQtfDUvYHpQxcuLw2QGAdKDtKRflIiHSZ8wXBDR36H9R2Ang==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-retry": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.5.tgz", + "integrity": "sha512-V7MSjVDTlEt/plmOFBn1762Dyu5uqMrV2Pl2X0dYk4XvWfdWJNe9Bs5Bzb56wkCuiWjSfClVMGcsuKrGj7S/yg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.0.5", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", + "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.620.0", + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.620.0", + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.614.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.614.0", + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/token-providers": { - "version": "3.614.0", + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.614.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-endpoints": { - "version": "3.614.0", + "node_modules/@aws-sdk/token-providers": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.821.0.tgz", + "integrity": "sha512-qJ7wgKhdxGbPg718zWXbCYKDuSWZNU3TSw64hPRW6FtbZrIyZxObpiTKC6DKwfsVoZZhHEoP/imGykN1OdOTJA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", - "@smithy/util-endpoints": "^2.0.5", + "@aws-sdk/core": "3.821.0", + "@aws-sdk/nested-clients": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.609.0", + "node_modules/@aws-sdk/token-providers/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", - "bowser": "^2.11.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.614.0", + "node_modules/@aws-sdk/token-providers/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/signature-v4": { - "version": "4.1.0", + "node_modules/@aws-sdk/token-providers/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-uri-escape": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.598.0", + "node_modules/@aws-sdk/types": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.821.0.tgz", + "integrity": "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/protocol-http": "^4.0.1", - "@smithy/types": "^3.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.598.0", + "node_modules/@aws-sdk/types/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.598.0", + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.821.0.tgz", + "integrity": "sha512-Uknt/zUZnLE76zaAAPEayOeF5/4IZ2puTFXvcSCWHsi9m3tqbb9UozlnlVqvCZLCRWfQryZQoG2W4XSS3qgk5A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/protocol-http": "^4.0.1", - "@smithy/types": "^3.1.0", + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.598.0", + "node_modules/@aws-sdk/util-endpoints/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@aws-sdk/util-endpoints": "3.598.0", - "@smithy/protocol-http": "^4.0.1", - "@smithy/types": "^3.1.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.598.0", + "node_modules/@aws-sdk/util-endpoints/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/types": "^3.1.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.1", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.598.0", + "node_modules/@aws-sdk/util-endpoints/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/property-provider": "^3.1.1", - "@smithy/shared-ini-file-loader": "^3.1.1", - "@smithy/types": "^3.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.598.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/types": { - "version": "3.598.0", + "node_modules/@aws-sdk/util-endpoints/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.598.0", + "node_modules/@aws-sdk/util-endpoints/node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/types": "^3.1.0", - "@smithy/util-endpoints": "^2.0.2", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/util-locate-window": { @@ -1623,26 +7379,43 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.598.0", + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", + "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/types": "^3.1.0", + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, + "node_modules/@aws-sdk/util-user-agent-browser/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.598.0", + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.821.0.tgz", + "integrity": "sha512-YwMXc9EvuzJgnLBTyiQly2juPujXwDgcMHB0iSN92tHe7Dd1jJ1feBmTgdClaaqCeHFUaFpw+3JU/ZUJ6LjR+A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/types": "^3.1.0", + "@aws-sdk/middleware-user-agent": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -1653,6 +7426,59 @@ } } }, + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -6688,6 +12514,39 @@ "node": ">= 8" } }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.14.1", "license": "MIT", @@ -6919,56 +12778,123 @@ "npm": ">= 8.6.0" } }, - "node_modules/@smithy/abort-controller": { - "version": "3.1.1", + "node_modules/@smithy/abort-controller": { + "version": "3.1.1", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "3.0.5", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "2.3.2", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "3.2.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.10.tgz", + "integrity": "sha512-323B8YckSbUH0nMIpXn7HZsAVKHYHFUODa8gG9cHo0ySvA1fr5iWaNT+iIL0UCqUzG6QPHA3BSsBtRQou4mMqQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.7.2", + "@smithy/util-hex-encoding": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.14.tgz", + "integrity": "sha512-kbrt0vjOIihW3V7Cqj1SXQvAI5BR8SnyQYsandva0AOR307cXAc+IhPngxIPslxTLfxwDpNu0HzCAq6g42kCPg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/eventstream-serde-universal": "^3.0.13", + "@smithy/types": "^3.7.2", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@smithy/config-resolver": { - "version": "3.0.5", + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.11.tgz", + "integrity": "sha512-P2pnEp4n75O+QHjyO7cbw/vsw5l93K/8EWyjNCAAybYwUmj3M+hjSQZ9P5TVdUgEG08ueMAP5R4FkuSkElZ5tQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", + "@smithy/types": "^3.7.2", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@smithy/core": { - "version": "2.3.2", + "node_modules/@smithy/eventstream-serde-node": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.13.tgz", + "integrity": "sha512-zqy/9iwbj8Wysmvi7Lq7XFLeDgjRpTbCfwBhJa8WbrylTAHiAu6oQTwdY7iu2lxigbc9YYr9vPv5SzYny5tCXQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.14", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.1.12", - "@smithy/types": "^3.3.0", - "@smithy/util-middleware": "^3.0.3", + "@smithy/eventstream-serde-universal": "^3.0.13", + "@smithy/types": "^3.7.2", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@smithy/credential-provider-imds": { - "version": "3.2.0", + "node_modules/@smithy/eventstream-serde-universal": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.13.tgz", + "integrity": "sha512-L1Ib66+gg9uTnqp/18Gz4MDpJPKRE44geOjOQ2SVc0eiaO5l255ADziATZgjQjqumC7yPtp1XnjHlF1srcwjKw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", + "@smithy/eventstream-codec": "^3.1.10", + "@smithy/types": "^3.7.2", "tslib": "^2.6.2" }, "engines": { @@ -7017,6 +12943,67 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/md5-js": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-2.0.7.tgz", + "integrity": "sha512-2i2BpXF9pI5D1xekqUsgQ/ohv5+H//G9FlawJrkOJskV18PgJ8LiNbLiskMeYt07yAsSTZR7qtlcAaa/GQLWww==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.3.1", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/md5-js/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/md5-js/node_modules/@smithy/types": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", + "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/md5-js/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/md5-js/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@smithy/middleware-content-length": { "version": "3.0.5", "license": "Apache-2.0", @@ -7124,10 +13111,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "4.1.0", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", + "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.7.2", "tslib": "^2.6.2" }, "engines": { @@ -7179,19 +13168,122 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "3.1.1", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/types": "^3.2.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.2", - "@smithy/util-uri-escape": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/smithy-client": { @@ -7210,7 +13302,9 @@ } }, "node_modules/@smithy/types": { - "version": "3.3.0", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -7391,6 +13485,33 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/util-waiter": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.2.0.tgz", + "integrity": "sha512-PpjSboaDUE6yl+1qlg3Si57++e84oXdWGbuFUSAciXsVfEZJJJupR2Nb0QuXHiunt2vGR+1PTizOMvnUPaG2Qg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^3.1.9", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-waiter/node_modules/@smithy/abort-controller": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.9.tgz", + "integrity": "sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "license": "Apache-2.0", @@ -8116,6 +14237,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/aws-lambda": { + "version": "8.10.149", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.149.tgz", + "integrity": "sha512-NXSZIhfJjnXqJgtS7IwutqIF/SOy1Wz5Px4gUY1RWITp3AYTyuJS4xaXr/bIJY1v15XMzrJ5soGnPM+7uigZjA==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "license": "MIT", @@ -8418,7 +14545,7 @@ }, "node_modules/@types/prop-types": { "version": "15.7.11", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/q": { @@ -8435,7 +14562,7 @@ }, "node_modules/@types/react": { "version": "18.2.47", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -8474,7 +14601,7 @@ }, "node_modules/@types/scheduler": { "version": "0.16.8", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/semver": { @@ -8533,6 +14660,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "license": "MIT" + }, "node_modules/@types/web-bluetooth": { "version": "0.0.20", "dev": true, @@ -9624,6 +15757,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws-amplify": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/aws-amplify/-/aws-amplify-6.15.0.tgz", + "integrity": "sha512-7h3wLgGzYWPUfdGIk1vgY+UU2/KoBZs5PZiP5aSsxjQdMb2Q7h8fBQgFcGvKG8kK9jD/UI8Mw8e2K/V7Zfw9jw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/analytics": "7.0.81", + "@aws-amplify/api": "6.3.12", + "@aws-amplify/auth": "6.13.0", + "@aws-amplify/core": "6.12.0", + "@aws-amplify/datastore": "5.0.83", + "@aws-amplify/notifications": "2.0.81", + "@aws-amplify/storage": "6.9.0", + "tslib": "^2.5.0" + } + }, + "node_modules/aws-jwt-verify": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/aws-jwt-verify/-/aws-jwt-verify-5.1.0.tgz", + "integrity": "sha512-98ioOBMyrLU5jW5rPvkJo20XlNB2rAX3tZR3BM6AamfBkOoSRLV1EyGkbgHQzgFOWyQ7yV8+tce6M24rOpMkgw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/aws-sdk": { "version": "2.1646.0", "hasInstallScript": true, @@ -10617,6 +16775,18 @@ "version": "1.2.3", "license": "MIT" }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/classnames": { "version": "2.5.1", "license": "MIT" @@ -10746,6 +16916,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/cmd-shim": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.3.tgz", @@ -11288,6 +17467,18 @@ "version": "1.5.10", "license": "MIT" }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/create-require": { "version": "1.1.1", "devOptional": true, @@ -11672,7 +17863,7 @@ }, "node_modules/csstype": { "version": "3.1.3", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/customize-cra": { @@ -14531,6 +20722,15 @@ "version": "1.4.0", "license": "MIT" }, + "node_modules/graphql": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", + "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==", + "license": "MIT", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -15065,7 +21265,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -15224,6 +21423,16 @@ "prop-types": "^15.8.1" } }, + "node_modules/input-otp": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", + "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/inquirer": { "version": "8.2.6", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", @@ -17057,6 +23266,15 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -18009,6 +24227,15 @@ "lru-cache": "6.0.0" } }, + "node_modules/lucide-react": { + "version": "0.507.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.507.0.tgz", + "integrity": "sha512-XfgE6gvAHwAtnbUvWiTTHx4S3VGR+cUJHEc0vrh9Ogu672I1Tue2+Cp/8JJqpytgcBHAB1FVI297W4XGNwc2dQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/lz-string": { "version": "1.5.0", "dev": true, @@ -23971,6 +30198,16 @@ "version": "3.2.4", "license": "MIT" }, + "node_modules/tailwind-merge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz", + "integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.1", "license": "MIT", @@ -24006,6 +30243,15 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, "node_modules/tailwindcss/node_modules/arg": { "version": "5.0.2", "license": "MIT" @@ -24775,6 +31021,15 @@ "node": ">=0.8.0" } }, + "node_modules/ulid": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.4.0.tgz", + "integrity": "sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg==", + "license": "MIT", + "bin": { + "ulid": "bin/cli.js" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "license": "MIT", @@ -26539,17 +32794,27 @@ "name": "connect-react-next", "version": "0.1.0", "dependencies": { - "@aws-sdk/client-cognito-identity-provider": "^3.423.0", + "@aws-sdk/client-cognito-identity-provider": "^3.799.0", "@aws-sdk/credential-providers": "^3.624.0", "@corbado/connect-react": "*", + "@radix-ui/react-slot": "^1.2.0", + "aws-amplify": "^6.14.4", + "aws-jwt-verify": "^5.0.0", "aws-sdk": "^2.1646.0", "axios": "^1.7.3", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "crypto-js": "^4.2.0", + "input-otp": "^1.4.2", + "jose": "^6.0.10", "jsonwebtoken": "^9.0.2", "jwks-rsa": "^3.1.0", + "lucide-react": "^0.507.0", "next": "15.2.4", "react": "^18", "react-dom": "^18", + "tailwind-merge": "^3.2.0", + "tailwindcss-animate": "^1.0.7", "totp-generator": "^1.0.0" }, "devDependencies": { @@ -26563,6 +32828,15 @@ "typescript": "^5" } }, + "playground/connect-next/node_modules/jose": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.11.tgz", + "integrity": "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "playground/react": { "name": "react-ui", "version": "0.1.0", diff --git a/playground/connect-next/app/(api)/connectTokenExternal/route.ts b/playground/connect-next/app/(api)/connectTokenExternal/route.ts index 1ef175ab1..2e3a28cb5 100644 --- a/playground/connect-next/app/(api)/connectTokenExternal/route.ts +++ b/playground/connect-next/app/(api)/connectTokenExternal/route.ts @@ -19,6 +19,29 @@ export async function POST(req: NextRequest) { identifier: identifier, }); + const simulateError = process.env.SIMULATE_ERROR; + if (simulateError && displayName.endsWith('@corbado.com')) { + console.warn('Simulating error for testing purposes'); + + switch (simulateError) { + case 'error_response': + return new Response(JSON.stringify({ error: 'Simulated error' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + case 'invalid_token': + return new Response(JSON.stringify({ token: 'invalid_token' }), { + status: 201, + headers: { 'Content-Type': 'application/json' }, + }); + case 'empty_token': + return new Response(JSON.stringify({ token: '' }), { + status: 201, + headers: { 'Content-Type': 'application/json' }, + }); + } + } + return new Response(JSON.stringify({ token: connectToken }), { status: 201, headers: { 'Content-Type': 'application/json' }, From 77cc7458946ce18f2f291ac4ae2a7aa375b3fb75 Mon Sep 17 00:00:00 2001 From: Incorbador Date: Wed, 11 Jun 2025 12:18:42 +0200 Subject: [PATCH 24/60] Add log --- playground/connect-next/app/(api)/connectTokenExternal/route.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/playground/connect-next/app/(api)/connectTokenExternal/route.ts b/playground/connect-next/app/(api)/connectTokenExternal/route.ts index 2e3a28cb5..5bb84f008 100644 --- a/playground/connect-next/app/(api)/connectTokenExternal/route.ts +++ b/playground/connect-next/app/(api)/connectTokenExternal/route.ts @@ -10,6 +10,7 @@ export async function POST(req: NextRequest) { const body = (await req.json()) as Payload; const { idToken, connectTokenType } = body; + console.log('creating connectTokenType', connectTokenType); try { const { displayName, identifier } = await verifyAmplifyTokenExternal(idToken); From a65687cf4b0cb2e63699845d377bf202c61f5d29 Mon Sep 17 00:00:00 2001 From: aehnh Date: Mon, 16 Jun 2025 14:41:37 +0200 Subject: [PATCH 25/60] run playground in connect playwright --- .../workflows/deploy-playground-and-test.yml | 295 ------------------ .../workflows/deploy-playgrounds-and-test.yml | 295 ++++++++++++++++++ .github/workflows/test.yml | 203 ++++++++++++ .../tests-e2e/playwright.config.connect.ts | 2 +- .../src/complete/utils/playground.ts | 26 -- .../src/connect/fixtures/BaseTest.ts | 4 +- .../tests-e2e/src/connect/models/BaseModel.ts | 12 +- .../src/connect/models/StorageModel.ts | 4 +- .../src/connect/scenarios/append.spec.ts | 29 +- .../tests-e2e/src/connect/scenarios/hooks.ts | 8 +- .../src/connect/scenarios/login.spec.ts | 67 +++- .../src/connect/scenarios/misc.spec.ts | 42 ++- .../connect/scenarios/passkey-list.spec.ts | 31 +- .../tests-e2e/src/connect/utils/Playground.ts | 85 +++++ playground/connect-next/package.json | 1 + 15 files changed, 752 insertions(+), 352 deletions(-) delete mode 100644 .github/workflows/deploy-playground-and-test.yml create mode 100644 .github/workflows/deploy-playgrounds-and-test.yml create mode 100644 .github/workflows/test.yml create mode 100644 packages/tests-e2e/src/connect/utils/Playground.ts diff --git a/.github/workflows/deploy-playground-and-test.yml b/.github/workflows/deploy-playground-and-test.yml deleted file mode 100644 index 6f57df33f..000000000 --- a/.github/workflows/deploy-playground-and-test.yml +++ /dev/null @@ -1,295 +0,0 @@ -name: Deploy Playground Apps to Vercel and Test on React -env: - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - BRANCH_NAME_RAW: ${{ github.head_ref || github.ref_name }} - -on: - pull_request: - push: - branches: - - develop - -jobs: - deploy: - runs-on: ubuntu-24.04 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - - - name: Cache node modules - id: cache-npm - uses: actions/cache@v3 - env: - cache-name: cache-node-modules - with: - path: | - ~/.npm - ./node_modules - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-force-1 - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} - name: Install Dependencies - run: | - npm i - npm install lerna - npm install vercel@33.2.0 - npm list - - - name: Check ESLint Errors - run: npm run lint - - - name: Check Prettier Formatting - run: npm run prettier:check - - - name: Build SDKs - run: | - npm run build - npm run build:bundler:local - - - name: Normalize branch name - run: | - echo "BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-')" >> "$GITHUB_ENV" - shell: bash - - - name: Deploy react playground to Vercel - run: | - npx vercel link --yes --project react-playground --scope corbado -t $VERCEL_TOKEN - npx vercel pull -t $VERCEL_TOKEN - npx vercel build -t $VERCEL_TOKEN - url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" - npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.react.playground.corbado.io - env: - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_REACT_PLAYGROUND }} - - - name: Deploy connect next playground to Vercel - run: | - npx vercel link --yes --project connect-next-playground --scope corbado -t $VERCEL_TOKEN - npx vercel pull --environment=preview -t $VERCEL_TOKEN - npx vercel build -t $VERCEL_TOKEN - url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" - npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.connect-next.playground.corbado.io - env: - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_CONNECT_NEXT_PLAYGROUND }} - - - name: Deploy web-js playground to Vercel - run: | - npx vercel link --yes --project web-js-playground --scope corbado -t $VERCEL_TOKEN - npx vercel pull -t $VERCEL_TOKEN - npx vercel build -t $VERCEL_TOKEN - url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" - npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.web-js.playground.corbado.io - env: - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_WEB_JS_PLAYGROUND }} - - - name: Deploy web-js-script playground to Vercel - run: | - npx vercel link --yes --project web-js-script-playground --scope corbado -t $VERCEL_TOKEN - npx vercel pull -t $VERCEL_TOKEN - npx vercel build -t $VERCEL_TOKEN - url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" - npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.web-js-script.playground.corbado.io - env: - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_WEB_JS_SCRIPT_PLAYGROUND }} - - test: - needs: deploy - timeout-minutes: 60 - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - - - name: Cache node modules - id: cache-npm - uses: actions/cache@v3 - env: - cache-name: cache-node-modules - with: - path: | - ~/.npm - ./node_modules - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-force-1 - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} - name: Install root dependencies - run: | - npm i - npm install lerna - npm install vercel@33.2.0 - npm list - - - name: Build SDKs - run: | - npm run build - npm run build:bundler:local - - - name: Get installed Playwright version - id: playwright-version - run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')" - - - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} - name: List the state of node modules - continue-on-error: true - run: | - cd packages/tests-e2e - npm list - - - name: Install dependencies - run: | - cd packages/tests-e2e - npm install - sudo apt-get update - sudo apt-get install -y libxml2-utils - - - uses: actions/cache@v3 - id: playwright-cache - with: - path: '~/.cache/ms-playwright' - key: '${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}' - restore-keys: | - ${{ runner.os }}-playwright- - - - name: Install Playwright's dependencies - if: steps.playwright-cache.outputs.cache-hit != 'true' - run: | - cd packages/tests-e2e - npx playwright install --with-deps - - - name: Create a log stream on AWS CloudWatch - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} - run: | - TIMESTAMP=$(date +%s000) - echo "LOG_STREAM_NAME=javascript-$TIMESTAMP" >> $GITHUB_ENV - aws logs create-log-stream --log-group-name "test-results-board" --log-stream-name "javascript-$TIMESTAMP" - - - name: Run Complete tests for react - run: | - cd packages/tests-e2e - set +e - npx playwright test --config=playwright.config.complete.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "COMPLETE_REACT_FAILED=true" >> $GITHUB_ENV - fi - env: - PLAYWRIGHT_NUM_CORES: 4 - PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} - PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} - PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} - PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} - GITHUB_RUN_ID: ${{ github.run_id }} - SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} - GITHUB_BRANCH_NAME: ${{ github.ref_name }} - continue-on-error: true - - - uses: actions/upload-artifact@v4 - with: - name: playwright-report-complete-react - path: packages/tests-e2e/playwright-report/ - retention-days: 30 - - - name: Send logs to AWS CloudWatch - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} - run: | - cd packages/tests-e2e - TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) - FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) - SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) - ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) - TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) - PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) - FAILED=$((FAILURES + ERRORS)) - TIMESTAMP=$(date +%s000) - LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"complete\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" - aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" - - - name: Run Connect tests for react - run: | - cd packages/tests-e2e - BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') - export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.connect-next.playground.corbado.io" - set +e - npx playwright test --config=playwright.config.connect.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "CONNECT_REACT_FAILED=true" >> $GITHUB_ENV - fi - env: - PLAYWRIGHT_NUM_CORES: 4 - BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} - PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} - PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} - GITHUB_RUN_ID: ${{ github.run_id }} - SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} - GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} - continue-on-error: true - - - uses: actions/upload-artifact@v4 - with: - name: playwright-report-connect-react - path: packages/tests-e2e/playwright-report/ - retention-days: 30 - - - name: Send logs to AWS CloudWatch - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} - run: | - cd packages/tests-e2e - TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) - FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) - SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) - ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) - TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) - PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) - FAILED=$((FAILURES + ERRORS)) - TIMESTAMP=$(date +%s000) - LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"connect\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" - aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" - - - name: Aggregate results - run: | - FAILED_STEPS="" - if [ "${COMPLETE_REACT_FAILED}" = "true" ]; then - FAILED_STEPS="${FAILED_STEPS} Complete-React" - fi - if [ "${COMPLETE_WEBJS_FAILED}" = "true" ]; then - FAILED_STEPS="${FAILED_STEPS} Complete-WebJs" - fi - if [ "${COMPLETE_WEBJSSCRIPT_FAILED}" = "true" ]; then - FAILED_STEPS="${FAILED_STEPS} Complete-WebJsScript" - fi - if [ "${CONNECT_REACT_FAILED}" = "true" ]; then - FAILED_STEPS="${FAILED_STEPS} Connect-React" - fi - - if [ -n "$FAILED_STEPS" ]; then - echo "The following test steps have failed: $FAILED_STEPS" - exit 1 - else - echo "All tests passed." - fi diff --git a/.github/workflows/deploy-playgrounds-and-test.yml b/.github/workflows/deploy-playgrounds-and-test.yml new file mode 100644 index 000000000..0edd894c1 --- /dev/null +++ b/.github/workflows/deploy-playgrounds-and-test.yml @@ -0,0 +1,295 @@ +name: Deploy Playground Apps to Vercel and Test on React +env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + BRANCH_NAME_RAW: ${{ github.head_ref || github.ref_name }} + +on: + pull_request: + push: + branches: + - develop + +jobs: + deploy: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + + - name: Cache node modules + id: cache-npm + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + path: | + ~/.npm + ./node_modules + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-force-1 + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + name: Install Dependencies + run: | + npm i + npm install lerna + npm install vercel@33.2.0 + npm list + + - name: Check ESLint Errors + run: npm run lint + + - name: Check Prettier Formatting + run: npm run prettier:check + + - name: Build SDKs + run: | + npm run build + npm run build:bundler:local + + - name: Normalize branch name + run: | + echo "BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-')" >> "$GITHUB_ENV" + shell: bash + + - name: Deploy react playground to Vercel + run: | + npx vercel link --yes --project react-playground --scope corbado -t $VERCEL_TOKEN + npx vercel pull -t $VERCEL_TOKEN + npx vercel build -t $VERCEL_TOKEN + url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" + npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.react.playground.corbado.io + env: + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_REACT_PLAYGROUND }} + + - name: Deploy connect next playground to Vercel + run: | + npx vercel link --yes --project connect-next-playground --scope corbado -t $VERCEL_TOKEN + npx vercel pull --environment=preview -t $VERCEL_TOKEN + npx vercel build -t $VERCEL_TOKEN + url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" + npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.connect-next.playground.corbado.io + env: + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_CONNECT_NEXT_PLAYGROUND }} + + - name: Deploy web-js playground to Vercel + run: | + npx vercel link --yes --project web-js-playground --scope corbado -t $VERCEL_TOKEN + npx vercel pull -t $VERCEL_TOKEN + npx vercel build -t $VERCEL_TOKEN + url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" + npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.web-js.playground.corbado.io + env: + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_WEB_JS_PLAYGROUND }} + + - name: Deploy web-js-script playground to Vercel + run: | + npx vercel link --yes --project web-js-script-playground --scope corbado -t $VERCEL_TOKEN + npx vercel pull -t $VERCEL_TOKEN + npx vercel build -t $VERCEL_TOKEN + url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" + npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.web-js-script.playground.corbado.io + env: + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_WEB_JS_SCRIPT_PLAYGROUND }} + +# test: +# needs: deploy +# timeout-minutes: 60 +# runs-on: ubuntu-latest +# +# steps: +# - name: Checkout code +# uses: actions/checkout@v4 +# +# - name: Set up Node.js +# uses: actions/setup-node@v4 +# +# - name: Cache node modules +# id: cache-npm +# uses: actions/cache@v3 +# env: +# cache-name: cache-node-modules +# with: +# path: | +# ~/.npm +# ./node_modules +# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-force-1 +# restore-keys: | +# ${{ runner.os }}-build-${{ env.cache-name }}- +# ${{ runner.os }}-build- +# ${{ runner.os }}- +# +# - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} +# name: Install root dependencies +# run: | +# npm i +# npm install lerna +# npm install vercel@33.2.0 +# npm list +# +# - name: Build SDKs +# run: | +# npm run build +# npm run build:bundler:local +# +# - name: Get installed Playwright version +# id: playwright-version +# run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')" +# +# - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} +# name: List the state of node modules +# continue-on-error: true +# run: | +# cd packages/tests-e2e +# npm list +# +# - name: Install dependencies +# run: | +# cd packages/tests-e2e +# npm install +# sudo apt-get update +# sudo apt-get install -y libxml2-utils +# +# - uses: actions/cache@v3 +# id: playwright-cache +# with: +# path: '~/.cache/ms-playwright' +# key: '${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}' +# restore-keys: | +# ${{ runner.os }}-playwright- +# +# - name: Install Playwright's dependencies +# if: steps.playwright-cache.outputs.cache-hit != 'true' +# run: | +# cd packages/tests-e2e +# npx playwright install --with-deps +# +# - name: Create a log stream on AWS CloudWatch +# env: +# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} +# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} +# AWS_REGION: ${{ secrets.AWS_REGION }} +# run: | +# TIMESTAMP=$(date +%s000) +# echo "LOG_STREAM_NAME=javascript-$TIMESTAMP" >> $GITHUB_ENV +# aws logs create-log-stream --log-group-name "test-results-board" --log-stream-name "javascript-$TIMESTAMP" +# +# - name: Run Complete tests for react +# run: | +# cd packages/tests-e2e +# set +e +# npx playwright test --config=playwright.config.complete.ts +# EXIT_CODE=$? +# if [ $EXIT_CODE -ne 0 ]; then +# echo "COMPLETE_REACT_FAILED=true" >> $GITHUB_ENV +# fi +# env: +# PLAYWRIGHT_NUM_CORES: 4 +# PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} +# PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} +# PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} +# PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} +# GITHUB_RUN_ID: ${{ github.run_id }} +# SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} +# GITHUB_BRANCH_NAME: ${{ github.ref_name }} +# continue-on-error: true +# +# - uses: actions/upload-artifact@v4 +# with: +# name: playwright-report-complete-react +# path: packages/tests-e2e/playwright-report/ +# retention-days: 30 +# +# - name: Send logs to AWS CloudWatch +# env: +# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} +# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} +# AWS_REGION: ${{ secrets.AWS_REGION }} +# run: | +# cd packages/tests-e2e +# TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) +# FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) +# SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) +# ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) +# TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) +# PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) +# FAILED=$((FAILURES + ERRORS)) +# TIMESTAMP=$(date +%s000) +# LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"complete\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" +# aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" +# +# - name: Run Connect tests for react +# run: | +# cd packages/tests-e2e +# BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') +# export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.connect-next.playground.corbado.io" +# set +e +# npx playwright test --config=playwright.config.connect.ts +# EXIT_CODE=$? +# if [ $EXIT_CODE -ne 0 ]; then +# echo "CONNECT_REACT_FAILED=true" >> $GITHUB_ENV +# fi +# env: +# PLAYWRIGHT_NUM_CORES: 4 +# BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} +# PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} +# PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} +# GITHUB_RUN_ID: ${{ github.run_id }} +# SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} +# GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} +# continue-on-error: true +# +# - uses: actions/upload-artifact@v4 +# with: +# name: playwright-report-connect-react +# path: packages/tests-e2e/playwright-report/ +# retention-days: 30 +# +# - name: Send logs to AWS CloudWatch +# env: +# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} +# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} +# AWS_REGION: ${{ secrets.AWS_REGION }} +# run: | +# cd packages/tests-e2e +# TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) +# FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) +# SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) +# ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) +# TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) +# PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) +# FAILED=$((FAILURES + ERRORS)) +# TIMESTAMP=$(date +%s000) +# LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"connect\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" +# aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" +# +# - name: Aggregate results +# run: | +# FAILED_STEPS="" +# if [ "${COMPLETE_REACT_FAILED}" = "true" ]; then +# FAILED_STEPS="${FAILED_STEPS} Complete-React" +# fi +# if [ "${COMPLETE_WEBJS_FAILED}" = "true" ]; then +# FAILED_STEPS="${FAILED_STEPS} Complete-WebJs" +# fi +# if [ "${COMPLETE_WEBJSSCRIPT_FAILED}" = "true" ]; then +# FAILED_STEPS="${FAILED_STEPS} Complete-WebJsScript" +# fi +# if [ "${CONNECT_REACT_FAILED}" = "true" ]; then +# FAILED_STEPS="${FAILED_STEPS} Connect-React" +# fi +# +# if [ -n "$FAILED_STEPS" ]; then +# echo "The following test steps have failed: $FAILED_STEPS" +# exit 1 +# else +# echo "All tests passed." +# fi diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..ef2d889d4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,203 @@ +name: Test on React +env: + BRANCH_NAME_RAW: ${{ github.head_ref || github.ref_name }} + +on: + pull_request: + push: + branches: + - develop + +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-24.04 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + + - name: Cache node modules + id: cache-npm + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + path: | + ~/.npm + ./node_modules + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-force-1 + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + name: Install root dependencies + run: | + npm i + npm install lerna + npm install vercel@33.2.0 + npm list + + - name: Build SDKs + run: | + npm run build + npm run build:bundler:local + + - name: Get installed Playwright version + id: playwright-version + run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')" + + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + name: List the state of node modules + continue-on-error: true + run: | + cd packages/tests-e2e + npm list + + - name: Install dependencies + run: | + cd packages/tests-e2e + npm install + sudo apt-get update + sudo apt-get install -y libxml2-utils + + - uses: actions/cache@v3 + id: playwright-cache + with: + path: '~/.cache/ms-playwright' + key: '${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}' + restore-keys: | + ${{ runner.os }}-playwright- + + - name: Install Playwright's dependencies + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: | + cd packages/tests-e2e + npx playwright install --with-deps + + - name: Create a log stream on AWS CloudWatch + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION }} + run: | + TIMESTAMP=$(date +%s000) + echo "LOG_STREAM_NAME=javascript-$TIMESTAMP" >> $GITHUB_ENV + aws logs create-log-stream --log-group-name "test-results-board" --log-stream-name "javascript-$TIMESTAMP" + + - name: Run Complete tests for react + run: | + cd packages/tests-e2e + set +e + npx playwright test --config=playwright.config.complete.ts + EXIT_CODE=$? + if [ $EXIT_CODE -ne 0 ]; then + echo "COMPLETE_REACT_FAILED=true" >> $GITHUB_ENV + fi + env: + PLAYWRIGHT_NUM_CORES: 4 + PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} + PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} + PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} + PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} + GITHUB_RUN_ID: ${{ github.run_id }} + SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} + GITHUB_BRANCH_NAME: ${{ github.ref_name }} + continue-on-error: true + + - uses: actions/upload-artifact@v4 + with: + name: playwright-report-complete-react + path: packages/tests-e2e/playwright-report/ + retention-days: 30 + + - name: Send logs to AWS CloudWatch + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION }} + run: | + cd packages/tests-e2e + TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) + FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) + SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) + ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) + TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) + PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) + FAILED=$((FAILURES + ERRORS)) + TIMESTAMP=$(date +%s000) + LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"complete\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" + aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" + +# - name: Run Connect tests for react +# run: | +# cd packages/tests-e2e +# BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') +# export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.connect-next.playground.corbado.io" +# set +e +# npx playwright test --config=playwright.config.connect.ts +# EXIT_CODE=$? +# if [ $EXIT_CODE -ne 0 ]; then +# echo "CONNECT_REACT_FAILED=true" >> $GITHUB_ENV +# fi +# env: +# PLAYWRIGHT_NUM_CORES: 4 +# BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} +# PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} +# PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} +# GITHUB_RUN_ID: ${{ github.run_id }} +# SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} +# GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} +# continue-on-error: true +# +# - uses: actions/upload-artifact@v4 +# with: +# name: playwright-report-connect-react +# path: packages/tests-e2e/playwright-report/ +# retention-days: 30 +# +# - name: Send logs to AWS CloudWatch +# env: +# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} +# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} +# AWS_REGION: ${{ secrets.AWS_REGION }} +# run: | +# cd packages/tests-e2e +# TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) +# FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) +# SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) +# ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) +# TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) +# PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) +# FAILED=$((FAILURES + ERRORS)) +# TIMESTAMP=$(date +%s000) +# LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"connect\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" +# aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" + + - name: Aggregate results + run: | + FAILED_STEPS="" + if [ "${COMPLETE_REACT_FAILED}" = "true" ]; then + FAILED_STEPS="${FAILED_STEPS} Complete-React" + fi + if [ "${COMPLETE_WEBJS_FAILED}" = "true" ]; then + FAILED_STEPS="${FAILED_STEPS} Complete-WebJs" + fi + if [ "${COMPLETE_WEBJSSCRIPT_FAILED}" = "true" ]; then + FAILED_STEPS="${FAILED_STEPS} Complete-WebJsScript" + fi +# if [ "${CONNECT_REACT_FAILED}" = "true" ]; then +# FAILED_STEPS="${FAILED_STEPS} Connect-React" +# fi + + if [ -n "$FAILED_STEPS" ]; then + echo "The following test steps have failed: $FAILED_STEPS" + exit 1 + else + echo "All tests passed." + fi diff --git a/packages/tests-e2e/playwright.config.connect.ts b/packages/tests-e2e/playwright.config.connect.ts index 568c353e5..6d37126fe 100644 --- a/packages/tests-e2e/playwright.config.connect.ts +++ b/packages/tests-e2e/playwright.config.connect.ts @@ -46,7 +46,7 @@ export default defineConfig({ use: { actionTimeout: operationTimeout, // default: none navigationTimeout: operationTimeout, // default: none - baseURL: process.env.PLAYWRIGHT_TEST_URL, + // baseURL: process.env.PLAYWRIGHT_TEST_URL, screenshot: 'only-on-failure', video: 'retain-on-failure', trace: 'retain-on-failure', diff --git a/packages/tests-e2e/src/complete/utils/playground.ts b/packages/tests-e2e/src/complete/utils/playground.ts index 04ae084c6..b5f93aecd 100644 --- a/packages/tests-e2e/src/complete/utils/playground.ts +++ b/packages/tests-e2e/src/complete/utils/playground.ts @@ -8,10 +8,6 @@ import waitPort from 'wait-port'; type PlaygroundType = 'react' | 'web-js' | 'web-js-script'; const PLAYGROUND_TYPE: PlaygroundType = (process.env.PLAYGROUND_TYPE as PlaygroundType) || 'react'; -// function getRootDir(): string { -// return path.resolve(__dirname, '../../../../..'); -// } - function getPlaygroundDir(): string { switch (PLAYGROUND_TYPE) { case 'react': @@ -68,28 +64,6 @@ export function killPlaygroundNew(server: ChildProcess) { } export default async function installPlaygroundDeps() { - // const rootDir = getRootDir(); - // - // const lernaBuildProcess = spawn('npm', ['run', 'build'], { - // cwd: rootDir, - // stdio: 'inherit', - // shell: true, - // }); - // - // await new Promise((resolve, reject) => { - // lernaBuildProcess.on('close', (code: number) => { - // if (code === 0) { - // console.log(`[Global Setup] 'lerna run build' completed successfully.`); - // resolve(); - // } else { - // reject(new Error(`[Global Setup] 'lerna run build' failed with code ${code}`)); - // } - // }); - // lernaBuildProcess.on('error', (err: Error) => { - // reject(new Error(`[Global Setup] Failed to start 'lerna run build' process: ${err.message}`)); - // }); - // }); - const playgroundDir = getPlaygroundDir(); const installProcess = spawn('npm', ['install'], { diff --git a/packages/tests-e2e/src/connect/fixtures/BaseTest.ts b/packages/tests-e2e/src/connect/fixtures/BaseTest.ts index 7d1ad37e6..5f427b53f 100644 --- a/packages/tests-e2e/src/connect/fixtures/BaseTest.ts +++ b/packages/tests-e2e/src/connect/fixtures/BaseTest.ts @@ -5,7 +5,9 @@ import { CDPSessionManager } from '../utils/CDPSessionManager'; import { NetworkRequestBlocker } from '../utils/NetworkRequestBlocker'; import { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; -export const test = base.extend<{ model: BaseModel }>({ +export const test = base.extend<{ + model: BaseModel; +}>({ model: async ({ page }, use) => { const cdpManager = new CDPSessionManager(); await cdpManager.initialize(page); diff --git a/packages/tests-e2e/src/connect/models/BaseModel.ts b/packages/tests-e2e/src/connect/models/BaseModel.ts index 4d891e6a9..3f2cbca53 100644 --- a/packages/tests-e2e/src/connect/models/BaseModel.ts +++ b/packages/tests-e2e/src/connect/models/BaseModel.ts @@ -42,16 +42,16 @@ export class BaseModel { this.mfa = new MFAModel(page); } - loadSignup() { - return this.page.goto('/signup'); + loadSignup(port: number) { + return this.page.goto(`http://localhost:${port.toString()}/signup`); } - loadLogin() { - return this.page.goto('/login'); + loadLogin(port: number) { + return this.page.goto(`http://localhost:${port.toString()}/login`); } - loadHome() { - return this.page.goto('/home'); + loadHome(port: number) { + return this.page.goto(`http://localhost:${port.toString()}/home`); } expectScreen(screenName: ScreenNames) { diff --git a/packages/tests-e2e/src/connect/models/StorageModel.ts b/packages/tests-e2e/src/connect/models/StorageModel.ts index f28ba1971..3d7d3b511 100644 --- a/packages/tests-e2e/src/connect/models/StorageModel.ts +++ b/packages/tests-e2e/src/connect/models/StorageModel.ts @@ -11,8 +11,8 @@ export class StorageModel { this.page = page; } - async loadInvitationToken() { - await this.page.goto('/login?invitationToken=inv-token-correct'); + async loadInvitationToken(port: number) { + await this.page.goto(`http://localhost:${port.toString()}/login?invitationToken=inv-token-correct`); await expectScreen(this.page, ScreenNames.InitLogin); } diff --git a/packages/tests-e2e/src/connect/scenarios/append.spec.ts b/packages/tests-e2e/src/connect/scenarios/append.spec.ts index 3add83f22..9074f4c2d 100644 --- a/packages/tests-e2e/src/connect/scenarios/append.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/append.spec.ts @@ -1,13 +1,27 @@ +import type { ChildProcess } from 'node:child_process'; + import { expect } from '@playwright/test'; import { test } from '../fixtures/BaseTest'; import { password, ScreenNames } from '../utils/Constants'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; import { loadPasskeyAppend, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; test.describe('append component', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, false); + setupUser(test, () => port, true, false); loadPasskeyAppend(test); test('successful passkey append on login', async ({ model }) => { @@ -31,9 +45,20 @@ test.describe('append component', () => { }); test.describe('skip append component', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, false); + setupUser(test, () => port, true, false); test('Corbado FAPI unavailable', async ({ model }) => { await model.home.logout(); diff --git a/packages/tests-e2e/src/connect/scenarios/hooks.ts b/packages/tests-e2e/src/connect/scenarios/hooks.ts index 90efe88d0..6ddea8aa4 100644 --- a/packages/tests-e2e/src/connect/scenarios/hooks.ts +++ b/packages/tests-e2e/src/connect/scenarios/hooks.ts @@ -57,9 +57,10 @@ export function loadInvitationToken( PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, PlaywrightWorkerArgs & PlaywrightWorkerOptions >, + getPort: () => number, ) { test.beforeEach(async ({ model }) => { - await model.storage.loadInvitationToken(); + await model.storage.loadInvitationToken(getPort()); }); } @@ -68,14 +69,15 @@ export function setupUser( PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, PlaywrightWorkerArgs & PlaywrightWorkerOptions >, + getPort: () => number, invited = true, append = true, ) { test.beforeEach(async ({ model }) => { if (invited) { - await model.storage.loadInvitationToken(); + await model.storage.loadInvitationToken(getPort()); } - await model.loadSignup(); + await model.loadSignup(getPort()); await model.expectScreen(ScreenNames.InitSignup); await model.createUser(invited, append); await model.expectScreen(ScreenNames.Home); diff --git a/packages/tests-e2e/src/connect/scenarios/login.spec.ts b/packages/tests-e2e/src/connect/scenarios/login.spec.ts index 6d647c4a0..a2fad14b8 100644 --- a/packages/tests-e2e/src/connect/scenarios/login.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/login.spec.ts @@ -1,11 +1,25 @@ +import type { ChildProcess } from 'node:child_process'; + import { expect } from '@playwright/test'; import { test } from '../fixtures/BaseTest'; import { ErrorTexts, password, ScreenNames } from '../utils/Constants'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; import { loadInvitationToken, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; test.describe('login component (without invitation token)', () => { - setupUser(test, false); + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + + setupUser(test, () => port, false); test('successful login with credentials', async ({ model }) => { await model.home.logout(); @@ -21,8 +35,19 @@ test.describe('login component (without invitation token)', () => { }); test.describe('login component (with invitation token, without passkeys)', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); - setupUser(test, true, false); + setupUser(test, () => port, true, false); test('successful login with credentials', async ({ model }) => { await model.home.logout(); @@ -44,9 +69,20 @@ test.describe('login component (with invitation token, without passkeys)', () => }); test.describe('login component (with invitation token, with passkeys)', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, true); + setupUser(test, () => port, true, true); test('successful login with passkey', async ({ model }) => { await model.home.logout(); @@ -117,7 +153,7 @@ test.describe('login component (with invitation token, with passkeys)', () => { await model.passkeyList.deletePasskey(0); await model.passkeyList.expectPasskeys(0); - await model.loadHome(); + await model.loadHome(port); await model.expectScreen(ScreenNames.Home); await model.login.submitConditionalUI(async () => { @@ -142,12 +178,23 @@ test.describe('login component (with invitation token, with passkeys)', () => { }); test.describe('login component (without user)', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - loadInvitationToken(test); + loadInvitationToken(test, () => port); test('attempt login with incomplete credentials', async ({ model }) => { - await model.loadLogin(); + await model.loadLogin(port); await model.expectScreen(ScreenNames.InitLogin); await model.login.submitEmail('', false); @@ -155,7 +202,7 @@ test.describe('login component (without user)', () => { }); test('attempt login with unknown credentials', async ({ model }) => { - await model.loadLogin(); + await model.loadLogin(port); await model.expectScreen(ScreenNames.InitLogin); await model.login.submitEmail('integration-test+unknown@corbado.com', false); @@ -165,7 +212,7 @@ test.describe('login component (without user)', () => { test('Corbado FAPI unavailable', async ({ model }) => { await model.blocker.blockCorbadoFAPI(); - await model.loadLogin(); + await model.loadLogin(port); // It seems that the InitLogin page is now cached so that email needs to be submitted before reaching the InitLoginFallback screen. await model.login.submitEmail('integration-test+dummy@corbado.com', false); await model.expectScreen(ScreenNames.InitLoginFallback); @@ -176,7 +223,7 @@ test.describe('login component (without user)', () => { await model.storage.checkInvitationToken(); const processId = await model.storage.getProcessID(); - await model.loadLogin(); + await model.loadLogin(port); await model.expectScreen(ScreenNames.InitLogin); await model.storage.checkInvitationToken(); await model.storage.checkProcessID(processId); @@ -188,7 +235,7 @@ test.describe('login component (without user)', () => { await model.storage.setLoginLifetime(Math.floor(Date.now() / 1000) - 1); await model.storage.deleteInvitationToken(); - await model.loadLogin(); + await model.loadLogin(port); await model.expectScreen(ScreenNames.InitLoginFallback); }); }); diff --git a/packages/tests-e2e/src/connect/scenarios/misc.spec.ts b/packages/tests-e2e/src/connect/scenarios/misc.spec.ts index 36156d7da..e9f56fcf2 100644 --- a/packages/tests-e2e/src/connect/scenarios/misc.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/misc.spec.ts @@ -1,5 +1,8 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../fixtures/BaseTest'; import { ScreenNames, WebhookTypes } from '../utils/Constants'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; import { loadPasskeyAppend, loadPasskeyList, @@ -11,9 +14,20 @@ import { test.describe.serial('webhook tests', () => { test.describe('login component (webhook)', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, true); + setupUser(test, () => port, true, true); setupWebhooks(test, [WebhookTypes.Login]); test('successful login with passkey (+ webhook)', async ({ model }) => { @@ -31,9 +45,20 @@ test.describe.serial('webhook tests', () => { }); test.describe('append component (webhook)', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, false); + setupUser(test, () => port, true, false); loadPasskeyAppend(test); setupWebhooks(test, [WebhookTypes.Create]); @@ -49,9 +74,20 @@ test.describe.serial('webhook tests', () => { }); test.describe('passkey-list component (webhook)', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, false); + setupUser(test, () => port, true, false); loadPasskeyList(test); setupWebhooks(test, [WebhookTypes.Create, WebhookTypes.Delete]); diff --git a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts index 880d0b8a2..1d8165898 100644 --- a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts @@ -1,11 +1,25 @@ +import type { ChildProcess } from 'node:child_process'; + import { expect, test } from '../fixtures/BaseTest'; import { ErrorTexts, ScreenNames } from '../utils/Constants'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; import { loadPasskeyList, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; test.describe('passkey-list component', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, false); + setupUser(test, () => port, true, false); loadPasskeyList(test); test('list, delete, create passkey', async ({ model }) => { @@ -82,9 +96,20 @@ test.describe('passkey-list component', () => { }); test.describe('skip passkey-list component', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, true); + setupUser(test, () => port, true, true); test('Connect Token endpoint unavailable', async ({ model }) => { await model.blocker.blockCorbadoConnectTokenEndpoint(); @@ -106,7 +131,7 @@ test.describe('skip passkey-list component', () => { await model.home.gotoPasskeyList(); await model.expectScreen(ScreenNames.PasskeyList); await model.passkeyList.expectPasskeys(1); - await model.loadHome(); + await model.loadHome(port); await model.expectScreen(ScreenNames.Home); expect(await model.storage.getManageLifetime()).toBeGreaterThan(Math.floor(Date.now() / 1000)); diff --git a/packages/tests-e2e/src/connect/utils/Playground.ts b/packages/tests-e2e/src/connect/utils/Playground.ts new file mode 100644 index 000000000..ddabbc62f --- /dev/null +++ b/packages/tests-e2e/src/connect/utils/Playground.ts @@ -0,0 +1,85 @@ +import type { ChildProcess } from 'node:child_process'; +import { spawn } from 'node:child_process'; + +import getPort from 'get-port'; +import path from 'path'; +import waitPort from 'wait-port'; + +type PlaygroundType = 'connect-next' | 'connect-web-js'; +const PLAYGROUND_TYPE: PlaygroundType = (process.env.PLAYGROUND_TYPE as PlaygroundType) || 'connect-next'; + +export type PlaygroundInfo = { + server: ChildProcess; + port: number; +}; + +function getPlaygroundDir(): string { + switch (PLAYGROUND_TYPE) { + case 'connect-next': + return path.resolve(__dirname, '../../../../../playground/connect-next'); + case 'connect-web-js': + return path.resolve(__dirname, '../../../../../playground/connect-web-js'); + default: + throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); + } +} + +function getPlaygroundArgs(port: number): string[] { + switch (PLAYGROUND_TYPE) { + case 'connect-next': + return ['run', 'build-and-start', '--', '--port', port.toString()]; + case 'connect-web-js': + throw new Error(`Unimplemented: ${PLAYGROUND_TYPE}`); + default: + throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); + } +} + +export async function spawnPlaygroundNew(): Promise { + const port = await getPort(); + + const playgroundDir = getPlaygroundDir(); + const server = spawn('npm', getPlaygroundArgs(port), { + cwd: playgroundDir, + env: { + ...process.env, + }, + stdio: 'ignore', + shell: true, + }); + const ok = await waitPort({ host: 'localhost', port, timeout: 15_000, output: 'silent' }); + if (!ok) { + server.kill(); + throw new Error(`Server never came up on port ${port}`); + } + + return { server, port }; +} + +export function killPlaygroundNew(server: ChildProcess) { + server.kill(); +} + +export default async function installPlaygroundDeps() { + const playgroundDir = getPlaygroundDir(); + + const installProcess = spawn('npm', ['install'], { + cwd: playgroundDir, + stdio: 'inherit', + shell: true, + }); + + await new Promise((resolve, reject) => { + installProcess.on('close', (code: number) => { + if (code === 0) { + console.log(`[Global Setup] Dependencies installed successfully in ${playgroundDir}.`); + resolve(); + } else { + reject(new Error(`[Global Setup] npm install failed in ${playgroundDir} with code ${code}`)); + } + }); + installProcess.on('error', (err: Error) => { + reject(new Error(`[Global Setup] Failed to start npm install process: ${err.message}`)); + }); + }); +} diff --git a/playground/connect-next/package.json b/playground/connect-next/package.json index 50c01549b..dadfe43e8 100644 --- a/playground/connect-next/package.json +++ b/playground/connect-next/package.json @@ -6,6 +6,7 @@ "dev": "next dev", "build": "next build", "start": "next start", + "build-and-start": "next build && next start", "lint": "next lint" }, "dependencies": { From b9c97fbfe04b0a7a57602fc40df801c4df22bfc0 Mon Sep 17 00:00:00 2001 From: aehnh Date: Mon, 16 Jun 2025 14:45:19 +0200 Subject: [PATCH 26/60] rename workflow file --- .../{deploy-playgrounds-and-test.yml => deploy-playgrounds.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{deploy-playgrounds-and-test.yml => deploy-playgrounds.yml} (99%) diff --git a/.github/workflows/deploy-playgrounds-and-test.yml b/.github/workflows/deploy-playgrounds.yml similarity index 99% rename from .github/workflows/deploy-playgrounds-and-test.yml rename to .github/workflows/deploy-playgrounds.yml index 0edd894c1..b365c3b4c 100644 --- a/.github/workflows/deploy-playgrounds-and-test.yml +++ b/.github/workflows/deploy-playgrounds.yml @@ -1,4 +1,4 @@ -name: Deploy Playground Apps to Vercel and Test on React +name: Deploy Playground Apps to Vercel env: VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} From 53025344928762982b64ef1fd0ac815180478d31 Mon Sep 17 00:00:00 2001 From: aehnh Date: Mon, 16 Jun 2025 14:46:17 +0200 Subject: [PATCH 27/60] remove comments --- .github/workflows/deploy-playgrounds.yml | 193 ----------------------- 1 file changed, 193 deletions(-) diff --git a/.github/workflows/deploy-playgrounds.yml b/.github/workflows/deploy-playgrounds.yml index b365c3b4c..0b4d240fa 100644 --- a/.github/workflows/deploy-playgrounds.yml +++ b/.github/workflows/deploy-playgrounds.yml @@ -100,196 +100,3 @@ jobs: env: VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_WEB_JS_SCRIPT_PLAYGROUND }} -# test: -# needs: deploy -# timeout-minutes: 60 -# runs-on: ubuntu-latest -# -# steps: -# - name: Checkout code -# uses: actions/checkout@v4 -# -# - name: Set up Node.js -# uses: actions/setup-node@v4 -# -# - name: Cache node modules -# id: cache-npm -# uses: actions/cache@v3 -# env: -# cache-name: cache-node-modules -# with: -# path: | -# ~/.npm -# ./node_modules -# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-force-1 -# restore-keys: | -# ${{ runner.os }}-build-${{ env.cache-name }}- -# ${{ runner.os }}-build- -# ${{ runner.os }}- -# -# - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} -# name: Install root dependencies -# run: | -# npm i -# npm install lerna -# npm install vercel@33.2.0 -# npm list -# -# - name: Build SDKs -# run: | -# npm run build -# npm run build:bundler:local -# -# - name: Get installed Playwright version -# id: playwright-version -# run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')" -# -# - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} -# name: List the state of node modules -# continue-on-error: true -# run: | -# cd packages/tests-e2e -# npm list -# -# - name: Install dependencies -# run: | -# cd packages/tests-e2e -# npm install -# sudo apt-get update -# sudo apt-get install -y libxml2-utils -# -# - uses: actions/cache@v3 -# id: playwright-cache -# with: -# path: '~/.cache/ms-playwright' -# key: '${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}' -# restore-keys: | -# ${{ runner.os }}-playwright- -# -# - name: Install Playwright's dependencies -# if: steps.playwright-cache.outputs.cache-hit != 'true' -# run: | -# cd packages/tests-e2e -# npx playwright install --with-deps -# -# - name: Create a log stream on AWS CloudWatch -# env: -# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} -# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} -# AWS_REGION: ${{ secrets.AWS_REGION }} -# run: | -# TIMESTAMP=$(date +%s000) -# echo "LOG_STREAM_NAME=javascript-$TIMESTAMP" >> $GITHUB_ENV -# aws logs create-log-stream --log-group-name "test-results-board" --log-stream-name "javascript-$TIMESTAMP" -# -# - name: Run Complete tests for react -# run: | -# cd packages/tests-e2e -# set +e -# npx playwright test --config=playwright.config.complete.ts -# EXIT_CODE=$? -# if [ $EXIT_CODE -ne 0 ]; then -# echo "COMPLETE_REACT_FAILED=true" >> $GITHUB_ENV -# fi -# env: -# PLAYWRIGHT_NUM_CORES: 4 -# PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} -# PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} -# PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} -# PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} -# GITHUB_RUN_ID: ${{ github.run_id }} -# SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} -# GITHUB_BRANCH_NAME: ${{ github.ref_name }} -# continue-on-error: true -# -# - uses: actions/upload-artifact@v4 -# with: -# name: playwright-report-complete-react -# path: packages/tests-e2e/playwright-report/ -# retention-days: 30 -# -# - name: Send logs to AWS CloudWatch -# env: -# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} -# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} -# AWS_REGION: ${{ secrets.AWS_REGION }} -# run: | -# cd packages/tests-e2e -# TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) -# FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) -# SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) -# ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) -# TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) -# PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) -# FAILED=$((FAILURES + ERRORS)) -# TIMESTAMP=$(date +%s000) -# LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"complete\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" -# aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" -# -# - name: Run Connect tests for react -# run: | -# cd packages/tests-e2e -# BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') -# export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.connect-next.playground.corbado.io" -# set +e -# npx playwright test --config=playwright.config.connect.ts -# EXIT_CODE=$? -# if [ $EXIT_CODE -ne 0 ]; then -# echo "CONNECT_REACT_FAILED=true" >> $GITHUB_ENV -# fi -# env: -# PLAYWRIGHT_NUM_CORES: 4 -# BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} -# PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} -# PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} -# GITHUB_RUN_ID: ${{ github.run_id }} -# SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} -# GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} -# continue-on-error: true -# -# - uses: actions/upload-artifact@v4 -# with: -# name: playwright-report-connect-react -# path: packages/tests-e2e/playwright-report/ -# retention-days: 30 -# -# - name: Send logs to AWS CloudWatch -# env: -# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} -# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} -# AWS_REGION: ${{ secrets.AWS_REGION }} -# run: | -# cd packages/tests-e2e -# TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) -# FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) -# SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) -# ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) -# TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) -# PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) -# FAILED=$((FAILURES + ERRORS)) -# TIMESTAMP=$(date +%s000) -# LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"connect\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" -# aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" -# -# - name: Aggregate results -# run: | -# FAILED_STEPS="" -# if [ "${COMPLETE_REACT_FAILED}" = "true" ]; then -# FAILED_STEPS="${FAILED_STEPS} Complete-React" -# fi -# if [ "${COMPLETE_WEBJS_FAILED}" = "true" ]; then -# FAILED_STEPS="${FAILED_STEPS} Complete-WebJs" -# fi -# if [ "${COMPLETE_WEBJSSCRIPT_FAILED}" = "true" ]; then -# FAILED_STEPS="${FAILED_STEPS} Complete-WebJsScript" -# fi -# if [ "${CONNECT_REACT_FAILED}" = "true" ]; then -# FAILED_STEPS="${FAILED_STEPS} Connect-React" -# fi -# -# if [ -n "$FAILED_STEPS" ]; then -# echo "The following test steps have failed: $FAILED_STEPS" -# exit 1 -# else -# echo "All tests passed." -# fi From 61da5997716a95d8f0cddfa30b6e339ca9a3f9db Mon Sep 17 00:00:00 2001 From: aehnh Date: Mon, 16 Jun 2025 14:48:43 +0200 Subject: [PATCH 28/60] remove comments --- .github/workflows/test.yml | 49 -------------------------------------- 1 file changed, 49 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef2d889d4..a06be3be2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -134,51 +134,6 @@ jobs: LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"complete\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" -# - name: Run Connect tests for react -# run: | -# cd packages/tests-e2e -# BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') -# export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.connect-next.playground.corbado.io" -# set +e -# npx playwright test --config=playwright.config.connect.ts -# EXIT_CODE=$? -# if [ $EXIT_CODE -ne 0 ]; then -# echo "CONNECT_REACT_FAILED=true" >> $GITHUB_ENV -# fi -# env: -# PLAYWRIGHT_NUM_CORES: 4 -# BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} -# PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} -# PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} -# GITHUB_RUN_ID: ${{ github.run_id }} -# SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} -# GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} -# continue-on-error: true -# -# - uses: actions/upload-artifact@v4 -# with: -# name: playwright-report-connect-react -# path: packages/tests-e2e/playwright-report/ -# retention-days: 30 -# -# - name: Send logs to AWS CloudWatch -# env: -# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} -# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} -# AWS_REGION: ${{ secrets.AWS_REGION }} -# run: | -# cd packages/tests-e2e -# TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) -# FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) -# SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) -# ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) -# TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) -# PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) -# FAILED=$((FAILURES + ERRORS)) -# TIMESTAMP=$(date +%s000) -# LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"connect\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" -# aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" - - name: Aggregate results run: | FAILED_STEPS="" @@ -191,10 +146,6 @@ jobs: if [ "${COMPLETE_WEBJSSCRIPT_FAILED}" = "true" ]; then FAILED_STEPS="${FAILED_STEPS} Complete-WebJsScript" fi -# if [ "${CONNECT_REACT_FAILED}" = "true" ]; then -# FAILED_STEPS="${FAILED_STEPS} Connect-React" -# fi - if [ -n "$FAILED_STEPS" ]; then echo "The following test steps have failed: $FAILED_STEPS" exit 1 From 7c847655285dd9a83d8a4e0bb6f040f23ee5fcad Mon Sep 17 00:00:00 2001 From: aehnh Date: Mon, 16 Jun 2025 15:04:29 +0200 Subject: [PATCH 29/60] recover connect tests --- .github/workflows/test.yml | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a06be3be2..5d6cea7d5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -134,6 +134,51 @@ jobs: LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"complete\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" + - name: Run Connect tests for react + run: | + cd packages/tests-e2e + BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') + export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.connect-next.playground.corbado.io" + set +e + npx playwright test --config=playwright.config.connect.ts + EXIT_CODE=$? + if [ $EXIT_CODE -ne 0 ]; then + echo "CONNECT_REACT_FAILED=true" >> $GITHUB_ENV + fi + env: + PLAYWRIGHT_NUM_CORES: 4 + BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} + PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} + PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} + GITHUB_RUN_ID: ${{ github.run_id }} + SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} + GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} + continue-on-error: true + + - uses: actions/upload-artifact@v4 + with: + name: playwright-report-connect-react + path: packages/tests-e2e/playwright-report/ + retention-days: 30 + + - name: Send logs to AWS CloudWatch + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION }} + run: | + cd packages/tests-e2e + TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) + FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) + SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) + ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) + TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) + PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) + FAILED=$((FAILURES + ERRORS)) + TIMESTAMP=$(date +%s000) + LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"connect\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" + aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" + - name: Aggregate results run: | FAILED_STEPS="" From 3393866767042027722bece5225c152f74c2e7b3 Mon Sep 17 00:00:00 2001 From: aehnh Date: Mon, 16 Jun 2025 15:15:50 +0200 Subject: [PATCH 30/60] matrix complete and connect tests --- .github/workflows/test.yml | 106 +++++++++---------------------------- 1 file changed, 26 insertions(+), 80 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5d6cea7d5..b77fd4482 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,23 @@ jobs: test: timeout-minutes: 60 runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - testType: complete + configFile: playwright.config.complete.ts + extraEnv: | + PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} + PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} + PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} + PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} + - testType: connect + configFile: playwright.config.connect.ts + extraEnv: | + BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} + PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} + PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} steps: - name: Checkout code @@ -90,78 +107,26 @@ jobs: echo "LOG_STREAM_NAME=javascript-$TIMESTAMP" >> $GITHUB_ENV aws logs create-log-stream --log-group-name "test-results-board" --log-stream-name "javascript-$TIMESTAMP" - - name: Run Complete tests for react - run: | - cd packages/tests-e2e - set +e - npx playwright test --config=playwright.config.complete.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "COMPLETE_REACT_FAILED=true" >> $GITHUB_ENV - fi - env: + - name: Run ${{ matrix.testType }} tests for react + working-directory: packages/tests-e2e + env: | PLAYWRIGHT_NUM_CORES: 4 - PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} - PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} - PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} - PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} GITHUB_RUN_ID: ${{ github.run_id }} SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} GITHUB_BRANCH_NAME: ${{ github.ref_name }} - continue-on-error: true - - - uses: actions/upload-artifact@v4 - with: - name: playwright-report-complete-react - path: packages/tests-e2e/playwright-report/ - retention-days: 30 - - - name: Send logs to AWS CloudWatch - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} + ${{ matrix.extraEnv }} run: | - cd packages/tests-e2e - TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) - FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) - SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) - ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) - TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) - PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) - FAILED=$((FAILURES + ERRORS)) - TIMESTAMP=$(date +%s000) - LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"complete\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" - aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" - - - name: Run Connect tests for react - run: | - cd packages/tests-e2e - BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') - export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.connect-next.playground.corbado.io" - set +e - npx playwright test --config=playwright.config.connect.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "CONNECT_REACT_FAILED=true" >> $GITHUB_ENV - fi - env: - PLAYWRIGHT_NUM_CORES: 4 - BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} - PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} - PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} - GITHUB_RUN_ID: ${{ github.run_id }} - SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} - GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} - continue-on-error: true + npx playwright test --config=${{ matrix.configFile }} - uses: actions/upload-artifact@v4 + if: always() with: - name: playwright-report-connect-react + name: playwright-report-${{ matrix.testType }}-react path: packages/tests-e2e/playwright-report/ retention-days: 30 - name: Send logs to AWS CloudWatch + if: always() env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -176,24 +141,5 @@ jobs: PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) FAILED=$((FAILURES + ERRORS)) TIMESTAMP=$(date +%s000) - LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"connect\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" + LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"${{ matrix.testType }}\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" - - - name: Aggregate results - run: | - FAILED_STEPS="" - if [ "${COMPLETE_REACT_FAILED}" = "true" ]; then - FAILED_STEPS="${FAILED_STEPS} Complete-React" - fi - if [ "${COMPLETE_WEBJS_FAILED}" = "true" ]; then - FAILED_STEPS="${FAILED_STEPS} Complete-WebJs" - fi - if [ "${COMPLETE_WEBJSSCRIPT_FAILED}" = "true" ]; then - FAILED_STEPS="${FAILED_STEPS} Complete-WebJsScript" - fi - if [ -n "$FAILED_STEPS" ]; then - echo "The following test steps have failed: $FAILED_STEPS" - exit 1 - else - echo "All tests passed." - fi From 4893caf93028b4eb0151faf4055a3e4a60e9e3de Mon Sep 17 00:00:00 2001 From: aehnh Date: Mon, 16 Jun 2025 15:20:56 +0200 Subject: [PATCH 31/60] move env vars out of matrix --- .github/workflows/test.yml | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b77fd4482..7d0a5e0be 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,17 +18,22 @@ jobs: include: - testType: complete configFile: playwright.config.complete.ts - extraEnv: | - PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} - PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} - PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} - PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} - testType: connect configFile: playwright.config.connect.ts - extraEnv: | - BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} - PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} - PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} + env: + PLAYWRIGHT_NUM_CORES: 4 + PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} + PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} + PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} + PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} + + BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} + PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} + PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} + + GITHUB_RUN_ID: ${{ github.run_id }} + SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} + GITHUB_BRANCH_NAME: ${{ github.ref_name }} steps: - name: Checkout code @@ -109,12 +114,6 @@ jobs: - name: Run ${{ matrix.testType }} tests for react working-directory: packages/tests-e2e - env: | - PLAYWRIGHT_NUM_CORES: 4 - GITHUB_RUN_ID: ${{ github.run_id }} - SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} - GITHUB_BRANCH_NAME: ${{ github.ref_name }} - ${{ matrix.extraEnv }} run: | npx playwright test --config=${{ matrix.configFile }} From 67a2e814a1518ab51dbce71a1e728058ed54323b Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 18 Jun 2025 10:37:36 +0200 Subject: [PATCH 32/60] configure playground connect secrets for ci e2e test pipeline --- .github/workflows/test.yml | 11 ++++++++++- packages/tests-e2e/.env.complete.ci | 2 +- packages/tests-e2e/.env.complete.example | 2 +- packages/tests-e2e/.env.connect.ci | 2 +- packages/tests-e2e/.env.connect.example | 4 ++-- packages/tests-e2e/src/connect/models/WebhookModel.ts | 8 ++++---- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7d0a5e0be..91035e833 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,9 +27,18 @@ jobs: PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} - BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} + NEXT_PUBLIC_CORBADO_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_CORBADO_PROJECT_ID }} + NEXT_PUBLIC_CORBADO_FRONTEND_API_URL_SUFFIX: ${{ secrets.NEXT_PUBLIC_CORBADO_FRONTEND_API_URL_SUFFIX }} + CORBADO_BACKEND_API_URL: ${{ secrets.CORBADO_BACKEND_API_URL }} + CORBADO_BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} + AWS_COGNITO_USER_POOL_ID: ${{ secrets.AWS_COGNITO_USER_POOL_ID }} + AWS_COGNITO_CLIENT_ID: ${{ secrets.AWS_COGNITO_CLIENT_ID }} + AWS_COGNITO_CLIENT_SECRET: ${{ secrets.AWS_COGNITO_CLIENT_SECRET }} + AWS_REGION: ${{ secrets.AWS_REGION }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} GITHUB_RUN_ID: ${{ github.run_id }} SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} diff --git a/packages/tests-e2e/.env.complete.ci b/packages/tests-e2e/.env.complete.ci index 636344bea..5e5a4cb60 100644 --- a/packages/tests-e2e/.env.complete.ci +++ b/packages/tests-e2e/.env.complete.ci @@ -1,3 +1,3 @@ DEVELOPERPANEL_API_URL=https://console.cloud.corbado-staging.io -BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io +CORBADO_BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io diff --git a/packages/tests-e2e/.env.complete.example b/packages/tests-e2e/.env.complete.example index 7b0d36135..053011364 100644 --- a/packages/tests-e2e/.env.complete.example +++ b/packages/tests-e2e/.env.complete.example @@ -1,7 +1,7 @@ # save to .env.local PLAYWRIGHT_TEST_URL=http://localhost:3000 DEVELOPERPANEL_API_URL=https://console.cloud.corbado-staging.io -BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io +CORBADO_BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io FRONTEND_API_URL_SUFFIX= PLAYWRIGHT_GOOGLE_EMAIL= PLAYWRIGHT_GOOGLE_PASSWORD= diff --git a/packages/tests-e2e/.env.connect.ci b/packages/tests-e2e/.env.connect.ci index 136997e7b..6aff2d306 100644 --- a/packages/tests-e2e/.env.connect.ci +++ b/packages/tests-e2e/.env.connect.ci @@ -1,3 +1,3 @@ -BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io +CORBADO_BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io #DEVELOPERPANEL_API_URL=https://console.cloud.corbado-staging.io #FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io diff --git a/packages/tests-e2e/.env.connect.example b/packages/tests-e2e/.env.connect.example index 7d08eec39..8eb902121 100644 --- a/packages/tests-e2e/.env.connect.example +++ b/packages/tests-e2e/.env.connect.example @@ -1,6 +1,6 @@ # save to .env.local PLAYWRIGHT_TEST_URL=https://develop.connect-next.playground.corbado.io DEVELOPERPANEL_API_URL=https://console.cloud.corbado-staging.io -BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io -BACKEND_API_BASIC_AUTH= +CORBADO_BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io +CORBADO_BACKEND_API_BASIC_AUTH= PLAYWRIGHT_NGROK_AUTH_TOKEN= diff --git a/packages/tests-e2e/src/connect/models/WebhookModel.ts b/packages/tests-e2e/src/connect/models/WebhookModel.ts index 51dd6d274..0e569bdf1 100644 --- a/packages/tests-e2e/src/connect/models/WebhookModel.ts +++ b/packages/tests-e2e/src/connect/models/WebhookModel.ts @@ -55,10 +55,10 @@ export class WebhookModel { addr: port, authtoken: process.env.PLAYWRIGHT_NGROK_AUTH_TOKEN, }); - const createRes = await fetch(`${process.env.BACKEND_API_URL}/v2/webhookEndpoints`, { + const createRes = await fetch(`${process.env.CORBADO_BACKEND_API_URL}/v2/webhookEndpoints`, { method: 'POST', headers: { - Authorization: `Basic ${process.env.BACKEND_API_BASIC_AUTH}`, + Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ @@ -87,10 +87,10 @@ export class WebhookModel { throw new Error('PLAYWRIGHT_CONNECT_PROJECT_ID not set'); } - const deleteRes = await fetch(`${process.env.BACKEND_API_URL}/v2/webhookEndpoints/${this.webhookEndpointID}`, { + const deleteRes = await fetch(`${process.env.CORBADO_BACKEND_API_URL}/v2/webhookEndpoints/${this.webhookEndpointID}`, { method: 'DELETE', headers: { - Authorization: `Basic ${process.env.BACKEND_API_BASIC_AUTH}`, + Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, 'Content-Type': 'application/json', }, }); From 878da3108e5ef62dc30b5fa62c645f62488101e6 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 18 Jun 2025 10:49:18 +0200 Subject: [PATCH 33/60] remove outdated workflow --- .github/workflows/deploy-playground-and-test.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .github/workflows/deploy-playground-and-test.yml diff --git a/.github/workflows/deploy-playground-and-test.yml b/.github/workflows/deploy-playground-and-test.yml deleted file mode 100644 index e69de29bb..000000000 From ebcf3121cf2f638f598e9ad67669da185b8ef1d9 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 18 Jun 2025 10:53:23 +0200 Subject: [PATCH 34/60] prettier --- .../tests-e2e/src/connect/models/WebhookModel.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/tests-e2e/src/connect/models/WebhookModel.ts b/packages/tests-e2e/src/connect/models/WebhookModel.ts index 0e569bdf1..5be3fab0c 100644 --- a/packages/tests-e2e/src/connect/models/WebhookModel.ts +++ b/packages/tests-e2e/src/connect/models/WebhookModel.ts @@ -87,13 +87,16 @@ export class WebhookModel { throw new Error('PLAYWRIGHT_CONNECT_PROJECT_ID not set'); } - const deleteRes = await fetch(`${process.env.CORBADO_BACKEND_API_URL}/v2/webhookEndpoints/${this.webhookEndpointID}`, { - method: 'DELETE', - headers: { - Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, - 'Content-Type': 'application/json', + const deleteRes = await fetch( + `${process.env.CORBADO_BACKEND_API_URL}/v2/webhookEndpoints/${this.webhookEndpointID}`, + { + method: 'DELETE', + headers: { + Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, + 'Content-Type': 'application/json', + }, }, - }); + ); expect(deleteRes.ok).toBeTruthy(); this.webhookServer.close(); From e48f846b957f760aad83a9c3e3078bb22f080ad4 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 18 Jun 2025 11:06:48 +0200 Subject: [PATCH 35/60] debug logs for playground in playwright --- packages/tests-e2e/src/connect/utils/Playground.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tests-e2e/src/connect/utils/Playground.ts b/packages/tests-e2e/src/connect/utils/Playground.ts index ddabbc62f..676273f8a 100644 --- a/packages/tests-e2e/src/connect/utils/Playground.ts +++ b/packages/tests-e2e/src/connect/utils/Playground.ts @@ -44,7 +44,7 @@ export async function spawnPlaygroundNew(): Promise { env: { ...process.env, }, - stdio: 'ignore', + stdio: 'inherit', shell: true, }); const ok = await waitPort({ host: 'localhost', port, timeout: 15_000, output: 'silent' }); From dd1b19395fb2488750cb2086e5e7df6234edee43 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 18 Jun 2025 11:15:22 +0200 Subject: [PATCH 36/60] install connect playground deps --- packages/tests-e2e/playwright.config.connect.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tests-e2e/playwright.config.connect.ts b/packages/tests-e2e/playwright.config.connect.ts index 5c233bcb4..1bc0d6029 100644 --- a/packages/tests-e2e/playwright.config.connect.ts +++ b/packages/tests-e2e/playwright.config.connect.ts @@ -71,4 +71,5 @@ export default defineConfig({ testMatch: ['scenarios/misc.spec.ts'], }, ], + globalSetup: 'src/connect/utils/Playground.ts', }); From 211ed3d60ed9430927f6ca93634f94e6bc45e08d Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 18 Jun 2025 11:30:35 +0200 Subject: [PATCH 37/60] disable next cache for concurrent connect playground builds --- packages/tests-e2e/src/connect/utils/Playground.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tests-e2e/src/connect/utils/Playground.ts b/packages/tests-e2e/src/connect/utils/Playground.ts index 676273f8a..399d62afa 100644 --- a/packages/tests-e2e/src/connect/utils/Playground.ts +++ b/packages/tests-e2e/src/connect/utils/Playground.ts @@ -43,6 +43,7 @@ export async function spawnPlaygroundNew(): Promise { cwd: playgroundDir, env: { ...process.env, + NEXT_PRIVATE_DISABLE_CACHE: '1', }, stdio: 'inherit', shell: true, From ec2bfc9d3b98c83fa2719959ff0a70ee09088c62 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 18 Jun 2025 11:41:11 +0200 Subject: [PATCH 38/60] build on global setup --- .../tests-e2e/src/connect/utils/Playground.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/tests-e2e/src/connect/utils/Playground.ts b/packages/tests-e2e/src/connect/utils/Playground.ts index 399d62afa..41ed386d4 100644 --- a/packages/tests-e2e/src/connect/utils/Playground.ts +++ b/packages/tests-e2e/src/connect/utils/Playground.ts @@ -27,7 +27,7 @@ function getPlaygroundDir(): string { function getPlaygroundArgs(port: number): string[] { switch (PLAYGROUND_TYPE) { case 'connect-next': - return ['run', 'build-and-start', '--', '--port', port.toString()]; + return ['run', 'start', '--', '--port', port.toString()]; case 'connect-web-js': throw new Error(`Unimplemented: ${PLAYGROUND_TYPE}`); default: @@ -74,7 +74,22 @@ export default async function installPlaygroundDeps() { installProcess.on('close', (code: number) => { if (code === 0) { console.log(`[Global Setup] Dependencies installed successfully in ${playgroundDir}.`); - resolve(); + const buildProcess = spawn('npm', ['run', 'build'], { + cwd: playgroundDir, + stdio: 'inherit', + shell: true, + }); + buildProcess.on('close', (buildCode: number) => { + if (buildCode === 0) { + console.log(`[Global Setup] Playground built successfully in ${playgroundDir}.`); + resolve(); + } else { + reject(new Error(`[Global Setup] npm run build-and-preview failed in ${playgroundDir} with code ${buildCode}`)); + } + }); + buildProcess.on('error', (err: Error) => { + reject(new Error(`[Global Setup] Failed to start build process: ${err.message}`)); + }); } else { reject(new Error(`[Global Setup] npm install failed in ${playgroundDir} with code ${code}`)); } From 76d85dbc35fe8602e2e2c15d80bfb62c79795b26 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 18 Jun 2025 11:41:31 +0200 Subject: [PATCH 39/60] rever cache disable --- packages/tests-e2e/src/connect/utils/Playground.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/tests-e2e/src/connect/utils/Playground.ts b/packages/tests-e2e/src/connect/utils/Playground.ts index 41ed386d4..e16118810 100644 --- a/packages/tests-e2e/src/connect/utils/Playground.ts +++ b/packages/tests-e2e/src/connect/utils/Playground.ts @@ -43,7 +43,6 @@ export async function spawnPlaygroundNew(): Promise { cwd: playgroundDir, env: { ...process.env, - NEXT_PRIVATE_DISABLE_CACHE: '1', }, stdio: 'inherit', shell: true, From 903391a2a4c15fd7dfc419a84277a8964a8a7034 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 18 Jun 2025 12:40:05 +0200 Subject: [PATCH 40/60] fix github secret env vars --- .github/workflows/test.yml | 4 ++-- packages/tests-e2e/src/connect/utils/Playground.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 91035e833..85466e450 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,8 +37,8 @@ jobs: AWS_COGNITO_CLIENT_ID: ${{ secrets.AWS_COGNITO_CLIENT_ID }} AWS_COGNITO_CLIENT_SECRET: ${{ secrets.AWS_COGNITO_CLIENT_SECRET }} AWS_REGION: ${{ secrets.AWS_REGION }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_CONNECT_PLAYGROUND }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_CONNECT_PLAYGROUND }} GITHUB_RUN_ID: ${{ github.run_id }} SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} diff --git a/packages/tests-e2e/src/connect/utils/Playground.ts b/packages/tests-e2e/src/connect/utils/Playground.ts index e16118810..5c7e588d9 100644 --- a/packages/tests-e2e/src/connect/utils/Playground.ts +++ b/packages/tests-e2e/src/connect/utils/Playground.ts @@ -83,7 +83,9 @@ export default async function installPlaygroundDeps() { console.log(`[Global Setup] Playground built successfully in ${playgroundDir}.`); resolve(); } else { - reject(new Error(`[Global Setup] npm run build-and-preview failed in ${playgroundDir} with code ${buildCode}`)); + reject( + new Error(`[Global Setup] npm run build-and-preview failed in ${playgroundDir} with code ${buildCode}`), + ); } }); buildProcess.on('error', (err: Error) => { From 18ee133ea8e7a27f32dd9285f20304ea86cce353 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 18 Jun 2025 14:08:28 +0200 Subject: [PATCH 41/60] change useragent --- packages/tests-e2e/playwright.config.connect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tests-e2e/playwright.config.connect.ts b/packages/tests-e2e/playwright.config.connect.ts index 1bc0d6029..fd2662c30 100644 --- a/packages/tests-e2e/playwright.config.connect.ts +++ b/packages/tests-e2e/playwright.config.connect.ts @@ -45,7 +45,7 @@ export default defineConfig({ }, use: { userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 15.3.2) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/129.0.6668.29 Safari/537.36', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 15.3.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36', actionTimeout: operationTimeout, // default: none navigationTimeout: operationTimeout, // default: none // baseURL: process.env.PLAYWRIGHT_TEST_URL, From 757805717efa28e4e4a95b144949470f9c5e21b9 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 18 Jun 2025 14:41:04 +0200 Subject: [PATCH 42/60] use proper github secret name --- .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 85466e450..cdc605270 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: NEXT_PUBLIC_CORBADO_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_CORBADO_PROJECT_ID }} NEXT_PUBLIC_CORBADO_FRONTEND_API_URL_SUFFIX: ${{ secrets.NEXT_PUBLIC_CORBADO_FRONTEND_API_URL_SUFFIX }} CORBADO_BACKEND_API_URL: ${{ secrets.CORBADO_BACKEND_API_URL }} - CORBADO_BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} + CORBADO_BACKEND_API_BASIC_AUTH: ${{ secrets.CORBADO_BACKEND_API_BASIC_AUTH }} PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} AWS_COGNITO_USER_POOL_ID: ${{ secrets.AWS_COGNITO_USER_POOL_ID }} From 04238cbe3c6f0de906f334976466581a937d7cd0 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 18 Jun 2025 15:37:54 +0200 Subject: [PATCH 43/60] ignore playground app logs in playwright --- packages/tests-e2e/src/connect/utils/Playground.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tests-e2e/src/connect/utils/Playground.ts b/packages/tests-e2e/src/connect/utils/Playground.ts index 5c7e588d9..90a2a2f1d 100644 --- a/packages/tests-e2e/src/connect/utils/Playground.ts +++ b/packages/tests-e2e/src/connect/utils/Playground.ts @@ -44,7 +44,7 @@ export async function spawnPlaygroundNew(): Promise { env: { ...process.env, }, - stdio: 'inherit', + stdio: 'ignore', shell: true, }); const ok = await waitPort({ host: 'localhost', port, timeout: 15_000, output: 'silent' }); From cfa4e86fbc293c1db0fa85f8c31b9d763905b258 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 24 Jun 2025 11:22:54 +0200 Subject: [PATCH 44/60] build playground on playwright global setup instead of building before every test group --- .../src/complete/utils/playground.ts | 47 ++++++++++++++++--- .../tests-e2e/src/connect/utils/Playground.ts | 2 +- playground/react/package.json | 1 - playground/web-js-script/package.json | 2 +- playground/web-js/package.json | 2 +- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/packages/tests-e2e/src/complete/utils/playground.ts b/packages/tests-e2e/src/complete/utils/playground.ts index b5f93aecd..482f1b2a2 100644 --- a/packages/tests-e2e/src/complete/utils/playground.ts +++ b/packages/tests-e2e/src/complete/utils/playground.ts @@ -21,14 +21,27 @@ function getPlaygroundDir(): string { } } -function getPlaygroundArgs(port: number): string[] { +function getPlaygroundBuildArgs(): string[] | null { switch (PLAYGROUND_TYPE) { case 'react': - return ['run', 'build-and-preview', '--', '--port', port.toString()]; + return ['run', 'build']; case 'web-js': - return ['run', 'build-and-preview', '--', '-l', port.toString()]; + return ['run', 'build']; case 'web-js-script': - return ['run', 'build-and-preview', '--', '-l', port.toString()]; + return null; + default: + throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); + } +} + +function getPlaygroundStartArgs(port: number): string[] { + switch (PLAYGROUND_TYPE) { + case 'react': + return ['run', 'preview', '--', '--port', port.toString()]; + case 'web-js': + return ['run', 'serve', '--', '-l', port.toString()]; + case 'web-js-script': + return ['run', 'serve', '--', '-l', port.toString()]; default: throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); } @@ -41,7 +54,7 @@ export async function spawnPlaygroundNew(projectId: string): Promise<{ const port = await getPort(); const playgroundDir = getPlaygroundDir(); - const server = spawn('npm', getPlaygroundArgs(port), { + const server = spawn('npm', getPlaygroundStartArgs(port), { cwd: playgroundDir, env: { ...process.env, @@ -76,7 +89,29 @@ export default async function installPlaygroundDeps() { installProcess.on('close', (code: number) => { if (code === 0) { console.log(`[Global Setup] Dependencies installed successfully in ${playgroundDir}.`); - resolve(); + const buildCommand = getPlaygroundBuildArgs(); + if (!buildCommand) { + console.log(`[Global Setup] No build step required for ${PLAYGROUND_TYPE}.`); + return resolve(); + } + const buildProcess = spawn('npm', buildCommand, { + cwd: playgroundDir, + stdio: 'inherit', + shell: true, + }); + buildProcess.on('close', (buildCode: number) => { + if (buildCode === 0) { + console.log(`[Global Setup] Playground built successfully in ${playgroundDir}.`); + resolve(); + } else { + reject( + new Error(`[Global Setup] npm run build failed in ${playgroundDir} with code ${buildCode}`), + ); + } + }); + buildProcess.on('error', (err: Error) => { + reject(new Error(`[Global Setup] Failed to start build process: ${err.message}`)); + }); } else { reject(new Error(`[Global Setup] npm install failed in ${playgroundDir} with code ${code}`)); } diff --git a/packages/tests-e2e/src/connect/utils/Playground.ts b/packages/tests-e2e/src/connect/utils/Playground.ts index 90a2a2f1d..74e3d7e8b 100644 --- a/packages/tests-e2e/src/connect/utils/Playground.ts +++ b/packages/tests-e2e/src/connect/utils/Playground.ts @@ -84,7 +84,7 @@ export default async function installPlaygroundDeps() { resolve(); } else { reject( - new Error(`[Global Setup] npm run build-and-preview failed in ${playgroundDir} with code ${buildCode}`), + new Error(`[Global Setup] npm run build failed in ${playgroundDir} with code ${buildCode}`), ); } }); diff --git a/playground/react/package.json b/playground/react/package.json index f6c95968f..4c887dd85 100644 --- a/playground/react/package.json +++ b/playground/react/package.json @@ -8,7 +8,6 @@ "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", - "build-and-preview": "tsc && vite build && vite preview", "test": "vitest", "test:coverage": "vitest run --coverage" }, diff --git a/playground/web-js-script/package.json b/playground/web-js-script/package.json index ece48ddfa..9a5bce3b8 100644 --- a/playground/web-js-script/package.json +++ b/playground/web-js-script/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "build-and-preview": "npx serve --single" + "serve": "npx serve --single" }, "keywords": [], "author": "", diff --git a/playground/web-js/package.json b/playground/web-js/package.json index a900197a0..0b0c42464 100644 --- a/playground/web-js/package.json +++ b/playground/web-js/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "cross-env NODE_ENV=local webpack-dev-server --open --mode development", "build": "cross-env NODE_ENV=vercel webpack --mode production", - "build-and-preview": "cross-env NODE_ENV=vercel webpack --mode production && npx serve dist --single" + "serve": "npx serve dist --single" }, "keywords": [], "author": "", From 76bf296aa89c48ecd51b437a9abd281b775e7620 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 24 Jun 2025 11:31:37 +0200 Subject: [PATCH 45/60] use correct configs for nightly connect tests --- .github/workflows/test-all.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 74c418f23..a4d16c1fe 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -235,12 +235,18 @@ jobs: fi env: PLAYWRIGHT_NUM_CORES: 4 - BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} + NEXT_PUBLIC_CORBADO_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_CORBADO_PROJECT_ID }} + NEXT_PUBLIC_CORBADO_FRONTEND_API_URL_SUFFIX: ${{ secrets.NEXT_PUBLIC_CORBADO_FRONTEND_API_URL_SUFFIX }} + CORBADO_BACKEND_API_URL: ${{ secrets.CORBADO_BACKEND_API_URL }} + CORBADO_BACKEND_API_BASIC_AUTH: ${{ secrets.CORBADO_BACKEND_API_BASIC_AUTH }} PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} - GITHUB_RUN_ID: ${{ github.run_id }} - SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} - GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} + AWS_COGNITO_USER_POOL_ID: ${{ secrets.AWS_COGNITO_USER_POOL_ID }} + AWS_COGNITO_CLIENT_ID: ${{ secrets.AWS_COGNITO_CLIENT_ID }} + AWS_COGNITO_CLIENT_SECRET: ${{ secrets.AWS_COGNITO_CLIENT_SECRET }} + AWS_REGION: ${{ secrets.AWS_REGION }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_CONNECT_PLAYGROUND }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_CONNECT_PLAYGROUND }} continue-on-error: true - uses: actions/upload-artifact@v4 From ac06d736787be7e4f2facd558e613868cf68e507 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 24 Jun 2025 11:32:33 +0200 Subject: [PATCH 46/60] prettier --- packages/tests-e2e/src/complete/utils/playground.ts | 4 +--- packages/tests-e2e/src/connect/utils/Playground.ts | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/tests-e2e/src/complete/utils/playground.ts b/packages/tests-e2e/src/complete/utils/playground.ts index 482f1b2a2..a2423b914 100644 --- a/packages/tests-e2e/src/complete/utils/playground.ts +++ b/packages/tests-e2e/src/complete/utils/playground.ts @@ -104,9 +104,7 @@ export default async function installPlaygroundDeps() { console.log(`[Global Setup] Playground built successfully in ${playgroundDir}.`); resolve(); } else { - reject( - new Error(`[Global Setup] npm run build failed in ${playgroundDir} with code ${buildCode}`), - ); + reject(new Error(`[Global Setup] npm run build failed in ${playgroundDir} with code ${buildCode}`)); } }); buildProcess.on('error', (err: Error) => { diff --git a/packages/tests-e2e/src/connect/utils/Playground.ts b/packages/tests-e2e/src/connect/utils/Playground.ts index 74e3d7e8b..a2d526421 100644 --- a/packages/tests-e2e/src/connect/utils/Playground.ts +++ b/packages/tests-e2e/src/connect/utils/Playground.ts @@ -83,9 +83,7 @@ export default async function installPlaygroundDeps() { console.log(`[Global Setup] Playground built successfully in ${playgroundDir}.`); resolve(); } else { - reject( - new Error(`[Global Setup] npm run build failed in ${playgroundDir} with code ${buildCode}`), - ); + reject(new Error(`[Global Setup] npm run build failed in ${playgroundDir} with code ${buildCode}`)); } }); buildProcess.on('error', (err: Error) => { From 23afd9e0b26e663c8d42ee795566508b41662b52 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 24 Jun 2025 11:35:29 +0200 Subject: [PATCH 47/60] remove unused command --- playground/connect-next/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/playground/connect-next/package.json b/playground/connect-next/package.json index dadfe43e8..50c01549b 100644 --- a/playground/connect-next/package.json +++ b/playground/connect-next/package.json @@ -6,7 +6,6 @@ "dev": "next dev", "build": "next build", "start": "next start", - "build-and-start": "next build && next start", "lint": "next lint" }, "dependencies": { From a0547d2b8016d44aa2774262d5ca05a8cf0445ba Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 24 Jun 2025 16:39:16 +0200 Subject: [PATCH 48/60] change remaining vercel URLs to localhost --- .../src/connect/scenarios/passkey-list.spec.ts | 6 +++--- .../src/connect/utils/NetworkRequestBlocker.ts | 4 ++-- .../src/connect/utils/VirtualAuthenticator.ts | 2 +- playground/connect-next/app/login/actions.ts | 18 +++++++++++++++++- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts index 1d8165898..5d39061c8 100644 --- a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts @@ -39,7 +39,7 @@ test.describe('passkey-list component', () => { test('Connect Token endpoint unavailable during passkey creation', async ({ model }) => { await model.passkeyList.expectPasskeys(0); - await model.blocker.blockCorbadoConnectTokenEndpoint(); + await model.blocker.blockCorbadoConnectTokenEndpoint(port); await model.page.getByRole('button', { name: 'Add a passkey' }).click(); await model.expectError(ErrorTexts.PasskeyCreateFail); @@ -75,7 +75,7 @@ test.describe('passkey-list component', () => { await model.passkeyList.createPasskey(true); await model.passkeyList.expectPasskeys(1); - await model.blocker.blockCorbadoConnectTokenEndpoint(); + await model.blocker.blockCorbadoConnectTokenEndpoint(port); await model.passkeyList.deletePasskey(0); await model.expectError(ErrorTexts.PasskeyDeleteFail); @@ -112,7 +112,7 @@ test.describe('skip passkey-list component', () => { setupUser(test, () => port, true, true); test('Connect Token endpoint unavailable', async ({ model }) => { - await model.blocker.blockCorbadoConnectTokenEndpoint(); + await model.blocker.blockCorbadoConnectTokenEndpoint(port); await model.home.gotoPasskeyList(); await model.expectScreen(ScreenNames.PasskeyList); diff --git a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts index c780a351f..612c405b1 100644 --- a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts +++ b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts @@ -25,10 +25,10 @@ export class NetworkRequestBlocker { }); } - blockCorbadoConnectTokenEndpoint() { + blockCorbadoConnectTokenEndpoint(port: number) { // This is sufficient, as the connectTokens endpoint is called from /passkey-list handler return this.#cdpClient.send('Network.setBlockedURLs', { - urls: ['*.playground.corbado.io/passkey-list'], + urls: [`localhost:${port.toString()}/passkey-list`], }); } } diff --git a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts index dea81b8a4..16c6a8b7d 100644 --- a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts +++ b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts @@ -84,7 +84,7 @@ export class VirtualAuthenticator { credential: { credentialId: '', // 'WZuSfPDeCfXUMqO3vcVZ6ZYY0w2W4NpLcLzTjMl4qns=', isResidentCredential: true, - rpId: 'connect-next.playground.corbado.io', + rpId: 'localhost', privateKey: 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgz/eSahk8R0fk3Jjpcbd1LPc2gGKyzEG23UFIbFTqSbyhRANCAAQ4a8dJ559cf0cZcg0U7k5oCofmtOzuqXDSwzP8LLhv0InronrySiaWAGuWFpVsbNyOnWSd6VZJU8wiFKSMiDWN', userHandle: '', // 'TDBlaFVpNnRNQg==', diff --git a/playground/connect-next/app/login/actions.ts b/playground/connect-next/app/login/actions.ts index 9ccb62476..8acfee5b1 100644 --- a/playground/connect-next/app/login/actions.ts +++ b/playground/connect-next/app/login/actions.ts @@ -50,6 +50,13 @@ export async function postPasskeyLoginNew(signedPasskeyData: string, clientState signedPasskeyData: signedPasskeyData, }); + console.log('url: ', url); + console.log('auth: ', `${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`); + + console.log('Calling postPasskeyLoginNew with:'); + console.log('signedPasskeyData:', signedPasskeyData); + console.log('clientState:', clientState); + const response = await fetch(url, { method: 'POST', headers: { @@ -60,7 +67,16 @@ export async function postPasskeyLoginNew(signedPasskeyData: string, clientState body: body, }); - const out = await response.json(); + const clonedResponse = response.clone(); + let out: any; + try { + out = await response.json(); + } catch (err) { + const text = await clonedResponse.text(); // Get raw HTML or empty string + console.error('Failed to parse JSON. Raw response:', text.slice(0, 300)); + throw new Error('Invalid JSON response from backend'); + } + console.log(out); await postPasskeyLogin(out.session); From e67adf246986ca070aed1618c37331eeed5f6347 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 24 Jun 2025 17:02:24 +0200 Subject: [PATCH 49/60] abstract playground url out as env var --- .github/workflows/test-all.yml | 4 ++++ .github/workflows/test.yml | 2 ++ packages/tests-e2e/.env.complete.example | 2 +- packages/tests-e2e/.env.connect.example | 2 +- packages/tests-e2e/playwright.config.complete.ts | 6 ------ packages/tests-e2e/src/complete/models/CorbadoAuthModel.ts | 2 +- packages/tests-e2e/src/complete/models/PasskeyListModel.ts | 2 +- packages/tests-e2e/src/connect/models/BaseModel.ts | 6 +++--- packages/tests-e2e/src/connect/models/StorageModel.ts | 2 +- packages/tests-e2e/src/connect/models/WebhookModel.ts | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index a4d16c1fe..18e94d0c9 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -101,6 +101,7 @@ jobs: fi env: PLAYWRIGHT_NUM_CORES: 4 + PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} @@ -145,6 +146,7 @@ jobs: fi env: PLAYWRIGHT_NUM_CORES: 4 + PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} @@ -189,6 +191,7 @@ jobs: fi env: PLAYWRIGHT_NUM_CORES: 4 + PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} @@ -235,6 +238,7 @@ jobs: fi env: PLAYWRIGHT_NUM_CORES: 4 + PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} NEXT_PUBLIC_CORBADO_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_CORBADO_PROJECT_ID }} NEXT_PUBLIC_CORBADO_FRONTEND_API_URL_SUFFIX: ${{ secrets.NEXT_PUBLIC_CORBADO_FRONTEND_API_URL_SUFFIX }} CORBADO_BACKEND_API_URL: ${{ secrets.CORBADO_BACKEND_API_URL }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cdc605270..b2acdd831 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,8 @@ jobs: configFile: playwright.config.connect.ts env: PLAYWRIGHT_NUM_CORES: 4 + PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} + PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} diff --git a/packages/tests-e2e/.env.complete.example b/packages/tests-e2e/.env.complete.example index 053011364..2a5850d4e 100644 --- a/packages/tests-e2e/.env.complete.example +++ b/packages/tests-e2e/.env.complete.example @@ -1,5 +1,5 @@ # save to .env.local -PLAYWRIGHT_TEST_URL=http://localhost:3000 +PLAYWRIGHT_TEST_URL=http://localhost DEVELOPERPANEL_API_URL=https://console.cloud.corbado-staging.io CORBADO_BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io FRONTEND_API_URL_SUFFIX= diff --git a/packages/tests-e2e/.env.connect.example b/packages/tests-e2e/.env.connect.example index 8eb902121..6d922847d 100644 --- a/packages/tests-e2e/.env.connect.example +++ b/packages/tests-e2e/.env.connect.example @@ -1,5 +1,5 @@ # save to .env.local -PLAYWRIGHT_TEST_URL=https://develop.connect-next.playground.corbado.io +PLAYWRIGHT_TEST_URL=http://localhost DEVELOPERPANEL_API_URL=https://console.cloud.corbado-staging.io CORBADO_BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io CORBADO_BACKEND_API_BASIC_AUTH= diff --git a/packages/tests-e2e/playwright.config.complete.ts b/packages/tests-e2e/playwright.config.complete.ts index e6803c41e..8364eaaf6 100644 --- a/packages/tests-e2e/playwright.config.complete.ts +++ b/packages/tests-e2e/playwright.config.complete.ts @@ -93,11 +93,5 @@ export default defineConfig({ testMatch: ['scenarios/passkey-list-general/*.ts'], }, ], - // webServer: { - // command: webServerCommand, - // url: 'http://localhost:4173', - // reuseExistingServer: !process.env.CI, - // timeout: 15 * 1000, - // }, globalSetup: 'src/complete/utils/playground.ts', }); diff --git a/packages/tests-e2e/src/complete/models/CorbadoAuthModel.ts b/packages/tests-e2e/src/complete/models/CorbadoAuthModel.ts index 479953f21..d5e4a6a15 100644 --- a/packages/tests-e2e/src/complete/models/CorbadoAuthModel.ts +++ b/packages/tests-e2e/src/complete/models/CorbadoAuthModel.ts @@ -37,7 +37,7 @@ export class CorbadoAuthModel { async load(projectId: string, port: number, passkeySupport?: boolean, hashCode?: string) { this.projectId = projectId; - let url = `http://localhost:${port.toString()}/${this.projectId}/auth`; + let url = `${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/${this.projectId}/auth`; if (hashCode) { url += `#${hashCode}`; } diff --git a/packages/tests-e2e/src/complete/models/PasskeyListModel.ts b/packages/tests-e2e/src/complete/models/PasskeyListModel.ts index 1fc9dbed5..3fc560b36 100644 --- a/packages/tests-e2e/src/complete/models/PasskeyListModel.ts +++ b/packages/tests-e2e/src/complete/models/PasskeyListModel.ts @@ -28,7 +28,7 @@ export class PasskeyListModel { await this.virtualAuthenticator.addWebAuthn(passkeySupport); } - const url = `http://localhost:${port.toString()}/${this.projectId}/auth#signup-init`; + const url = `${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/${this.projectId}/auth#signup-init`; await this.page.goto(url); await this.page.waitForSelector('.cb-container-body'); diff --git a/packages/tests-e2e/src/connect/models/BaseModel.ts b/packages/tests-e2e/src/connect/models/BaseModel.ts index 390221be6..db6920381 100644 --- a/packages/tests-e2e/src/connect/models/BaseModel.ts +++ b/packages/tests-e2e/src/connect/models/BaseModel.ts @@ -43,15 +43,15 @@ export class BaseModel { } loadSignup(port: number) { - return this.page.goto(`http://localhost:${port.toString()}/signup`); + return this.page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/signup`); } loadLogin(port: number) { - return this.page.goto(`http://localhost:${port.toString()}/login`); + return this.page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login`); } loadHome(port: number) { - return this.page.goto(`http://localhost:${port.toString()}/home`); + return this.page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/home`); } expectScreen(screenName: ScreenNames) { diff --git a/packages/tests-e2e/src/connect/models/StorageModel.ts b/packages/tests-e2e/src/connect/models/StorageModel.ts index 3d7d3b511..5f1097e27 100644 --- a/packages/tests-e2e/src/connect/models/StorageModel.ts +++ b/packages/tests-e2e/src/connect/models/StorageModel.ts @@ -12,7 +12,7 @@ export class StorageModel { } async loadInvitationToken(port: number) { - await this.page.goto(`http://localhost:${port.toString()}/login?invitationToken=inv-token-correct`); + await this.page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); await expectScreen(this.page, ScreenNames.InitLogin); } diff --git a/packages/tests-e2e/src/connect/models/WebhookModel.ts b/packages/tests-e2e/src/connect/models/WebhookModel.ts index 5be3fab0c..8c99f12a9 100644 --- a/packages/tests-e2e/src/connect/models/WebhookModel.ts +++ b/packages/tests-e2e/src/connect/models/WebhookModel.ts @@ -46,7 +46,7 @@ export class WebhookModel { } }); this.webhookServer.listen(port, () => { - console.log(`Webhook server running at http://localhost:${port}`); + console.log(`Webhook server running at ${process.env.PLAYWRIGHT_TEST_URL}:${port}`); resolve(this.webhookServer); }); }); From 87fa6b711414f78829ab4bba5c61bfe110b26797 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 24 Jun 2025 17:04:28 +0200 Subject: [PATCH 50/60] prettier --- packages/tests-e2e/src/connect/models/StorageModel.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/tests-e2e/src/connect/models/StorageModel.ts b/packages/tests-e2e/src/connect/models/StorageModel.ts index 5f1097e27..b8ad9c458 100644 --- a/packages/tests-e2e/src/connect/models/StorageModel.ts +++ b/packages/tests-e2e/src/connect/models/StorageModel.ts @@ -12,7 +12,9 @@ export class StorageModel { } async loadInvitationToken(port: number) { - await this.page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); + await this.page.goto( + `${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`, + ); await expectScreen(this.page, ScreenNames.InitLogin); } From 99494e33344f969aae8d35063feee02255b25385 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 24 Jun 2025 18:03:35 +0200 Subject: [PATCH 51/60] modify nightly test workflow file --- .github/workflows/test-all.yml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 18e94d0c9..3fb601c85 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -93,12 +93,7 @@ jobs: - name: Run Complete tests for react run: | cd packages/tests-e2e - set +e npx playwright test --config=playwright.config.complete.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "COMPLETE_REACT_FAILED=true" >> $GITHUB_ENV - fi env: PLAYWRIGHT_NUM_CORES: 4 PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} @@ -138,12 +133,7 @@ jobs: - name: Run Complete tests for web-js run: | cd packages/tests-e2e - set +e npx playwright test --config=playwright.config.complete.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "COMPLETE_WEBJS_FAILED=true" >> $GITHUB_ENV - fi env: PLAYWRIGHT_NUM_CORES: 4 PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} @@ -183,12 +173,7 @@ jobs: - name: Run Complete tests for web-js-script run: | cd packages/tests-e2e - set +e npx playwright test --config=playwright.config.complete.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "COMPLETE_WEBJSSCRIPT_FAILED=true" >> $GITHUB_ENV - fi env: PLAYWRIGHT_NUM_CORES: 4 PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} @@ -228,14 +213,7 @@ jobs: - name: Run Connect tests for react run: | cd packages/tests-e2e - BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') - export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.connect-next.playground.corbado.io" - set +e npx playwright test --config=playwright.config.connect.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "CONNECT_REACT_FAILED=true" >> $GITHUB_ENV - fi env: PLAYWRIGHT_NUM_CORES: 4 PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} From 7db7bf0456062a90bbcd521335b49e6bcd6bcefd Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 24 Jun 2025 18:30:19 +0200 Subject: [PATCH 52/60] change readme --- packages/tests-e2e/README.md | 40 ++++++++---------------------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/packages/tests-e2e/README.md b/packages/tests-e2e/README.md index 78fbe14ee..d8359a789 100644 --- a/packages/tests-e2e/README.md +++ b/packages/tests-e2e/README.md @@ -4,46 +4,24 @@ This package currently contains all the end-to-end tests for testing the React p ## Running Tests Locally -This package is intended to test the local Playground React deployment connected to the staging backend deployment. +Playground is locally run within Playwright for both Complete and Connect tests. For reference look at `utils/playground.ts`. -### Setup +This means that tests can simply be run with a single command. The command depends on whether you want to run it headless or with UI. -Copy the contents of .env.example into .env.local. -For `PLAYWRIGHT_JWT_TOKEN` environment variable, request the JWT token from Martin or Stefan. -For `PLAYWRIGHT_MICROSOFT_EMAIL` and `PLAYWRIGHT_MICROSOFT_PASSWORD` environment variables, request the credentials from Anders or Martin. You can also create your own Microsoft account for testing. - -Make sure that the local Playground React deployment is configured to connect to the staging frontend API endpoints in `playground/react/.env`. +### Headless ``` -REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io +cd packages/tests-e2e +npm run e2e:complete +npm run e2e:connect ``` -Run the Playground React project locally. - -```console -$ cd playground/react -$ npm start -``` - -Now Playwright is ready to test the local Playground deployment. - ### With UI -```console -$ cd packages/tests-e2e -$ npx playwright test --config=playwright.config.complete.ts --ui --project=nightly ``` - -### From CLI - -```console -npx playwright test --config=playwright.config.complete.ts --project=nightly -``` - -Alternatively, you can do: - -```console -npm run e2e:ui:nightly +cd packages/tests-e2e +npm run e2e:complete:ui +npm run e2e:connect:ui ``` ## Generating JWT Token From 991377dbbea22a8214547ec8cd659a1ec4997288 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 24 Jun 2025 18:36:31 +0200 Subject: [PATCH 53/60] add more randomness to logstream name --- .github/workflows/test.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2acdd831..805aab6cb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -120,8 +120,10 @@ jobs: AWS_REGION: ${{ secrets.AWS_REGION }} run: | TIMESTAMP=$(date +%s000) - echo "LOG_STREAM_NAME=javascript-$TIMESTAMP" >> $GITHUB_ENV - aws logs create-log-stream --log-group-name "test-results-board" --log-stream-name "javascript-$TIMESTAMP" + RANDOM_SUFFIX=$(head /dev/urandom | tr -dc a-z0-9 | head -c 6) + LOG_STREAM_NAME="javascript-${TIMESTAMP}-${RANDOM_SUFFIX}" + echo "LOG_STREAM_NAME=$LOG_STREAM_NAME" >> $GITHUB_ENV + aws logs create-log-stream --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" - name: Run ${{ matrix.testType }} tests for react working-directory: packages/tests-e2e From 992bd0534b7e403e39c0c595958593a93e66f196 Mon Sep 17 00:00:00 2001 From: Martin Kellner Date: Thu, 7 Aug 2025 14:58:39 +0200 Subject: [PATCH 54/60] Add assetlinks --- playground/connect-next/public/.well-known/assetlinks.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 playground/connect-next/public/.well-known/assetlinks.json diff --git a/playground/connect-next/public/.well-known/assetlinks.json b/playground/connect-next/public/.well-known/assetlinks.json new file mode 100644 index 000000000..350c3528d --- /dev/null +++ b/playground/connect-next/public/.well-known/assetlinks.json @@ -0,0 +1 @@ +[{"relation":["delegate_permission/common.handle_all_urls","delegate_permission/common.get_login_creds"],"target":{"namespace":"android_app","package_name":"com.corbado.connect.example","sha256_cert_fingerprints":["F8:90:4E:9A:99:01:71:75:25:38:D5:36:16:2D:B3:65:EB:41:51:D4:53:9A:72:BC:4B:56:C5:16:43:62:E2:C0"]}}] From efe4a1f0713a4d52aa01b2e861f5f86f77a7c19a Mon Sep 17 00:00:00 2001 From: Martin Kellner Date: Thu, 7 Aug 2025 15:02:08 +0200 Subject: [PATCH 55/60] Prettier --- .../connect-next/public/.well-known/assetlinks.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/playground/connect-next/public/.well-known/assetlinks.json b/playground/connect-next/public/.well-known/assetlinks.json index 350c3528d..a4ff05d3c 100644 --- a/playground/connect-next/public/.well-known/assetlinks.json +++ b/playground/connect-next/public/.well-known/assetlinks.json @@ -1 +1,12 @@ -[{"relation":["delegate_permission/common.handle_all_urls","delegate_permission/common.get_login_creds"],"target":{"namespace":"android_app","package_name":"com.corbado.connect.example","sha256_cert_fingerprints":["F8:90:4E:9A:99:01:71:75:25:38:D5:36:16:2D:B3:65:EB:41:51:D4:53:9A:72:BC:4B:56:C5:16:43:62:E2:C0"]}}] +[ + { + "relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"], + "target": { + "namespace": "android_app", + "package_name": "com.corbado.connect.example", + "sha256_cert_fingerprints": [ + "F8:90:4E:9A:99:01:71:75:25:38:D5:36:16:2D:B3:65:EB:41:51:D4:53:9A:72:BC:4B:56:C5:16:43:62:E2:C0" + ] + } + } +] From 516141a0c7869d8753d70f49f676527c205d6398 Mon Sep 17 00:00:00 2001 From: Martin Kellner Date: Fri, 8 Aug 2025 15:05:59 +0200 Subject: [PATCH 56/60] Refactor connect tests (first passing test) --- package-lock.json | 59 +++- .../tests-e2e/playwright.config.connect.ts | 14 +- .../src/connect/models/AppendModel.ts | 11 +- .../tests-e2e/src/connect/models/BaseModel.ts | 2 +- .../tests-e2e/src/connect/models/MFAModel.ts | 19 +- .../src/connect/models/SignupModel.ts | 9 +- .../src/connect/models/StorageModel.ts | 16 +- .../src/connect/scenarios/append.spec.ts | 9 +- .../tests-e2e/src/connect/scenarios/hooks.ts | 6 +- .../src/connect/scenarios/login.spec.ts | 8 +- .../src/connect/utils/ExpectScreen.ts | 2 +- .../src/connect/utils/VirtualAuthenticator.ts | 32 +- .../tests-e2e/src/connect2/models/BasePage.ts | 59 ++++ .../src/connect2/models/LoginPage.ts | 57 ++++ .../tests-e2e/src/connect2/models/MFAPage.ts | 12 + .../src/connect2/models/PostLoginPage.ts | 59 ++++ .../src/connect2/models/ProfilePage.ts | 62 ++++ .../src/connect2/models/SignupPage.ts | 42 +++ .../src/connect2/scenarios/append.spec.ts | 54 ++++ .../src/connect2/scenarios/login.spec.ts | 28 ++ .../scenarios/network-blocking.spec.ts | 24 ++ .../connect2/utils/NetworkRequestBlocker.ts | 34 ++ .../src/connect2/utils/Playground.ts | 100 ++++++ .../src/connect2/utils/TestDataFactory.ts | 10 + .../connect2/utils/VirtualAuthenticator.ts | 109 +++++++ .../app/(auth-required)/home/client.tsx | 50 --- .../app/(auth-required)/home/page.tsx | 9 - .../app/(auth-required)/post-login/page.tsx | 14 +- .../profile/AccountSection.tsx | 159 ++++++++++ .../(auth-required)/profile/DangerSection.tsx | 38 +++ .../(auth-required)/profile/MFASection.tsx | 72 +++++ .../profile/PasskeySection.tsx | 20 ++ .../app/(auth-required)/profile/Section.tsx | 16 + .../app/(auth-required)/profile/actions.tsx | 16 + .../app/(auth-required)/profile/page.tsx | 29 ++ .../app/(auth-required)/setup-mfa/page.tsx | 297 ++++++++++++++++++ .../app/{signup => (no-auth)}/actions.ts | 0 .../login/ConventionalLogin.tsx | 20 +- .../app/(no-auth)/login/PasswordForm.tsx | 75 +++++ .../app/(no-auth)/login/WrappedLogin.tsx | 104 ++++++ .../app/(no-auth)/login/actions.ts | 13 + .../connect-next/app/(no-auth)/login/page.tsx | 11 + .../connect-next/app/(no-auth)/page.tsx | 148 +++++++++ playground/connect-next/app/globals.css | 85 +++++ .../connect-next/app/login/LoginComponent.tsx | 85 ----- .../connect-next/app/login/PasswordForm.tsx | 72 ----- playground/connect-next/app/login/actions.ts | 26 -- playground/connect-next/app/login/page.tsx | 14 - playground/connect-next/app/page.tsx | 20 -- .../app/passkey-list-wv/actions.ts | 20 -- .../connect-next/app/passkey-list-wv/page.tsx | 15 - .../connect-next/app/post-login-wv/actions.ts | 34 -- .../connect-next/app/post-login-wv/page.tsx | 27 -- .../connect-next/app/redirect/actions.ts | 12 - playground/connect-next/app/redirect/page.tsx | 42 --- playground/connect-next/app/signup/page.tsx | 114 ------- .../connect-next/components/ui/separator.tsx | 31 ++ .../connect-next/components/ui/skeleton.tsx | 15 + playground/connect-next/package.json | 2 + 59 files changed, 1920 insertions(+), 622 deletions(-) create mode 100644 packages/tests-e2e/src/connect2/models/BasePage.ts create mode 100644 packages/tests-e2e/src/connect2/models/LoginPage.ts create mode 100644 packages/tests-e2e/src/connect2/models/MFAPage.ts create mode 100644 packages/tests-e2e/src/connect2/models/PostLoginPage.ts create mode 100644 packages/tests-e2e/src/connect2/models/ProfilePage.ts create mode 100644 packages/tests-e2e/src/connect2/models/SignupPage.ts create mode 100644 packages/tests-e2e/src/connect2/scenarios/append.spec.ts create mode 100644 packages/tests-e2e/src/connect2/scenarios/login.spec.ts create mode 100644 packages/tests-e2e/src/connect2/scenarios/network-blocking.spec.ts create mode 100644 packages/tests-e2e/src/connect2/utils/NetworkRequestBlocker.ts create mode 100644 packages/tests-e2e/src/connect2/utils/Playground.ts create mode 100644 packages/tests-e2e/src/connect2/utils/TestDataFactory.ts create mode 100644 packages/tests-e2e/src/connect2/utils/VirtualAuthenticator.ts delete mode 100644 playground/connect-next/app/(auth-required)/home/client.tsx delete mode 100644 playground/connect-next/app/(auth-required)/home/page.tsx create mode 100644 playground/connect-next/app/(auth-required)/profile/AccountSection.tsx create mode 100644 playground/connect-next/app/(auth-required)/profile/DangerSection.tsx create mode 100644 playground/connect-next/app/(auth-required)/profile/MFASection.tsx create mode 100644 playground/connect-next/app/(auth-required)/profile/PasskeySection.tsx create mode 100644 playground/connect-next/app/(auth-required)/profile/Section.tsx create mode 100644 playground/connect-next/app/(auth-required)/profile/actions.tsx create mode 100644 playground/connect-next/app/(auth-required)/profile/page.tsx create mode 100644 playground/connect-next/app/(auth-required)/setup-mfa/page.tsx rename playground/connect-next/app/{signup => (no-auth)}/actions.ts (100%) rename playground/connect-next/app/{ => (no-auth)}/login/ConventionalLogin.tsx (87%) create mode 100644 playground/connect-next/app/(no-auth)/login/PasswordForm.tsx create mode 100644 playground/connect-next/app/(no-auth)/login/WrappedLogin.tsx create mode 100644 playground/connect-next/app/(no-auth)/login/actions.ts create mode 100644 playground/connect-next/app/(no-auth)/login/page.tsx create mode 100644 playground/connect-next/app/(no-auth)/page.tsx delete mode 100644 playground/connect-next/app/login/LoginComponent.tsx delete mode 100644 playground/connect-next/app/login/PasswordForm.tsx delete mode 100644 playground/connect-next/app/login/actions.ts delete mode 100644 playground/connect-next/app/login/page.tsx delete mode 100644 playground/connect-next/app/page.tsx delete mode 100644 playground/connect-next/app/passkey-list-wv/actions.ts delete mode 100644 playground/connect-next/app/passkey-list-wv/page.tsx delete mode 100644 playground/connect-next/app/post-login-wv/actions.ts delete mode 100644 playground/connect-next/app/post-login-wv/page.tsx delete mode 100644 playground/connect-next/app/redirect/actions.ts delete mode 100644 playground/connect-next/app/redirect/page.tsx delete mode 100644 playground/connect-next/app/signup/page.tsx create mode 100644 playground/connect-next/components/ui/separator.tsx create mode 100644 playground/connect-next/components/ui/skeleton.tsx diff --git a/package-lock.json b/package-lock.json index c854b4c91..962eeee3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12029,6 +12029,52 @@ } } }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", @@ -13830,7 +13876,7 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" @@ -23736,6 +23782,15 @@ "node": ">=6" } }, + "node_modules/qrcode.react": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz", + "integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/querystring": { "version": "0.2.0", "engines": { @@ -29941,6 +29996,7 @@ "@aws-sdk/client-cognito-identity-provider": "^3.799.0", "@aws-sdk/credential-providers": "^3.624.0", "@corbado/connect-react": "*", + "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.0", "aws-amplify": "^6.14.4", "aws-jwt-verify": "^5.0.0", @@ -29955,6 +30011,7 @@ "jwks-rsa": "^3.1.0", "lucide-react": "^0.507.0", "next": "15.2.4", + "qrcode.react": "^4.2.0", "react": "^18", "react-dom": "^18", "tailwind-merge": "^3.2.0", diff --git a/packages/tests-e2e/playwright.config.connect.ts b/packages/tests-e2e/playwright.config.connect.ts index fd2662c30..285afbc2c 100644 --- a/packages/tests-e2e/playwright.config.connect.ts +++ b/packages/tests-e2e/playwright.config.connect.ts @@ -11,7 +11,7 @@ if (process.env.CI) { } export default defineConfig({ - testDir: './src/connect', + testDir: './src/connect2', // fullyParallel: true, forbidOnly: !!process.env.CI, retries: 4, @@ -54,22 +54,10 @@ export default defineConfig({ trace: 'retain-on-failure', }, projects: [ - { - name: 'login-component', - testMatch: ['scenarios/login.spec.ts'], - }, { name: 'append-component', testMatch: ['scenarios/append.spec.ts'], }, - { - name: 'passkey-list-component', - testMatch: ['scenarios/passkey-list.spec.ts'], - }, - { - name: 'misc', - testMatch: ['scenarios/misc.spec.ts'], - }, ], globalSetup: 'src/connect/utils/Playground.ts', }); diff --git a/packages/tests-e2e/src/connect/models/AppendModel.ts b/packages/tests-e2e/src/connect/models/AppendModel.ts index 63b6757b6..83c9e0bd3 100644 --- a/packages/tests-e2e/src/connect/models/AppendModel.ts +++ b/packages/tests-e2e/src/connect/models/AppendModel.ts @@ -24,15 +24,8 @@ export class AppendModel { } } - autoAppendPasskey(complete: boolean, operationTrigger: () => Promise) { - if (complete) { - return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); - } else { - return this.authenticator.startAndCancelPasskeyOperation(operationTrigger, async () => { - await expectScreen(this.page, ScreenNames.PasskeyAppend); - await this.page.waitForSelector('.button-loading-container', { state: 'detached' }); - }); - } + autoAppendPasskey() { + // no-op } confirmAppended() { diff --git a/packages/tests-e2e/src/connect/models/BaseModel.ts b/packages/tests-e2e/src/connect/models/BaseModel.ts index db6920381..6bf9e4e19 100644 --- a/packages/tests-e2e/src/connect/models/BaseModel.ts +++ b/packages/tests-e2e/src/connect/models/BaseModel.ts @@ -64,7 +64,7 @@ export class BaseModel { async createUser(invited: boolean, append: boolean) { this.email = await this.signup.autofillCredentials(); - await this.signup.submit(invited, append); + await this.signup.submit(); this.mfa.registerTokenUsed(); if (invited) { if (append) { diff --git a/packages/tests-e2e/src/connect/models/MFAModel.ts b/packages/tests-e2e/src/connect/models/MFAModel.ts index 8e5adda1c..c2425b0bd 100644 --- a/packages/tests-e2e/src/connect/models/MFAModel.ts +++ b/packages/tests-e2e/src/connect/models/MFAModel.ts @@ -1,17 +1,15 @@ import type { Page } from '@playwright/test'; -import { expect } from '@playwright/test'; import type { AppendModel } from './AppendModel'; export class MFAModel { page: Page; append: AppendModel; - timestamp: number; + timestamp?: number; constructor(page: Page, append: AppendModel) { this.page = page; this.append = append; - this.timestamp = Date.now(); } registerTokenUsed() { @@ -19,18 +17,15 @@ export class MFAModel { } async autofillTOTP() { - await this.page.waitForTimeout(31000 - (Date.now() - this.timestamp)); - await this.page.getByRole('button', { name: 'Autofill TOTP' }).click(); - await expect(this.page.getByPlaceholder('TOTP')).toHaveValue(/.+/); + if (this.timestamp) { + await this.page.waitForTimeout(31000 - (Date.now() - this.timestamp)); + } + + await this.page.getByRole('button', { name: 'Autofill' }).click(); this.registerTokenUsed(); } submit(invited: boolean, autoAppend: boolean) { - if (invited) { - const operationTrigger = () => this.page.getByRole('button', { name: 'Submit' }).click(); - return this.append.autoAppendPasskey(autoAppend, operationTrigger); - } else { - return this.page.getByRole('button', { name: 'Submit' }).click(); - } + return this.page.getByRole('button', { name: 'Submit' }).click(); } } diff --git a/packages/tests-e2e/src/connect/models/SignupModel.ts b/packages/tests-e2e/src/connect/models/SignupModel.ts index 852d34993..d98db76a0 100644 --- a/packages/tests-e2e/src/connect/models/SignupModel.ts +++ b/packages/tests-e2e/src/connect/models/SignupModel.ts @@ -16,12 +16,7 @@ export class SignupModel { return await this.page.getByPlaceholder('Email').inputValue(); } - submit(invited: boolean, autoAppend: boolean) { - if (invited) { - const operationTrigger = () => this.page.getByRole('button', { name: 'Sign up' }).click(); - return this.append.autoAppendPasskey(autoAppend, operationTrigger); - } else { - return this.page.getByRole('button', { name: 'Sign up' }).click(); - } + submit() { + return this.page.getByRole('button', { name: 'Sign up' }).click(); } } diff --git a/packages/tests-e2e/src/connect/models/StorageModel.ts b/packages/tests-e2e/src/connect/models/StorageModel.ts index b8ad9c458..def5c3283 100644 --- a/packages/tests-e2e/src/connect/models/StorageModel.ts +++ b/packages/tests-e2e/src/connect/models/StorageModel.ts @@ -35,7 +35,7 @@ export class StorageModel { const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); if (!cboConnectProcessRaw) { - throw new Error('cbo_connect_process not found in local storage'); + throw new Error(`cbo_connect_process not found in local storage ${key}`); } const cboConnectProcess = JSON.parse(cboConnectProcessRaw); expect(cboConnectProcess.id).not.toBeNull(); @@ -51,7 +51,7 @@ export class StorageModel { const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); if (!cboConnectProcessRaw) { - throw new Error('cbo_connect_process not found in local storage'); + throw new Error(`cbo_connect_process not found in local storage ${key}`); } const cboConnectProcess = JSON.parse(cboConnectProcessRaw); return cboConnectProcess.loginData.expiresAt; @@ -61,7 +61,7 @@ export class StorageModel { const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); if (!cboConnectProcessRaw) { - throw new Error('cbo_connect_process not found in local storage'); + throw new Error(`cbo_connect_process not found in local storage ${key}`); } const cboConnectProcess = JSON.parse(cboConnectProcessRaw); cboConnectProcess.loginData.expiresAt = newLifetime; @@ -75,7 +75,7 @@ export class StorageModel { const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); if (!cboConnectProcessRaw) { - throw new Error('cbo_connect_process not found in local storage'); + throw new Error(`cbo_connect_process not found in local storage ${key}`); } const cboConnectProcess = JSON.parse(cboConnectProcessRaw); return cboConnectProcess.appendData.expiresAt; @@ -85,7 +85,7 @@ export class StorageModel { const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); if (!cboConnectProcessRaw) { - throw new Error('cbo_connect_process not found in local storage'); + throw new Error(`cbo_connect_process not found in local storage ${key}`); } const cboConnectProcess = JSON.parse(cboConnectProcessRaw); cboConnectProcess.appendData.expiresAt = newLifetime; @@ -99,7 +99,7 @@ export class StorageModel { const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); if (!cboConnectProcessRaw) { - throw new Error('cbo_connect_process not found in local storage'); + throw new Error(`cbo_connect_process not found in local storage ${key}`); } const cboConnectProcess = JSON.parse(cboConnectProcessRaw); return cboConnectProcess.manageData.expiresAt; @@ -109,7 +109,7 @@ export class StorageModel { const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); if (!cboConnectProcessRaw) { - throw new Error('cbo_connect_process not found in local storage'); + throw new Error(`cbo_connect_process not found in local storage ${key}`); } const cboConnectProcess = JSON.parse(cboConnectProcessRaw); cboConnectProcess.manageData.expiresAt = newLifetime; @@ -123,7 +123,7 @@ export class StorageModel { const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); if (!cboConnectProcessRaw) { - throw new Error('cbo_connect_process not found in local storage'); + throw new Error(`cbo_connect_process not found in local storage ${key}`); } const cboConnectProcess = JSON.parse(cboConnectProcessRaw); expect(cboConnectProcess.loginData).toBeNull(); diff --git a/packages/tests-e2e/src/connect/scenarios/append.spec.ts b/packages/tests-e2e/src/connect/scenarios/append.spec.ts index 2ac8d5c37..363b96ab8 100644 --- a/packages/tests-e2e/src/connect/scenarios/append.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/append.spec.ts @@ -25,10 +25,13 @@ test.describe('append component', () => { loadBeforePasskeyAppend(test); test('successful passkey append on login', async ({ model }) => { - await model.mfa.submit(true, true); - await model.expectScreen(ScreenNames.PasskeyAppended); + await model.authenticator.runWithComplete(async () => { + await model.mfa.autofillTOTP(); + model.append.autoAppendPasskey(); + await model.expectScreen(ScreenNames.PasskeyAppended); + await model.append.confirmAppended(); + }); - await model.append.confirmAppended(); await model.expectScreen(ScreenNames.Home); }); diff --git a/packages/tests-e2e/src/connect/scenarios/hooks.ts b/packages/tests-e2e/src/connect/scenarios/hooks.ts index b8ba59780..e3d592237 100644 --- a/packages/tests-e2e/src/connect/scenarios/hooks.ts +++ b/packages/tests-e2e/src/connect/scenarios/hooks.ts @@ -16,7 +16,7 @@ export function setupVirtualAuthenticator( PlaywrightWorkerArgs & PlaywrightWorkerOptions >, ) { - test.beforeEach(async ({ model }) => { + test.beforeEach(async ({ model, page }) => { await model.authenticator.addWebAuthn(); }); @@ -100,10 +100,6 @@ export function loadBeforePasskeyAppend( await model.login.submitFallbackCredentials(model.email, password, true); await model.expectScreen(ScreenNames.MFA); - - await model.mfa.autofillTOTP(); - // await model.mfa.submit(); - // await model.expectScreen(ScreenNames.PasskeyAppend); }); } diff --git a/packages/tests-e2e/src/connect/scenarios/login.spec.ts b/packages/tests-e2e/src/connect/scenarios/login.spec.ts index 17833f71d..7c0c8dacd 100644 --- a/packages/tests-e2e/src/connect/scenarios/login.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/login.spec.ts @@ -29,7 +29,6 @@ test.describe('login component (without invitation token)', () => { await model.expectScreen(ScreenNames.MFA); await model.mfa.autofillTOTP(); - await model.mfa.submit(false, false); await model.expectScreen(ScreenNames.Home); }); }); @@ -59,10 +58,11 @@ test.describe('login component (with invitation token, without passkeys)', () => await model.login.submitFallbackCredentials(model.email, password, true); await model.expectScreen(ScreenNames.MFA); - await model.mfa.autofillTOTP(); - await model.mfa.submit(true, false); + await model.authenticator.runWithCancel(async () => { + await model.mfa.autofillTOTP(); + await model.append.skipAppend(); + }); - await model.append.skipAppend(); await model.expectScreen(ScreenNames.Home); }); }); diff --git a/packages/tests-e2e/src/connect/utils/ExpectScreen.ts b/packages/tests-e2e/src/connect/utils/ExpectScreen.ts index 25d47b96d..100066ce0 100644 --- a/packages/tests-e2e/src/connect/utils/ExpectScreen.ts +++ b/packages/tests-e2e/src/connect/utils/ExpectScreen.ts @@ -55,7 +55,7 @@ export const expectScreen = async (page: Page, screenName: ScreenNames): Promise return; case ScreenNames.MFA: - await expect(page.locator('div.font-bold.text-xl')).toHaveText('MFA'); + await expect(page.locator('h3.text-xl')).toHaveText('MFA'); return; default: diff --git a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts index 16c6a8b7d..45bd46176 100644 --- a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts +++ b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts @@ -1,4 +1,4 @@ -import type { CDPSession } from '@playwright/test'; +import type { CDPSession, Page } from '@playwright/test'; import type { CDPSessionManager } from './CDPSessionManager'; import { operationTimeout } from './Constants'; @@ -7,8 +7,9 @@ export class VirtualAuthenticator { #cdpClient: CDPSession; #authenticatorId = ''; - constructor(cdpManager: CDPSessionManager) { - this.#cdpClient = cdpManager.getClient(); + async init(page: Page, passkeysSupported = true) { + this.#cdpClient = await page.context().newCDPSession(page); + await this.addWebAuthn(passkeysSupported); } async addWebAuthn(passkeySupported = true) { @@ -71,6 +72,31 @@ export class VirtualAuthenticator { await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, false); } + async runWithComplete(cb: () => Promise) { + const postOperationPromise = new Promise(resolve => { + this.#cdpClient?.on('WebAuthn.credentialAdded', () => resolve()); + this.#cdpClient?.on('WebAuthn.credentialAsserted', () => resolve()); + }); + const wait = new Promise(resolve => setTimeout(resolve, operationTimeout)); + + await this.#setWebAuthnUserVerified(this.#authenticatorId, true); + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); + + await cb(); + + await Promise.race([postOperationPromise, wait.then(() => Promise.reject('Passkey input timeout'))]); + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, false); + } + + async runWithCancel(cb: () => Promise) { + await this.#setWebAuthnUserVerified(this.#authenticatorId, false); + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); + + await cb(); + + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, false); + } + clearCredentials() { return this.#cdpClient.send('WebAuthn.clearCredentials', { authenticatorId: this.#authenticatorId, diff --git a/packages/tests-e2e/src/connect2/models/BasePage.ts b/packages/tests-e2e/src/connect2/models/BasePage.ts new file mode 100644 index 000000000..4a5d0a1d6 --- /dev/null +++ b/packages/tests-e2e/src/connect2/models/BasePage.ts @@ -0,0 +1,59 @@ +import type { Page } from '@playwright/test'; + +export abstract class BasePage { + readonly page: Page; + + protected constructor(page: Page) { + this.page = page; + } + + abstract visible(): Promise; + + clickButton(label: string): Promise { + return this.page.getByRole('button', { name: label }).click(); + } + + clickLink(label: string): Promise { + return this.page.getByRole('link', { name: label }).click(); + } + + async waitForHeading(text: string): Promise { + try { + await this.page.getByRole('heading', { name: text }).waitFor({ state: 'visible', timeout: 10000 }); + return true; + } catch { + return false; + } + } + + async waitForText(text: string): Promise { + try { + await this.page.getByText(text).waitFor({ state: 'visible', timeout: 10000 }); + return true; + } catch { + return false; + } + } + + async waitForButton(label: string): Promise { + try { + await this.page.getByRole('button', { name: label }).waitFor({ state: 'visible', timeout: 10000 }); + return true; + } catch { + return false; + } + } + + async waitBySelector(selector: string): Promise { + try { + await this.page.locator(selector).waitFor({ state: 'visible', timeout: 10000 }); + return true; + } catch { + return false; + } + } + + expectText(text: string): Promise { + return this.page.getByText(text).waitFor({ state: 'visible' }); + } +} diff --git a/packages/tests-e2e/src/connect2/models/LoginPage.ts b/packages/tests-e2e/src/connect2/models/LoginPage.ts new file mode 100644 index 000000000..53b43999d --- /dev/null +++ b/packages/tests-e2e/src/connect2/models/LoginPage.ts @@ -0,0 +1,57 @@ +import type { Page } from '@playwright/test'; + +import { BasePage } from './BasePage'; +import { SignupPage } from './SignupPage'; +import { PostLoginPage } from './PostLoginPage'; +import { ProfilePage } from './ProfilePage'; + +export enum LoginStatus { + PasskeyOneTap, + PasskeyTextField, + FallbackFirst, + FallbackSecondTOTP, + PasskeyErrorSoft, +} + +export class LoginPage extends BasePage { + page: Page; + + constructor(page: Page) { + super(page); + this.page = page; + } + + visible(): Promise { + return this.waitForHeading('Login'); + } + + static async awaitPage(page: Page): Promise { + const loginPage = new LoginPage(page); + if (!(await loginPage.visible())) { + throw new Error('Login page not visible'); + } + + return loginPage; + } + + async navigateToSignup(): Promise { + await this.clickLink('Sign up'); + + return SignupPage.awaitPage(this.page); + } + + async awaitState(status: LoginStatus): Promise { + switch (status) { + case LoginStatus.PasskeyOneTap: + return this.waitForButton('Login with Passkey'); + default: + return true; + } + } + + async loginWithOneTap(): Promise { + await this.clickButton('Login with Passkey'); + + return ProfilePage.awaitPage(this.page); + } +} diff --git a/packages/tests-e2e/src/connect2/models/MFAPage.ts b/packages/tests-e2e/src/connect2/models/MFAPage.ts new file mode 100644 index 000000000..94157da17 --- /dev/null +++ b/packages/tests-e2e/src/connect2/models/MFAPage.ts @@ -0,0 +1,12 @@ +import type { Page } from '@playwright/test'; + +import { BasePage } from './BasePage'; + +export class MFAPage extends BasePage { + private readonly page: Page; + + constructor(page: Page) { + super(page); + this.page = page; + } +} diff --git a/packages/tests-e2e/src/connect2/models/PostLoginPage.ts b/packages/tests-e2e/src/connect2/models/PostLoginPage.ts new file mode 100644 index 000000000..dc7bec9b1 --- /dev/null +++ b/packages/tests-e2e/src/connect2/models/PostLoginPage.ts @@ -0,0 +1,59 @@ +import type { Page } from '@playwright/test'; + +import { BasePage } from './BasePage'; +import { MFAPage } from './MFAPage'; +import { ProfilePage } from './ProfilePage'; + +export class PostLoginPage extends BasePage { + constructor(page: Page) { + super(page); + } + + async visible(): Promise { + return this.waitForHeading('Simplify Your Login'); + } + + static async awaitPage(page: Page): Promise { + const postLoginPage = new PostLoginPage(page); + if (!(await postLoginPage.visible())) { + throw new Error('Post login page not visible'); + } + + return postLoginPage; + } + + async awaitErrorMessage(text: string): Promise { + return this.waitForText(text); + } + + async continue(expectAutoAppend: boolean): Promise { + if (!expectAutoAppend) { + await this.clickButton('Continue'); + } + + await this.expectText('Passkey Created Successfully'); + await this.clickButton('Continue'); + + return ProfilePage.awaitPage(this.page); + } + + async continueWithCancel(expectAutoAppend: boolean): Promise { + if (!expectAutoAppend) { + await this.clickButton('Continue'); + } + + return PostLoginPage.awaitPage(this.page); + } + + async skip(): Promise { + await this.clickButton('Skip'); + + return new ProfilePage(this.page); + } + + async skipAfterSignup(): Promise { + await this.clickButton('Skip'); + + return new MFAPage(this.page); + } +} diff --git a/packages/tests-e2e/src/connect2/models/ProfilePage.ts b/packages/tests-e2e/src/connect2/models/ProfilePage.ts new file mode 100644 index 000000000..cf24b3278 --- /dev/null +++ b/packages/tests-e2e/src/connect2/models/ProfilePage.ts @@ -0,0 +1,62 @@ +import type { Page } from '@playwright/test'; + +import { BasePage } from './BasePage'; +import { LoginPage } from './LoginPage'; + +export enum ProfileStatus { + ListEmpty, + ListWithInitialError, + ListWithPasskeys, +} + +export class ProfilePage extends BasePage { + page: Page; + + constructor(page: Page) { + super(page); + this.page = page; + } + + static async awaitPage(page: Page): Promise { + const profilePage = new ProfilePage(page); + await profilePage.visible(); + + return profilePage; + } + + visible(): Promise { + return this.waitForHeading('Your profile'); + } + + async logout(): Promise { + await this.page.getByRole('button', { name: 'Logout' }).click(); + + return LoginPage.awaitPage(this.page); + } + + async appendPasskey() { + return this.clickButton('Add a passkey'); + } + + async deletePasskeyByIndex(index: string, complete: boolean) {} + + async awaitState(status: ProfileStatus): Promise { + switch (status) { + case ProfileStatus.ListEmpty: + return this.waitForButton('No passkeys'); + case ProfileStatus.ListWithInitialError: + return this.waitForText('Error loading passkeys'); + case ProfileStatus.ListWithPasskeys: + return this.waitBySelector('div.cb-passkey-list-item-delete-icon'); + } + } + + async getPasskeyCount(): Promise { + const passkeyList = this.page.locator('div.cb-passkey-list-item-delete-icon'); + return await passkeyList.count(); + } + + async awaitErrorMessage(text: string): Promise { + return this.waitForText(text); + } +} diff --git a/packages/tests-e2e/src/connect2/models/SignupPage.ts b/packages/tests-e2e/src/connect2/models/SignupPage.ts new file mode 100644 index 000000000..cd05bb11a --- /dev/null +++ b/packages/tests-e2e/src/connect2/models/SignupPage.ts @@ -0,0 +1,42 @@ +import type { Page } from '@playwright/test'; + +import { LoginPage } from './LoginPage'; +import { PostLoginPage } from './PostLoginPage'; +import { BasePage } from './BasePage'; + +export class SignupPage extends BasePage { + private readonly page: Page; + + constructor(page: Page) { + super(page); + this.page = page; + } + + visible(): Promise { + return this.waitForHeading('Sign up'); + } + + static async awaitPage(page: Page): Promise { + const signupPage = new SignupPage(page); + if (!(await signupPage.visible())) { + throw new Error('Signup page not visible'); + } + + return signupPage; + } + + async navigateToLogin(): Promise { + await this.page.getByRole('button', { name: 'Login instead' }).click(); + + return new LoginPage(this.page); + } + + async submit(email: string, phoneNumber: string, password: string): Promise { + await this.page.getByPlaceholder('Email').fill(email); + await this.page.getByPlaceholder('Phone').fill(phoneNumber); + await this.page.getByPlaceholder('Password').fill(password); + await this.page.getByRole('button', { name: 'Sign up' }).click(); + + return PostLoginPage.awaitPage(this.page); + } +} diff --git a/packages/tests-e2e/src/connect2/scenarios/append.spec.ts b/packages/tests-e2e/src/connect2/scenarios/append.spec.ts new file mode 100644 index 000000000..86029138d --- /dev/null +++ b/packages/tests-e2e/src/connect2/scenarios/append.spec.ts @@ -0,0 +1,54 @@ +import type { ChildProcess } from 'node:child_process'; + +import { expect, test } from '@playwright/test'; + +import { killPlaygroundNew, spawnPlaygroundNew } from '../../connect/utils/Playground'; +import { LoginPage, LoginStatus } from '../models/LoginPage'; +import { TestDataFactory } from '../utils/TestDataFactory'; +import { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; +import { ProfileStatus } from '../models/ProfilePage'; + +test.describe('append flows', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + + test('testAppendAfterSignUp', async ({ page }) => { + await page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); + const loginPage = new LoginPage(page); + const signupPage = await loginPage.navigateToSignup(); + const virtualAuthenticator = await VirtualAuthenticator.init(page); + + const email = TestDataFactory.generateEmail(); + + await virtualAuthenticator.modeCancel(); + const postLoginPage = await signupPage.submit(email, TestDataFactory.phoneNumber, TestDataFactory.password); + const postLoginPage2 = await postLoginPage.continueWithCancel(true); + const postLoginPage3 = await postLoginPage2.continueWithCancel(false); + expect( + await postLoginPage3.awaitErrorMessage('You have cancelled setting up your passkey. Please try again.'), + ).toBeTruthy(); + + await virtualAuthenticator.modeComplete(); + const profilePage = await postLoginPage3.continue(false); + expect(await profilePage.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); + expect(await profilePage.getPasskeyCount()).toBe(1); + const loginPage2 = await profilePage.logout(); + + expect(await loginPage2.awaitState(LoginStatus.PasskeyOneTap)).toBeTruthy(); + const postLoginPage4 = await loginPage2.loginWithOneTap(); + expect(await profilePage.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); + await postLoginPage4.appendPasskey(); + await postLoginPage4.awaitErrorMessage('No passkey created'); + expect(await profilePage.getPasskeyCount()).toBe(1); + }); + + test.skip('testAppendAfterSignUpSkipped', async ({ page }) => {}); +}); diff --git a/packages/tests-e2e/src/connect2/scenarios/login.spec.ts b/packages/tests-e2e/src/connect2/scenarios/login.spec.ts new file mode 100644 index 000000000..ebdd6a411 --- /dev/null +++ b/packages/tests-e2e/src/connect2/scenarios/login.spec.ts @@ -0,0 +1,28 @@ +import type { ChildProcess } from 'node:child_process'; + +import { test } from '@playwright/test'; + +import { killPlaygroundNew, spawnPlaygroundNew } from '../../connect/utils/Playground'; + +test.describe('append flows', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + + test.skip('testLoginWithOneTap', async ({ page }) => {}); + + test.skip('testLoginWithCUI', async ({ page }) => {}); + + test.skip('testLoginErrorStates', async ({ page }) => {}); + + test.skip('testLoginErrorStatesGradualRollout', async ({ page }) => {}); + + test.skip('testLoginErrorStatesPasskeyDeletedClientSide', async ({ page }) => {}); +}); diff --git a/packages/tests-e2e/src/connect2/scenarios/network-blocking.spec.ts b/packages/tests-e2e/src/connect2/scenarios/network-blocking.spec.ts new file mode 100644 index 000000000..6859b082c --- /dev/null +++ b/packages/tests-e2e/src/connect2/scenarios/network-blocking.spec.ts @@ -0,0 +1,24 @@ +import type { ChildProcess } from 'node:child_process'; + +import { test } from '@playwright/test'; + +import { killPlaygroundNew, spawnPlaygroundNew } from '../../connect/utils/Playground'; + +test.describe('append flows', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + + test.skip('testLoginErrorStatesNetworkBlocking', async ({ page }) => {}); + + test.skip('testLoginErrorStatesPasskeyAppendBlocked', async ({ page }) => {}); + + test.skip('testManageErrorStatesNetworkBlocking', async ({ page }) => {}); +}); diff --git a/packages/tests-e2e/src/connect2/utils/NetworkRequestBlocker.ts b/packages/tests-e2e/src/connect2/utils/NetworkRequestBlocker.ts new file mode 100644 index 000000000..612c405b1 --- /dev/null +++ b/packages/tests-e2e/src/connect2/utils/NetworkRequestBlocker.ts @@ -0,0 +1,34 @@ +import type { CDPSession } from '@playwright/test'; + +import type { CDPSessionManager } from './CDPSessionManager'; + +export class NetworkRequestBlocker { + #cdpClient: CDPSession; + + constructor(cdpManager: CDPSessionManager) { + this.#cdpClient = cdpManager.getClient(); + } + + enableBlocking() { + return this.#cdpClient.send('Network.enable'); + } + + blockCorbadoFAPI() { + return this.#cdpClient.send('Network.setBlockedURLs', { + urls: ['*.frontendapi.cloud.corbado-staging.io/v2/connect'], + }); + } + + blockCorbadoFAPIFinishEndpoint() { + return this.#cdpClient.send('Network.setBlockedURLs', { + urls: ['*.frontendapi.cloud.corbado-staging.io/v2/connect/*/finish'], + }); + } + + blockCorbadoConnectTokenEndpoint(port: number) { + // This is sufficient, as the connectTokens endpoint is called from /passkey-list handler + return this.#cdpClient.send('Network.setBlockedURLs', { + urls: [`localhost:${port.toString()}/passkey-list`], + }); + } +} diff --git a/packages/tests-e2e/src/connect2/utils/Playground.ts b/packages/tests-e2e/src/connect2/utils/Playground.ts new file mode 100644 index 000000000..a2d526421 --- /dev/null +++ b/packages/tests-e2e/src/connect2/utils/Playground.ts @@ -0,0 +1,100 @@ +import type { ChildProcess } from 'node:child_process'; +import { spawn } from 'node:child_process'; + +import getPort from 'get-port'; +import path from 'path'; +import waitPort from 'wait-port'; + +type PlaygroundType = 'connect-next' | 'connect-web-js'; +const PLAYGROUND_TYPE: PlaygroundType = (process.env.PLAYGROUND_TYPE as PlaygroundType) || 'connect-next'; + +export type PlaygroundInfo = { + server: ChildProcess; + port: number; +}; + +function getPlaygroundDir(): string { + switch (PLAYGROUND_TYPE) { + case 'connect-next': + return path.resolve(__dirname, '../../../../../playground/connect-next'); + case 'connect-web-js': + return path.resolve(__dirname, '../../../../../playground/connect-web-js'); + default: + throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); + } +} + +function getPlaygroundArgs(port: number): string[] { + switch (PLAYGROUND_TYPE) { + case 'connect-next': + return ['run', 'start', '--', '--port', port.toString()]; + case 'connect-web-js': + throw new Error(`Unimplemented: ${PLAYGROUND_TYPE}`); + default: + throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); + } +} + +export async function spawnPlaygroundNew(): Promise { + const port = await getPort(); + + const playgroundDir = getPlaygroundDir(); + const server = spawn('npm', getPlaygroundArgs(port), { + cwd: playgroundDir, + env: { + ...process.env, + }, + stdio: 'ignore', + shell: true, + }); + const ok = await waitPort({ host: 'localhost', port, timeout: 15_000, output: 'silent' }); + if (!ok) { + server.kill(); + throw new Error(`Server never came up on port ${port}`); + } + + return { server, port }; +} + +export function killPlaygroundNew(server: ChildProcess) { + server.kill(); +} + +export default async function installPlaygroundDeps() { + const playgroundDir = getPlaygroundDir(); + + const installProcess = spawn('npm', ['install'], { + cwd: playgroundDir, + stdio: 'inherit', + shell: true, + }); + + await new Promise((resolve, reject) => { + installProcess.on('close', (code: number) => { + if (code === 0) { + console.log(`[Global Setup] Dependencies installed successfully in ${playgroundDir}.`); + const buildProcess = spawn('npm', ['run', 'build'], { + cwd: playgroundDir, + stdio: 'inherit', + shell: true, + }); + buildProcess.on('close', (buildCode: number) => { + if (buildCode === 0) { + console.log(`[Global Setup] Playground built successfully in ${playgroundDir}.`); + resolve(); + } else { + reject(new Error(`[Global Setup] npm run build failed in ${playgroundDir} with code ${buildCode}`)); + } + }); + buildProcess.on('error', (err: Error) => { + reject(new Error(`[Global Setup] Failed to start build process: ${err.message}`)); + }); + } else { + reject(new Error(`[Global Setup] npm install failed in ${playgroundDir} with code ${code}`)); + } + }); + installProcess.on('error', (err: Error) => { + reject(new Error(`[Global Setup] Failed to start npm install process: ${err.message}`)); + }); + }); +} diff --git a/packages/tests-e2e/src/connect2/utils/TestDataFactory.ts b/packages/tests-e2e/src/connect2/utils/TestDataFactory.ts new file mode 100644 index 000000000..8a2668ab6 --- /dev/null +++ b/packages/tests-e2e/src/connect2/utils/TestDataFactory.ts @@ -0,0 +1,10 @@ +export class TestDataFactory { + static phoneNumber = '+1234567890'; + static password = 'TestPassword123!'; + + static generateEmail(): string { + const currentMilliseconds = Date.now(); + + return `integration-test+${currentMilliseconds}@corbado.com`; + } +} diff --git a/packages/tests-e2e/src/connect2/utils/VirtualAuthenticator.ts b/packages/tests-e2e/src/connect2/utils/VirtualAuthenticator.ts new file mode 100644 index 000000000..b874990cc --- /dev/null +++ b/packages/tests-e2e/src/connect2/utils/VirtualAuthenticator.ts @@ -0,0 +1,109 @@ +import type { CDPSession, Page } from '@playwright/test'; + +export class VirtualAuthenticator { + #cdpClient: CDPSession; + #authenticatorId = ''; + + static async init(page: Page, passkeysSupported = true): Promise { + const authenticator = new VirtualAuthenticator(); + authenticator.#cdpClient = await page.context().newCDPSession(page); + await authenticator.addWebAuthn(passkeysSupported); + + return authenticator; + } + + async addWebAuthn(passkeySupported = true) { + await this.#cdpClient.send('WebAuthn.enable'); + const result = await this.#cdpClient.send('WebAuthn.addVirtualAuthenticator', { + options: passkeySupported + ? { + protocol: 'ctap2', + transport: 'internal', + hasResidentKey: true, + hasUserVerification: true, + automaticPresenceSimulation: false, + } + : { + protocol: 'u2f', + transport: 'usb', + }, + }); + + this.#authenticatorId = result.authenticatorId; + } + + async runWithComplete(cb: () => Promise) { + const postOperationPromise = new Promise(resolve => { + this.#cdpClient?.on('WebAuthn.credentialAdded', () => resolve()); + this.#cdpClient?.on('WebAuthn.credentialAsserted', () => resolve()); + }); + const wait = new Promise(resolve => setTimeout(resolve, 5000)); + + await this.#setWebAuthnUserVerified(this.#authenticatorId, true); + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); + + await cb(); + + await Promise.race([postOperationPromise, wait.then(() => Promise.reject('Passkey input timeout'))]); + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, false); + } + + async modeCancel() { + await this.#setWebAuthnUserVerified(this.#authenticatorId, false); + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); + } + + async modeComplete() { + await this.#setWebAuthnUserVerified(this.#authenticatorId, true); + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); + } + + async runWithCancel(cb: () => Promise) { + await this.#setWebAuthnUserVerified(this.#authenticatorId, false); + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); + + await cb(); + + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, false); + } + + clearCredentials() { + return this.#cdpClient.send('WebAuthn.clearCredentials', { + authenticatorId: this.#authenticatorId, + }); + } + + async addDummyCredential() { + try { + await this.#cdpClient.send('WebAuthn.addCredential', { + authenticatorId: this.#authenticatorId, + credential: { + credentialId: '', // 'WZuSfPDeCfXUMqO3vcVZ6ZYY0w2W4NpLcLzTjMl4qns=', + isResidentCredential: true, + rpId: 'localhost', + privateKey: + 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgz/eSahk8R0fk3Jjpcbd1LPc2gGKyzEG23UFIbFTqSbyhRANCAAQ4a8dJ559cf0cZcg0U7k5oCofmtOzuqXDSwzP8LLhv0InronrySiaWAGuWFpVsbNyOnWSd6VZJU8wiFKSMiDWN', + userHandle: '', // 'TDBlaFVpNnRNQg==', + signCount: 1, + }, + }); + } catch (e) { + console.error(e); + throw e; + } + } + + #setWebAuthnAutomaticPresenceSimulation(authenticatorId: string, automatic: boolean) { + return this.#cdpClient.send('WebAuthn.setAutomaticPresenceSimulation', { + authenticatorId: authenticatorId, + enabled: automatic, + }); + } + + #setWebAuthnUserVerified(authenticatorId: string, isUserVerified: boolean) { + return this.#cdpClient.send('WebAuthn.setUserVerified', { + authenticatorId, + isUserVerified, + }); + } +} diff --git a/playground/connect-next/app/(auth-required)/home/client.tsx b/playground/connect-next/app/(auth-required)/home/client.tsx deleted file mode 100644 index 17fe2dacf..000000000 --- a/playground/connect-next/app/(auth-required)/home/client.tsx +++ /dev/null @@ -1,50 +0,0 @@ -'use client'; - -import { useRouter } from 'next/navigation'; -import { signOut } from 'aws-amplify/auth'; - -type Props = { - maybeSecretCode?: string; -}; - -export default function Home({ maybeSecretCode }: Props) { - const router = useRouter(); - - const logout = async () => { - await signOut(); - router.push('/login'); - }; - - return ( - <> -
-
-
-
Home
-
-

Great, you are logged in.

- - - - {maybeSecretCode ? ( -
{maybeSecretCode}
- ) : null} -
-
-
-
- - ); -} diff --git a/playground/connect-next/app/(auth-required)/home/page.tsx b/playground/connect-next/app/(auth-required)/home/page.tsx deleted file mode 100644 index cbf7e565d..000000000 --- a/playground/connect-next/app/(auth-required)/home/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { cookies } from 'next/headers'; -import Home from '@/app/(auth-required)/home/client'; - -export default async function Page() { - const cookieStore = await cookies(); - const maybeSecretCode = cookieStore.get('secretCode'); - - return ; -} diff --git a/playground/connect-next/app/(auth-required)/post-login/page.tsx b/playground/connect-next/app/(auth-required)/post-login/page.tsx index 7e0ba3c24..c86a4cf8d 100644 --- a/playground/connect-next/app/(auth-required)/post-login/page.tsx +++ b/playground/connect-next/app/(auth-required)/post-login/page.tsx @@ -1,20 +1,28 @@ 'use client'; import { CorbadoConnectAppend } from '@corbado/connect-react'; -import { useRouter } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { getCorbadoToken, postPasskeyAppend } from '@/app/(auth-required)/post-login/actions'; import { fetchAuthSession } from 'aws-amplify/auth'; import { AppendStatus } from '@corbado/types'; export default function Page() { const router = useRouter(); + const searchParams = useSearchParams(); return (
router.push('/home')} + onSkip={async () => { + const postSignup = searchParams.get('post-signup'); + if (postSignup) { + router.push('/setup-mfa?post-signup=true'); + } else { + router.push('/profile'); + } + }} appendTokenProvider={async () => { const session = await fetchAuthSession(); const idToken = session.tokens?.idToken?.toString(); @@ -23,7 +31,7 @@ export default function Page() { }} onComplete={async (appendStatus: AppendStatus, clientState: string) => { await postPasskeyAppend(appendStatus, clientState); - router.push('/home'); + router.push('/profile'); }} />
diff --git a/playground/connect-next/app/(auth-required)/profile/AccountSection.tsx b/playground/connect-next/app/(auth-required)/profile/AccountSection.tsx new file mode 100644 index 000000000..e7c6f73b7 --- /dev/null +++ b/playground/connect-next/app/(auth-required)/profile/AccountSection.tsx @@ -0,0 +1,159 @@ +import { useEffect, useState } from "react"; +import { Skeleton } from "@/components/ui/skeleton"; +import { CognitoUserInfo, getCognitoUserInfo } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; + +import { updateUserAttribute, confirmUserAttribute } from "aws-amplify/auth"; +import { ConfirmOTP } from "@/components/ConfirmOTP"; + +export const AccountSection = () => { + const [userInfo, setUserInfo] = useState(); + const [editMode, setEditMode] = useState(false); + const [phone, setPhone] = useState(""); + const [saving, setSaving] = useState(false); + const [verificationRequired, setVerificationRequired] = useState(false); + const [message, setMessage] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function loadUser() { + try { + const userInfo = await getCognitoUserInfo(); + setUserInfo(userInfo); + setPhone(userInfo.phoneNumber || ""); + } catch { + setMessage("Failed to load user info"); + } finally { + setLoading(false); + } + } + loadUser(); + }, []); + + const handleEdit = () => { + setEditMode(true); + setMessage(null); + setPhone(userInfo?.phoneNumber || ""); + }; + + const handleCancel = () => { + setEditMode(false); + setVerificationRequired(false); + setMessage(null); + setPhone(userInfo?.phoneNumber || ""); + }; + + const handleSave = async (e: React.FormEvent) => { + e.preventDefault(); + setSaving(true); + setMessage(null); + try { + const output = await updateUserAttribute({ + userAttribute: { attributeKey: "phone_number", value: phone }, + }); + if ( + output.nextStep && + output.nextStep.updateAttributeStep === "CONFIRM_ATTRIBUTE_WITH_CODE" + ) { + setVerificationRequired(true); + setMessage( + `A verification code was sent to your new phone number. Please enter it below.` + ); + } else { + setMessage("Phone number updated successfully."); + setEditMode(false); + setUserInfo((prev) => prev && { ...prev, phoneNumber: phone }); + } + } catch { + setMessage("Failed to update phone number."); + } finally { + setSaving(false); + } + }; + + const handleVerifyOTP = async (code: string): Promise => { + setMessage(null); + try { + await confirmUserAttribute({ + userAttributeKey: "phone_number", + confirmationCode: code, + }); + setMessage("Phone number verified and updated successfully."); + setEditMode(false); + setVerificationRequired(false); + setUserInfo((prev) => prev && { ...prev, phoneNumber: phone }); + return undefined; + } catch { + return "Failed to verify phone number. Please check the code and try again."; + } + }; + + if (loading) { + return ; + } + + const profileData = { + Username: userInfo?.username, + Email: userInfo?.email, + "Phone Number": userInfo?.phoneNumber, + "Email Verified": userInfo?.emailVerified?.toString(), + }; + + return ( +
+ {Object.entries(profileData).map(([label, value]) => ( +
+
{label}
+
+ {label === "Phone Number" && editMode ? ( + setPhone(e.target.value)} + className="border border-gray-300 rounded px-3 py-2" + required + /> + ) : ( + value || "-" + )} +
+
+ ))} + {editMode ? ( + verificationRequired ? ( +
+ + {message && ( +
{message}
+ )} +
+ ) : ( +
+
+ + +
+ {message && ( +
{message}
+ )} +
+ ) + ) : ( +
+ +
+ )} + {!editMode && message && ( +
{message}
+ )} +
+ ); +}; diff --git a/playground/connect-next/app/(auth-required)/profile/DangerSection.tsx b/playground/connect-next/app/(auth-required)/profile/DangerSection.tsx new file mode 100644 index 000000000..51a300617 --- /dev/null +++ b/playground/connect-next/app/(auth-required)/profile/DangerSection.tsx @@ -0,0 +1,38 @@ +import { deleteUser, signOut } from "aws-amplify/auth"; +import { Button } from "@/components/ui/button"; +import { useRouter } from "next/navigation"; + +export const DangerSection = () => { + const router = useRouter(); + + const logout = async () => { + await signOut(); + router.push("/login"); + }; + + const deleteAccount = async () => { + await deleteUser(); + router.push("/"); + }; + + return ( +
+
+
+ Log the user out on this device. +
+ +
+
+
Delete this user.
+ +
+
+ ); +}; + +export default DangerSection; diff --git a/playground/connect-next/app/(auth-required)/profile/MFASection.tsx b/playground/connect-next/app/(auth-required)/profile/MFASection.tsx new file mode 100644 index 000000000..86e597149 --- /dev/null +++ b/playground/connect-next/app/(auth-required)/profile/MFASection.tsx @@ -0,0 +1,72 @@ +import {useEffect, useState} from "react"; +import {fetchMFAPreference} from "aws-amplify/auth"; +import {Button} from "@/components/ui/button"; +import {useRouter} from "next/navigation"; +import {Skeleton} from "@/components/ui/skeleton"; + +type MfaInfo = { + preferred?: string; + enabledSMS: boolean; + enabledTOTP: boolean; +} + +export const MFASection = () => { + const [mfaInfo, setMfaInfo] = useState(); + const router = useRouter(); + + useEffect(() => { + async function loadUser() { + try { + const mfaPrefs = await fetchMFAPreference(); + let enabledSMS = false, enabledTOTP = false; + (mfaPrefs.enabled ?? []).forEach((mfaType: string) => { + if (mfaType === 'SMS') { + enabledSMS = true; + } else if (mfaType === 'TOTP') { + enabledTOTP = true; + } + }); + + setMfaInfo({ + preferred: mfaPrefs.preferred?.toString(), + enabledSMS, + enabledTOTP, + }); + } catch (err) { + console.error('Failed to load user info:', err); + } + + } + + void loadUser(); + }, []); + + if (!mfaInfo) { + return ; + } + + + const mfaData = mfaInfo && { + 'Preferred MFA': mfaInfo.preferred || 'Not Set', + 'SMS Enabled': mfaInfo.enabledSMS ? 'Yes' : 'No', + 'TOTP Enabled': mfaInfo.enabledTOTP ? 'Yes' : 'No', + }; + + return ( +
+
+ {mfaData && Object.entries(mfaData).map(([label, value]) => ( +
+
{label}
+
{value}
+
+ ))} +
+
+ +
+
+ ) +} + +export default MFASection; \ No newline at end of file diff --git a/playground/connect-next/app/(auth-required)/profile/PasskeySection.tsx b/playground/connect-next/app/(auth-required)/profile/PasskeySection.tsx new file mode 100644 index 000000000..175535eb8 --- /dev/null +++ b/playground/connect-next/app/(auth-required)/profile/PasskeySection.tsx @@ -0,0 +1,20 @@ +import { fetchAuthSession } from 'aws-amplify/auth'; +import { getConnectToken } from './actions'; +import { CorbadoConnectPasskeyList } from '@corbado/connect-react'; + +export const PasskeySection = () => { + return ( +
+ { + const session = await fetchAuthSession(); + const idToken = session.tokens?.idToken?.toString(); + + return await getConnectToken(connectTokenType, idToken); + }} + /> +
+ ); +}; + +export default PasskeySection; diff --git a/playground/connect-next/app/(auth-required)/profile/Section.tsx b/playground/connect-next/app/(auth-required)/profile/Section.tsx new file mode 100644 index 000000000..17518311c --- /dev/null +++ b/playground/connect-next/app/(auth-required)/profile/Section.tsx @@ -0,0 +1,16 @@ +import {Separator} from "@/components/ui/separator"; +import React from "react"; + +const Section = ({children, headline}: {children: React.ReactNode, headline: string}) => { + return ( +
+
{headline}
+ +
+ {children} +
+
+ ) +} + +export default Section; \ No newline at end of file diff --git a/playground/connect-next/app/(auth-required)/profile/actions.tsx b/playground/connect-next/app/(auth-required)/profile/actions.tsx new file mode 100644 index 000000000..3d438a84c --- /dev/null +++ b/playground/connect-next/app/(auth-required)/profile/actions.tsx @@ -0,0 +1,16 @@ +'use server'; + +import { getCorbadoConnectToken, verifyAmplifyToken } from '@/lib/utils'; + +export const getConnectToken = async (connectTokenType: string, idToken?: string) => { + if (!idToken) { + throw new Error('idToken is required'); + } + + const { displayName, identifier } = await verifyAmplifyToken(idToken); + + return getCorbadoConnectToken(connectTokenType, { + displayName: displayName, + identifier: identifier, + }); +}; diff --git a/playground/connect-next/app/(auth-required)/profile/page.tsx b/playground/connect-next/app/(auth-required)/profile/page.tsx new file mode 100644 index 000000000..256e2f5d1 --- /dev/null +++ b/playground/connect-next/app/(auth-required)/profile/page.tsx @@ -0,0 +1,29 @@ +'use client'; + +import PasskeySection from './PasskeySection'; +import { AccountSection } from './AccountSection'; +import DangerSection from './DangerSection'; +import MFASection from './MFASection'; +import Section from './Section'; + +export default function Page() { + return ( +
+
+

Your profile

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ); +} diff --git a/playground/connect-next/app/(auth-required)/setup-mfa/page.tsx b/playground/connect-next/app/(auth-required)/setup-mfa/page.tsx new file mode 100644 index 000000000..b69b4109b --- /dev/null +++ b/playground/connect-next/app/(auth-required)/setup-mfa/page.tsx @@ -0,0 +1,297 @@ +'use client'; + +import { useRouter, useSearchParams } from 'next/navigation'; +import { + confirmUserAttribute, + sendUserAttributeVerificationCode, + setUpTOTP, + updateMFAPreference, + verifyTOTPSetup, + updateUserAttribute, +} from 'aws-amplify/auth'; +import { Button } from '@/components/ui/button'; +import { Separator } from '@/components/ui/separator'; +import React, { useEffect, useState } from 'react'; +import ConfirmOTP from '@/components/ConfirmOTP'; +import { QRCodeSVG } from 'qrcode.react'; +import { CognitoUserInfo, getCognitoUserInfo } from '@/lib/utils'; + +enum State { + Select, + ConfirmSMS, + SetupTOTP, + ConfirmTOTP, + EditPhone, +} + +export default function Page() { + const router = useRouter(); + const searchParams = useSearchParams(); + const [userInfo, setUserInfo] = useState(); + const [state, setState] = useState(State.Select); + const [otpAuthUri, setOtpAuthUri] = useState(''); + const [sharedKey, setSharedKey] = useState(''); + const [editPhone, setEditPhone] = useState(''); + const [editPhoneError, setEditPhoneError] = useState(); + + const isPostSignup = !!searchParams.get('post-signup'); + const showGoBack = !isPostSignup; + + useEffect(() => { + void loadUser(); + }, []); + + async function loadUser() { + try { + const userInfo = await getCognitoUserInfo(); + setUserInfo(userInfo); + setEditPhone(''); + } catch (err) { + console.error('Failed to load user info:', err); + } + } + + const startSMS = async () => { + const res = await sendUserAttributeVerificationCode({ + userAttributeKey: 'phone_number', + }); + + console.log(res); + + setState(State.ConfirmSMS); + }; + + const startTOTP = async () => { + const setupRes = await setUpTOTP(); + const setupUri = setupRes.getSetupUri('Corbado Connect', userInfo?.email); + + setSharedKey(setupRes.sharedSecret); + setOtpAuthUri(setupUri.href); + setState(State.SetupTOTP); + }; + + const confirmTOTP = async (code: string): Promise => { + try { + await verifyTOTPSetup({ code }); + await updateMFAPreference({ + totp: 'PREFERRED', + }); + + navigateForward(); + } catch (error) { + if (error instanceof Error) { + return error.message; + } + + return 'Failed to confirm TOTP'; + } + }; + + const cancelConfirm = () => { + setEditPhone(''); + setState(State.Select); + }; + + const confirmSMS = async (code: string): Promise => { + try { + await confirmUserAttribute({ + userAttributeKey: 'phone_number', + confirmationCode: code, + }); + + const res = await updateMFAPreference({ + sms: 'PREFERRED', + }); + + console.log(res); + + navigateForward(); + } catch (error) { + if (error instanceof Error) { + return error.message; + } + + return 'Failed to confirm phone number'; + } + }; + + const navigateForward = () => { + const searchParams = new URLSearchParams(window.location.search); + const postSignup = searchParams.get('post-signup'); + + if (postSignup) { + router.push('/post-login'); + } else { + router.push('/profile'); + } + }; + + const updatePhoneNumber = async () => { + setEditPhoneError(undefined); + if (!editPhone) { + setEditPhoneError('Phone number cannot be empty'); + return; + } + try { + await updateUserAttribute({ + userAttribute: { attributeKey: 'phone_number', value: editPhone }, + }); + + setState(State.ConfirmSMS); + } catch (error) { + if (error instanceof Error) { + setEditPhoneError(error.message); + } else { + setEditPhoneError('Failed to update phone number'); + } + } + }; + + if (!userInfo) { + return
; + } + + let headline, sub: string; + let content: React.ReactNode; + switch (state) { + case State.Select: + headline = 'Protect your account'; + sub = 'To better protect your account you can either use SMS or TOTP.'; + content = ( +
+ + + + +
+ ); + break; + case State.ConfirmSMS: + headline = 'Confirm your phone number'; + sub = `We have sent a 6 digit code to ${editPhone || userInfo.phoneNumber}`; + content = ( +
+ +
+ ); + break; + + case State.SetupTOTP: + headline = 'Scan the QR code'; + sub = 'Use your favourite authenticator app (e.g. Google authenticator)'; + content = ( +
+ + + +
+ ); + break; + + case State.ConfirmTOTP: + headline = 'Enter your TOTP'; + sub = 'Enter the code from the authenticator you have just set up.'; + content = ( +
+ +
+ ); + + break; + + case State.EditPhone: + headline = 'Edit your phone number'; + sub = 'Update your phone number for SMS verification.'; + content = ( +
{ + e.preventDefault(); + updatePhoneNumber(); + }} + > + setEditPhone(e.target.value)} + placeholder='Enter new phone number' + /> + {editPhoneError &&
{editPhoneError}
} +
+ + +
+
+ ); + break; + } + + return ( +
+
+
+

{headline}

+

{sub}

+
+ {content} + {showGoBack && ( +
+ +
+ )} +
+
+ ); +} diff --git a/playground/connect-next/app/signup/actions.ts b/playground/connect-next/app/(no-auth)/actions.ts similarity index 100% rename from playground/connect-next/app/signup/actions.ts rename to playground/connect-next/app/(no-auth)/actions.ts diff --git a/playground/connect-next/app/login/ConventionalLogin.tsx b/playground/connect-next/app/(no-auth)/login/ConventionalLogin.tsx similarity index 87% rename from playground/connect-next/app/login/ConventionalLogin.tsx rename to playground/connect-next/app/(no-auth)/login/ConventionalLogin.tsx index 7077b14f4..266e00887 100644 --- a/playground/connect-next/app/login/ConventionalLogin.tsx +++ b/playground/connect-next/app/(no-auth)/login/ConventionalLogin.tsx @@ -1,11 +1,8 @@ import React, { useState } from 'react'; -import PasswordForm from '@/app/login/PasswordForm'; import { confirmSignIn, signIn } from 'aws-amplify/auth'; import { useRouter } from 'next/navigation'; import ConfirmOTP from '@/components/ConfirmOTP'; -import { cookies } from 'next/headers'; -import { TOTP } from 'totp-generator'; -import { autoFillTOTP } from '@/app/login/actions'; +import PasswordForm from './PasswordForm'; type Props = { initialUserProvidedIdentifier: string; @@ -38,7 +35,7 @@ export const ConventionalLogin = ({ initialUserProvidedIdentifier }: Props) => { break; case 'DONE': - router.push('/post-login'); + await navigatePostLogin(); break; @@ -55,6 +52,10 @@ export const ConventionalLogin = ({ initialUserProvidedIdentifier }: Props) => { } }; + const navigatePostLogin = async () => { + await router.push('/post-login'); + }; + const handleConfirmCode = async (code: string): Promise => { try { const res = await confirmSignIn({ @@ -62,7 +63,7 @@ export const ConventionalLogin = ({ initialUserProvidedIdentifier }: Props) => { }); if (res.isSignedIn) { - router.push('/post-login'); + await navigatePostLogin(); } } catch (e) { if (e instanceof Error) { @@ -96,12 +97,7 @@ export const ConventionalLogin = ({ initialUserProvidedIdentifier }: Props) => { case State.ProvideTOTPCode: headline = 'Check your authenticator'; sub = 'Please enter the code from your authenticator app.'; - content = ( - - ); + content = ; break; default: diff --git a/playground/connect-next/app/(no-auth)/login/PasswordForm.tsx b/playground/connect-next/app/(no-auth)/login/PasswordForm.tsx new file mode 100644 index 000000000..137e91347 --- /dev/null +++ b/playground/connect-next/app/(no-auth)/login/PasswordForm.tsx @@ -0,0 +1,75 @@ +import {FormEvent, useState} from "react"; +import {Button} from "@/components/ui/button"; + +type Props = { + initialUserProvidedIdentifier: string; + initialError?: string; + onClick: (username: string, password: string) => Promise; +} + +export const PasswordForm = ({onClick, initialUserProvidedIdentifier}: Props) => { + const [username, setUsername] = useState(initialUserProvidedIdentifier); + const [password, setPassword] = useState(''); + const [message, setMessage] = useState(''); + + const handleLogin = async (e: FormEvent) => { + e.preventDefault(); + setMessage('Loading...'); + const maybeError = await onClick(username, password); + if (maybeError) { + setMessage(maybeError); + } + } + + return ( +
+
+ + setUsername(e.target.value)} + /> +
+
+ + setPassword(e.target.value)} + /> +
+
{message}
+ +
+ ); +} + +export default PasswordForm; \ No newline at end of file diff --git a/playground/connect-next/app/(no-auth)/login/WrappedLogin.tsx b/playground/connect-next/app/(no-auth)/login/WrappedLogin.tsx new file mode 100644 index 000000000..6b2d173f4 --- /dev/null +++ b/playground/connect-next/app/(no-auth)/login/WrappedLogin.tsx @@ -0,0 +1,104 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import React, { useState } from 'react'; +import { confirmSignIn, signIn } from 'aws-amplify/auth'; +import ConventionalLogin from './ConventionalLogin'; +import { CorbadoConnectLogin } from '@corbado/connect-react'; +import Link from 'next/link'; +import { postPasskeyLogin } from './actions'; + +export type Props = { + clientState: string | undefined; +}; + +const decodeJwt = (token: string) => { + const [, payload] = token.split('.'); + return JSON.parse(atob(payload)); +}; + +type WithWebauthnId = { + webauthnId: string; +}; + +const WrappedLogin = ({ clientState }: Props) => { + const router = useRouter(); + + const [conventionalLoginVisible, setConventionalLoginVisible] = useState(false); + const [email, setEmail] = useState(''); + const [fallbackErrorMessage, setFallbackErrorMessage] = useState(''); + + console.log('fallbackErrorMessage', fallbackErrorMessage); + + const postPasskeyLoginNew = async (signedPasskeyData: string, clientState: string) => { + // decode JWT + const decoded = decodeJwt(signedPasskeyData) as WithWebauthnId; + + try { + await signIn({ + username: decoded.webauthnId, + options: { authFlowType: 'CUSTOM_WITHOUT_SRP' }, + }); + + const resultConfirm = await confirmSignIn({ + challengeResponse: signedPasskeyData, + }); + console.log('resultConfirm', resultConfirm); + + await postPasskeyLogin(clientState); + + await router.push('/post-login'); + } catch (e) { + console.error(e); + } + }; + + return ( +
+
+ {conventionalLoginVisible ? : null} + {!conventionalLoginVisible ? ( + <> +
+

Login with passkeys

+

A simple and secure way to log in.

+
+
+ { + setEmail(identifier); + setConventionalLoginVisible(true); + setFallbackErrorMessage(message); + }} + onFallbackCustom={(identifier: string, code: string) => { + setEmail(identifier); + setConventionalLoginVisible(true); + setFallbackErrorMessage(code); + }} + onError={(error: string) => console.log('error', error)} + onLoaded={(msg: string) => console.log('component has loaded: ' + msg)} + onComplete={async (signedPasskeyData: string, newClientState: string) => { + await postPasskeyLoginNew(signedPasskeyData, newClientState); + }} + onSignupClick={() => router.push('/')} + clientState={clientState} + /> +
+ + ) : null} +

+ {"Don't have an account? "} + + Sign up + + {' for free.'} +

+
+
+ ); +}; + +export default WrappedLogin; diff --git a/playground/connect-next/app/(no-auth)/login/actions.ts b/playground/connect-next/app/(no-auth)/login/actions.ts new file mode 100644 index 000000000..e84c93f2f --- /dev/null +++ b/playground/connect-next/app/(no-auth)/login/actions.ts @@ -0,0 +1,13 @@ +'use server'; + +import {cookies} from "next/headers"; + +export async function postPasskeyLogin(clientState: string) { + const cookieStore = await cookies(); + cookieStore.set({ + name: 'cbo_client_state', + value: clientState, + httpOnly: true, + expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), + }); +} diff --git a/playground/connect-next/app/(no-auth)/login/page.tsx b/playground/connect-next/app/(no-auth)/login/page.tsx new file mode 100644 index 000000000..138e76ada --- /dev/null +++ b/playground/connect-next/app/(no-auth)/login/page.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import { cookies } from "next/headers"; +import WrappedLogin from "./WrappedLogin"; + +export default async function Page() { + const cookieStore = await cookies(); + const clientState = cookieStore.get("cbo_client_state"); + console.log("clientState", clientState); + + return ; +} diff --git a/playground/connect-next/app/(no-auth)/page.tsx b/playground/connect-next/app/(no-auth)/page.tsx new file mode 100644 index 000000000..990058112 --- /dev/null +++ b/playground/connect-next/app/(no-auth)/page.tsx @@ -0,0 +1,148 @@ +'use client'; + +import { FormEvent, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { generateRandomString } from '@/lib/random'; +import { signIn, signUp } from 'aws-amplify/auth'; +import { Button } from '@/components/ui/button'; +import Link from 'next/link'; + +export default function SignupPage() { + const router = useRouter(); + const [email, setEmail] = useState(''); + const [phone, setPhone] = useState(''); + const [password, setPassword] = useState(''); + const [message, setMessage] = useState(''); + + const handleSignup = async (e: FormEvent) => { + e.preventDefault(); + const username = generateRandomString(10); + + try { + setMessage('Loading...'); + const resSignUp = await signUp({ + username: username, + password, + options: { + userAttributes: { + email: email, + phone_number: phone, + }, + }, + }); + + console.log(resSignUp); + + const resLogin = await signIn({ username, password }); + console.log(resLogin); + router.push('/post-login?post-signup=true'); + } catch (err) { + console.error('Error during signup:', err); + } + }; + + const handleAutofill = (e: FormEvent) => { + e.preventDefault(); + + const random = generateRandomString(6); + const email = `integration-test+${random}@corbado.com`; + setEmail(email); + setPassword('asdfasdf'); + setPhone('+4915121609839'); + }; + + return ( +
+
+
+

Sign Up

+

Create an account with your email, phone and password

+
+
+
+ + setEmail(e.target.value)} + /> +
+
+ + setPassword(e.target.value)} + /> +
+
+ + setPhone(e.target.value)} + /> +
+ {message &&
{message}
} + + +

+ {'Already have an account? '} + + Log in + + {' instead.'} +

+
+
+
+ ); +} diff --git a/playground/connect-next/app/globals.css b/playground/connect-next/app/globals.css index cdd296bfd..1a4a65284 100644 --- a/playground/connect-next/app/globals.css +++ b/playground/connect-next/app/globals.css @@ -76,3 +76,88 @@ html { @apply bg-background text-foreground; } } + +.cb-connect-custom-style { + --cb-color-outline: #dcdcdc; + --cb-color-primary: #253544; + --cb-color-secondary: #929aa1; + --cb-color-tertiary: #666; + --cb-color-on-primary: #fff; + --cb-border-radius: 5px; + --cb-color-surface: var(--color-gray-50); + --cb-color-primary-container: #eef2f3; + --cb-color-on-primary-container: #253544; + --cb-color-secondary-container: #ecf4e7; + --cb-color-on-secondary-container: #80b816; + --cb-font-family-primary: var( + --default-font-family, + ui-sans-serif, + system-ui, + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji", + "Segoe UI Symbol", + "Noto Color Emoji" + ); + --cb-font-family-secondary: var( + --default-font-family, + ui-sans-serif, + system-ui, + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji", + "Segoe UI Symbol", + "Noto Color Emoji" + ); + + --cb-color-error-container: #ffd6da; + --cb-color-on-error-container: #cf0000; + + .cb-tag { + border-radius: 20px; + } + + .cb-primary-button, + .cb-outline-button { + border-radius: 0.5rem; + padding: 0.5rem 1.5rem; + } + + .cb-input { + font-size: 12px; + border-radius: 0.5rem; + padding: 0.5rem 0.5rem; + max-width: none; + } + + .cb-passkey-list-item { + border: none; + border-bottom: 1px solid var(--cb-color-outline); + border-radius: 0; + } + + .cb-passkey-list-empty, + .cb-passkey-list-loader-container { + border-bottom: 1px solid var(--cb-color-outline); + } + + .cb-modal-cta { + justify-content: start; + } + + .cb-modal-cta > button { + width: fit-content; + } + + .cb-passkey-button-subtitle { + font-size: 0.7rem; + } + + .cb-connect-append-border { + padding: 0; + } + + .cb-connect-append-icon svg { + margin: auto; + } +} diff --git a/playground/connect-next/app/login/LoginComponent.tsx b/playground/connect-next/app/login/LoginComponent.tsx deleted file mode 100644 index 9cbf5c4d7..000000000 --- a/playground/connect-next/app/login/LoginComponent.tsx +++ /dev/null @@ -1,85 +0,0 @@ -'use client'; - -import { useRouter } from 'next/navigation'; -import { CorbadoConnectLogin } from '@corbado/connect-react'; -import { useState } from 'react'; -import ConventionalLogin from '@/app/login/ConventionalLogin'; -import { postPasskeyLogin } from './actions'; -import { confirmSignIn, signIn } from 'aws-amplify/auth'; - -export type Props = { - clientState: string | undefined; -}; - -const decodeJwt = (token: string) => { - const [, payload] = token.split('.'); - return JSON.parse(atob(payload)); -}; - -type WithWebauthnId = { - webauthnId: string; -}; - -export default function LoginComponent({ clientState }: Props) { - const router = useRouter(); - const [conventionalLoginVisible, setConventionalLoginVisible] = useState(false); - const [email, setEmail] = useState(''); - const [fallbackErrorMessage, setFallbackErrorMessage] = useState(''); - - const postPasskeyLoginNew = async (signedPasskeyData: string, clientState: string) => { - // decode JWT - const decoded = decodeJwt(signedPasskeyData) as WithWebauthnId; - - try { - await signIn({ - username: decoded.webauthnId, - options: { authFlowType: 'CUSTOM_WITHOUT_SRP' }, - }); - - const resultConfirm = await confirmSignIn({ - challengeResponse: signedPasskeyData, - }); - console.log('resultConfirm', resultConfirm); - - await postPasskeyLogin(clientState); - - router.push('/post-login'); - } catch (e) { - console.error(e); - } - }; - - return ( -
-
-
- {conventionalLoginVisible ? : null} -
- { - setEmail(identifier); - setConventionalLoginVisible(true); - setFallbackErrorMessage(message); - console.log('onFallback', identifier); - }} - onFallbackCustom={(identifier: string, code: string, _: string) => { - setEmail(identifier); - setConventionalLoginVisible(true); - setFallbackErrorMessage(code); - console.log('onFallbackCustom', identifier, code); - }} - onError={(error: string) => console.log('error', error)} - onLoaded={(msg: string) => console.log('component has loaded: ' + msg)} - onComplete={async (signedPasskeyData: string, newClientState: string) => { - await postPasskeyLoginNew(signedPasskeyData, newClientState); - }} - onSignupClick={() => router.push('/signup')} - onHelpClick={() => alert('help requested')} - clientState={clientState} - /> -
-
-
-
- ); -} diff --git a/playground/connect-next/app/login/PasswordForm.tsx b/playground/connect-next/app/login/PasswordForm.tsx deleted file mode 100644 index 0c0f65e31..000000000 --- a/playground/connect-next/app/login/PasswordForm.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { FormEvent, useState } from 'react'; - -type Props = { - initialUserProvidedIdentifier: string; - initialError?: string; - onClick: (username: string, password: string) => Promise; -}; - -export const PasswordForm = ({ onClick, initialUserProvidedIdentifier }: Props) => { - const [username, setUsername] = useState(initialUserProvidedIdentifier); - const [password, setPassword] = useState(''); - const [message, setMessage] = useState(''); - - const handleLogin = async (e: FormEvent) => { - e.preventDefault(); - setMessage('Loading...'); - const maybeError = await onClick(username, password); - if (maybeError) { - setMessage(maybeError); - } - }; - - return ( -
-
- - setUsername(e.target.value)} - /> -
-
- - setPassword(e.target.value)} - /> -
-
{message}
- -
- ); -}; - -export default PasswordForm; diff --git a/playground/connect-next/app/login/actions.ts b/playground/connect-next/app/login/actions.ts deleted file mode 100644 index 10379a604..000000000 --- a/playground/connect-next/app/login/actions.ts +++ /dev/null @@ -1,26 +0,0 @@ -'use server'; - -import { cookies } from 'next/headers'; -import { TOTP } from 'totp-generator'; - -export async function postPasskeyLogin(clientState: string) { - const cookieStore = await cookies(); - cookieStore.set({ - name: 'cbo_client_state', - value: clientState, - httpOnly: true, - expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), - }); -} - -export async function autoFillTOTP() { - const cookieStore = await cookies(); - const maybeSecretCode = cookieStore.get('secretCode'); - if (!maybeSecretCode) { - return; - } - - const { otp } = TOTP.generate(maybeSecretCode.value); - - return otp; -} diff --git a/playground/connect-next/app/login/page.tsx b/playground/connect-next/app/login/page.tsx deleted file mode 100644 index 374c4a880..000000000 --- a/playground/connect-next/app/login/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import LoginComponent from '@/app/login/LoginComponent'; -import { cookies } from 'next/headers'; - -export type Props = { - clientState: string | undefined; -}; - -export default async function LoginPage() { - const cookieStore = await cookies(); - const clientState = cookieStore.get('cbo_client_state'); - console.log('clientState', clientState); - - return ; -} diff --git a/playground/connect-next/app/page.tsx b/playground/connect-next/app/page.tsx deleted file mode 100644 index 576df74fd..000000000 --- a/playground/connect-next/app/page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -'use client'; - -import Link from 'next/link'; - -export default function Home() { - return ( -
-

Home

-

- Login -

-

- Signup -

-

- Demo -

-
- ); -} diff --git a/playground/connect-next/app/passkey-list-wv/actions.ts b/playground/connect-next/app/passkey-list-wv/actions.ts deleted file mode 100644 index bedbc4f2a..000000000 --- a/playground/connect-next/app/passkey-list-wv/actions.ts +++ /dev/null @@ -1,20 +0,0 @@ -'use server'; - -import { cookies } from 'next/headers'; -import { ConnectTokenType } from '@corbado/types'; -import { getCorbadoConnectToken, verifyAmplifyToken } from '@/lib/utils'; - -export const getCorbadoToken = async (tokenType: ConnectTokenType) => { - const cookieStore = await cookies(); - const idToken = cookieStore.get('idToken'); - if (!idToken) { - throw new Error('idToken is required'); - } - - const { displayName, identifier } = await verifyAmplifyToken(idToken.value); - - return getCorbadoConnectToken(tokenType, { - displayName: displayName, - identifier: identifier, - }); -}; diff --git a/playground/connect-next/app/passkey-list-wv/page.tsx b/playground/connect-next/app/passkey-list-wv/page.tsx deleted file mode 100644 index 4e6703b18..000000000 --- a/playground/connect-next/app/passkey-list-wv/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client'; -import { CorbadoConnectPasskeyList } from '@corbado/connect-react'; -import { getCorbadoToken } from './actions'; - -export default function PasskeyListPage() { - return ( -
-
-
- getCorbadoToken(tokenType)} /> -
-
-
- ); -} diff --git a/playground/connect-next/app/post-login-wv/actions.ts b/playground/connect-next/app/post-login-wv/actions.ts deleted file mode 100644 index 6285d3f9a..000000000 --- a/playground/connect-next/app/post-login-wv/actions.ts +++ /dev/null @@ -1,34 +0,0 @@ -'use server'; - -import { AppendStatus, ConnectTokenType } from '@corbado/types'; -import { cookies } from 'next/headers'; -import { getCorbadoConnectToken, verifyAmplifyToken } from '@/lib/utils'; - -export const getCorbadoToken = async () => { - const cookieStore = await cookies(); - const idToken = cookieStore.get('idToken'); - if (!idToken) { - throw new Error('idToken is required'); - } - - const { displayName, identifier } = await verifyAmplifyToken(idToken.value); - - return getCorbadoConnectToken('passkey-append' as ConnectTokenType, { - displayName: displayName, - identifier: identifier, - }); -}; - -export async function postPasskeyAppend(appendStatus: AppendStatus, clientState: string) { - // update client side state - console.log(appendStatus); - if (appendStatus === 'complete' || appendStatus === 'complete-noop') { - const cookieStore = await cookies(); - cookieStore.set({ - name: 'cbo_client_state', - value: clientState, - httpOnly: true, - expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), - }); - } -} diff --git a/playground/connect-next/app/post-login-wv/page.tsx b/playground/connect-next/app/post-login-wv/page.tsx deleted file mode 100644 index bbbcb7788..000000000 --- a/playground/connect-next/app/post-login-wv/page.tsx +++ /dev/null @@ -1,27 +0,0 @@ -'use client'; -import { CorbadoConnectAppend } from '@corbado/connect-react'; -import { getCorbadoToken, postPasskeyAppend } from '@/app/post-login-wv/actions'; -import { AppendStatus } from '@corbado/types'; - -export default function PostLoginPage() { - return ( -
-
-
- { - window.location.href = `auth://callback?status=${status}`; - }} - appendTokenProvider={async () => { - return await getCorbadoToken(); - }} - onComplete={async (status: AppendStatus, clientSideState: string) => { - await postPasskeyAppend(status, clientSideState); - window.location.href = `auth://callback?status=${status}`; - }} - /> -
-
-
- ); -} diff --git a/playground/connect-next/app/redirect/actions.ts b/playground/connect-next/app/redirect/actions.ts deleted file mode 100644 index 0c3ea693b..000000000 --- a/playground/connect-next/app/redirect/actions.ts +++ /dev/null @@ -1,12 +0,0 @@ -'use server'; - -import { cookies } from 'next/headers'; - -export const setIdToken = async (token: string) => { - const cookieStore = await cookies(); - cookieStore.set({ - name: 'idToken', - value: token, - httpOnly: true, - }); -}; diff --git a/playground/connect-next/app/redirect/page.tsx b/playground/connect-next/app/redirect/page.tsx deleted file mode 100644 index 8e0382ac4..000000000 --- a/playground/connect-next/app/redirect/page.tsx +++ /dev/null @@ -1,42 +0,0 @@ -'use client'; - -import { useRouter, useSearchParams } from 'next/navigation'; -import { setIdToken } from './actions'; -import { Suspense, useEffect, useState } from 'react'; - -export default function Page() { - return ( - - - - ); -} - -function Redirecting() { - const searchParams = useSearchParams(); - const token = searchParams.get('token'); - const redirectUrl = searchParams.get('redirectUrl'); - const [loading, setLoading] = useState(true); - const router = useRouter(); - - useEffect(() => { - const init = async () => { - if (!token || !redirectUrl) { - return; - } - - await setIdToken(token); - setLoading(false); - console.log('pushing redirectUrl', redirectUrl); - router.push(redirectUrl); - }; - - init(); - }, []); - - if (loading) { - return
Loading...
; - } - - return
Redirecting...
; -} diff --git a/playground/connect-next/app/signup/page.tsx b/playground/connect-next/app/signup/page.tsx deleted file mode 100644 index f2cf3c993..000000000 --- a/playground/connect-next/app/signup/page.tsx +++ /dev/null @@ -1,114 +0,0 @@ -'use client'; - -import { TOTP } from 'totp-generator'; - -import { useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { generateRandomString } from '@/lib/random'; -import { signUp, signIn, setUpTOTP, verifyTOTPSetup, updateMFAPreference } from 'aws-amplify/auth'; -import { setTOTPSecretCode } from '@/app/signup/actions'; - -export default function SignupPage() { - const router = useRouter(); - const [email, setEmail] = useState(''); - const [phone, setPhone] = useState(''); - const [password, setPassword] = useState(''); - - const onClickSignUp = async (email: string, phone: string, password: string) => { - const username = generateRandomString(10); - - try { - const resSignUp = await signUp({ - username: username, - password, - options: { - userAttributes: { - email: email, - phone_number: phone, - }, - }, - }); - - console.log(resSignUp); - - const resLogin = await signIn({ username, password }); - console.log(resLogin); - - const setupRes = await setUpTOTP(); - console.log('setupRes', setupRes); - - await setTOTPSecretCode(setupRes.sharedSecret); - - const { otp } = TOTP.generate(setupRes.sharedSecret); - console.log('otp', otp); - - await verifyTOTPSetup({ code: otp }); - await updateMFAPreference({ - totp: 'PREFERRED', - }); - - router.push('/post-login'); - } catch (err) { - console.error('Error during signup:', err); - } - }; - - return ( -
-
-
-
Signup
- -
-
- setEmail(e.target.value)} - /> - setPhone(e.target.value)} - /> - setPassword(e.target.value)} - /> -
- -
-
-
-
- ); -} diff --git a/playground/connect-next/components/ui/separator.tsx b/playground/connect-next/components/ui/separator.tsx new file mode 100644 index 000000000..12d81c4a8 --- /dev/null +++ b/playground/connect-next/components/ui/separator.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...props }, + ref + ) => ( + + ) +) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/playground/connect-next/components/ui/skeleton.tsx b/playground/connect-next/components/ui/skeleton.tsx new file mode 100644 index 000000000..d7e45f7bd --- /dev/null +++ b/playground/connect-next/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/playground/connect-next/package.json b/playground/connect-next/package.json index bbfb481db..8cd72ee17 100644 --- a/playground/connect-next/package.json +++ b/playground/connect-next/package.json @@ -12,6 +12,7 @@ "@aws-sdk/client-cognito-identity-provider": "^3.799.0", "@aws-sdk/credential-providers": "^3.624.0", "@corbado/connect-react": "*", + "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.0", "aws-amplify": "^6.14.4", "aws-jwt-verify": "^5.0.0", @@ -26,6 +27,7 @@ "jwks-rsa": "^3.1.0", "lucide-react": "^0.507.0", "next": "15.2.4", + "qrcode.react": "^4.2.0", "react": "^18", "react-dom": "^18", "tailwind-merge": "^3.2.0", From 1ff72e79798ba8824aab99f366f007b7203ba37e Mon Sep 17 00:00:00 2001 From: Martin Kellner Date: Fri, 8 Aug 2025 19:10:13 +0200 Subject: [PATCH 57/60] Refactor connect complete --- packages/tests-e2e/README.md | 8 + .../tests-e2e/playwright.config.connect.ts | 38 +- .../src/connect/fixtures/BaseTest.ts | 24 -- .../src/connect/models/AppendModel.ts | 38 -- .../tests-e2e/src/connect/models/BaseModel.ts | 79 ---- .../{connect2 => connect}/models/BasePage.ts | 20 + .../tests-e2e/src/connect/models/HomeModel.ts | 17 - .../src/connect/models/LoginModel.ts | 69 ---- .../tests-e2e/src/connect/models/LoginPage.ts | 112 ++++++ .../tests-e2e/src/connect/models/MFAModel.ts | 31 -- .../tests-e2e/src/connect/models/MFAPage.ts | 48 +++ .../src/connect/models/PasskeyListModel.ts | 48 --- .../models/PostLoginPage.ts | 27 +- .../models/ProfilePage.ts | 28 +- .../src/connect/models/SignupModel.ts | 22 -- .../models/SignupPage.ts | 4 +- .../src/connect/models/StorageModel.ts | 136 ------- .../src/connect/models/WebhookModel.ts | 105 ------ .../src/connect/scenarios/append.spec.ts | 170 +++++---- .../tests-e2e/src/connect/scenarios/hooks.ts | 117 ------ .../src/connect/scenarios/login.spec.ts | 345 +++++++----------- .../src/connect/scenarios/misc.spec.ts | 105 ------ .../scenarios/network-blocking.spec.ts | 147 ++++++++ .../connect/scenarios/passkey-list.spec.ts | 146 -------- .../src/connect/utils/AuthenticatorApp.ts | 55 +++ .../src/connect/utils/CDPSessionManager.ts | 16 - .../tests-e2e/src/connect/utils/Constants.ts | 36 -- .../src/connect/utils/ExpectScreen.ts | 72 ---- .../connect/utils/NetworkRequestBlocker.ts | 62 +++- .../utils/TestDataFactory.ts | 0 .../src/connect/utils/VirtualAuthenticator.ts | 58 +-- .../src/connect2/models/LoginPage.ts | 57 --- .../tests-e2e/src/connect2/models/MFAPage.ts | 12 - .../src/connect2/scenarios/append.spec.ts | 54 --- .../src/connect2/scenarios/login.spec.ts | 28 -- .../scenarios/network-blocking.spec.ts | 24 -- .../connect2/utils/NetworkRequestBlocker.ts | 34 -- .../src/connect2/utils/Playground.ts | 100 ----- .../connect2/utils/VirtualAuthenticator.ts | 109 ------ .../profile/AccountSection.tsx | 119 +++--- .../(auth-required)/profile/DangerSection.tsx | 33 +- .../(auth-required)/profile/MFASection.tsx | 120 +++--- .../app/(auth-required)/profile/Section.tsx | 26 +- .../app/(auth-required)/setup-mfa/page.tsx | 8 +- .../app/(no-auth)/login/ConventionalLogin.tsx | 4 +- .../app/(no-auth)/login/PasswordForm.tsx | 133 ++++--- .../app/(no-auth)/login/WrappedLogin.tsx | 7 +- .../app/(no-auth)/login/actions.ts | 16 +- .../connect-next/app/(no-auth)/login/page.tsx | 10 +- playground/connect-next/app/globals.css | 16 +- .../connect-next/components/ui/separator.tsx | 39 +- .../connect-next/components/ui/skeleton.tsx | 13 +- 52 files changed, 986 insertions(+), 2159 deletions(-) delete mode 100644 packages/tests-e2e/src/connect/fixtures/BaseTest.ts delete mode 100644 packages/tests-e2e/src/connect/models/AppendModel.ts delete mode 100644 packages/tests-e2e/src/connect/models/BaseModel.ts rename packages/tests-e2e/src/{connect2 => connect}/models/BasePage.ts (72%) delete mode 100644 packages/tests-e2e/src/connect/models/HomeModel.ts delete mode 100644 packages/tests-e2e/src/connect/models/LoginModel.ts create mode 100644 packages/tests-e2e/src/connect/models/LoginPage.ts delete mode 100644 packages/tests-e2e/src/connect/models/MFAModel.ts create mode 100644 packages/tests-e2e/src/connect/models/MFAPage.ts delete mode 100644 packages/tests-e2e/src/connect/models/PasskeyListModel.ts rename packages/tests-e2e/src/{connect2 => connect}/models/PostLoginPage.ts (70%) rename packages/tests-e2e/src/{connect2 => connect}/models/ProfilePage.ts (56%) delete mode 100644 packages/tests-e2e/src/connect/models/SignupModel.ts rename packages/tests-e2e/src/{connect2 => connect}/models/SignupPage.ts (96%) delete mode 100644 packages/tests-e2e/src/connect/models/StorageModel.ts delete mode 100644 packages/tests-e2e/src/connect/models/WebhookModel.ts delete mode 100644 packages/tests-e2e/src/connect/scenarios/hooks.ts delete mode 100644 packages/tests-e2e/src/connect/scenarios/misc.spec.ts create mode 100644 packages/tests-e2e/src/connect/scenarios/network-blocking.spec.ts delete mode 100644 packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts create mode 100644 packages/tests-e2e/src/connect/utils/AuthenticatorApp.ts delete mode 100644 packages/tests-e2e/src/connect/utils/CDPSessionManager.ts delete mode 100644 packages/tests-e2e/src/connect/utils/Constants.ts delete mode 100644 packages/tests-e2e/src/connect/utils/ExpectScreen.ts rename packages/tests-e2e/src/{connect2 => connect}/utils/TestDataFactory.ts (100%) delete mode 100644 packages/tests-e2e/src/connect2/models/LoginPage.ts delete mode 100644 packages/tests-e2e/src/connect2/models/MFAPage.ts delete mode 100644 packages/tests-e2e/src/connect2/scenarios/append.spec.ts delete mode 100644 packages/tests-e2e/src/connect2/scenarios/login.spec.ts delete mode 100644 packages/tests-e2e/src/connect2/scenarios/network-blocking.spec.ts delete mode 100644 packages/tests-e2e/src/connect2/utils/NetworkRequestBlocker.ts delete mode 100644 packages/tests-e2e/src/connect2/utils/Playground.ts delete mode 100644 packages/tests-e2e/src/connect2/utils/VirtualAuthenticator.ts diff --git a/packages/tests-e2e/README.md b/packages/tests-e2e/README.md index d8359a789..d5a33afdc 100644 --- a/packages/tests-e2e/README.md +++ b/packages/tests-e2e/README.md @@ -56,3 +56,11 @@ echo $JWT The first segment of the script contains all the information that must be extracted from the backend deployment. As evident in the script, the token is valid for around 34 days. The token is intended to be renewed monthly using this script. For rewnewal, it is not necessary to extract all the information in the first segment. + +## Authoring rules (connect2) + +- Knowledge about page structure lives in `/models` only. Scenarios call model methods, not raw selectors. +- All passkey authenticator interactions flow through `VirtualAuthenticator`. +- All TOTP authenticator interactions flow through `AuthenticatorApp`. +- Scenarios set up app state via navigation helpers and avoid duplicating UI logic. +- Prefer explicit `awaitPage/visible` checks when changing screens. diff --git a/packages/tests-e2e/playwright.config.connect.ts b/packages/tests-e2e/playwright.config.connect.ts index 285afbc2c..1626916d2 100644 --- a/packages/tests-e2e/playwright.config.connect.ts +++ b/packages/tests-e2e/playwright.config.connect.ts @@ -2,7 +2,7 @@ import { defineConfig } from '@playwright/test'; import dotenv from 'dotenv'; import path from 'path'; -import { operationTimeout, totalTimeout } from './src/connect/utils/Constants'; +const operationTimeout = 5000; if (process.env.CI) { dotenv.config({ path: path.resolve(__dirname, '.env.connect.ci'), override: true }); @@ -11,8 +11,7 @@ if (process.env.CI) { } export default defineConfig({ - testDir: './src/connect2', - // fullyParallel: true, + testDir: './src/connect', forbidOnly: !!process.env.CI, retries: 4, workers: process.env.CI @@ -20,26 +19,8 @@ export default defineConfig({ ? parseInt(process.env.PLAYWRIGHT_NUM_CORES, 10) - 1 : undefined : undefined, - reporter: [ - // [ - // '../../node_modules/playwright-slack-report/dist/src/SlackReporter.js', - // { - // channels: ['corbado-tests'], - // sendResults: 'always', - // showInThread: true, - // meta: [ - // { - // key: 'Test Run Info', - // value: `https://github.com/corbado/javascript/actions/runs/${process.env.GITHUB_RUN_ID}`, - // }, - // { key: 'branch', value: `${process.env.GITHUB_BRANCH_NAME}` }, - // ], - // }, - // ], - ['html'], - ['junit', { outputFile: 'test-results/results.xml' }], - ], - timeout: totalTimeout, // default: 30000ms + reporter: [['html'], ['junit', { outputFile: 'test-results/results.xml' }]], + timeout: 120000, // default: 30000ms expect: { timeout: operationTimeout, // default: 5000ms }, @@ -48,16 +29,23 @@ export default defineConfig({ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 15.3.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36', actionTimeout: operationTimeout, // default: none navigationTimeout: operationTimeout, // default: none - // baseURL: process.env.PLAYWRIGHT_TEST_URL, screenshot: 'only-on-failure', video: 'retain-on-failure', trace: 'retain-on-failure', }, projects: [ { - name: 'append-component', + name: 'append', testMatch: ['scenarios/append.spec.ts'], }, + { + name: 'login', + testMatch: ['scenarios/login.spec.ts'], + }, + { + name: 'network-blocking', + testMatch: ['scenarios/network-blocking.spec.ts'], + }, ], globalSetup: 'src/connect/utils/Playground.ts', }); diff --git a/packages/tests-e2e/src/connect/fixtures/BaseTest.ts b/packages/tests-e2e/src/connect/fixtures/BaseTest.ts deleted file mode 100644 index 5f427b53f..000000000 --- a/packages/tests-e2e/src/connect/fixtures/BaseTest.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { test as base } from '@playwright/test'; - -import { BaseModel } from '../models/BaseModel'; -import { CDPSessionManager } from '../utils/CDPSessionManager'; -import { NetworkRequestBlocker } from '../utils/NetworkRequestBlocker'; -import { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; - -export const test = base.extend<{ - model: BaseModel; -}>({ - model: async ({ page }, use) => { - const cdpManager = new CDPSessionManager(); - await cdpManager.initialize(page); - - const authenticator = new VirtualAuthenticator(cdpManager); - const blocker = new NetworkRequestBlocker(cdpManager); - - const model = new BaseModel(page, authenticator, blocker); - - await use(model); - }, -}); - -export { expect } from '@playwright/test'; diff --git a/packages/tests-e2e/src/connect/models/AppendModel.ts b/packages/tests-e2e/src/connect/models/AppendModel.ts deleted file mode 100644 index 83c9e0bd3..000000000 --- a/packages/tests-e2e/src/connect/models/AppendModel.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { Page } from '@playwright/test'; - -import { ErrorTexts, ScreenNames } from '../utils/Constants'; -import { expectError, expectScreen } from '../utils/ExpectScreen'; -import type { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; - -export class AppendModel { - page: Page; - authenticator: VirtualAuthenticator; - - constructor(page: Page, authenticator: VirtualAuthenticator) { - this.page = page; - this.authenticator = authenticator; - } - - appendPasskey(complete: boolean) { - const operationTrigger = () => this.page.getByRole('button', { name: 'Continue' }).click(); - if (complete) { - return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); - } else { - return this.authenticator.startAndCancelPasskeyOperation(operationTrigger, () => - expectError(this.page, ErrorTexts.CancelledPasskey), - ); - } - } - - autoAppendPasskey() { - // no-op - } - - confirmAppended() { - return this.page.getByRole('button', { name: 'Continue' }).click(); - } - - skipAppend() { - return this.page.locator('.cb-append-skip').click(); - } -} diff --git a/packages/tests-e2e/src/connect/models/BaseModel.ts b/packages/tests-e2e/src/connect/models/BaseModel.ts deleted file mode 100644 index 6bf9e4e19..000000000 --- a/packages/tests-e2e/src/connect/models/BaseModel.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { Page } from '@playwright/test'; - -import type { ErrorTexts } from '../utils/Constants'; -import { ScreenNames } from '../utils/Constants'; -import { expectError, expectScreen } from '../utils/ExpectScreen'; -import type { NetworkRequestBlocker } from '../utils/NetworkRequestBlocker'; -import type { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; -import { AppendModel } from './AppendModel'; -import { HomeModel } from './HomeModel'; -import { LoginModel } from './LoginModel'; -import { MFAModel } from './MFAModel'; -import { PasskeyListModel } from './PasskeyListModel'; -import { SignupModel } from './SignupModel'; -import { StorageModel } from './StorageModel'; -import { WebhookModel } from './WebhookModel'; - -export class BaseModel { - page: Page; - authenticator: VirtualAuthenticator; - blocker: NetworkRequestBlocker; - signup: SignupModel; - login: LoginModel; - append: AppendModel; - home: HomeModel; - passkeyList: PasskeyListModel; - webhook: WebhookModel; - storage: StorageModel; - mfa: MFAModel; - email = ''; - - constructor(page: Page, authenticator: VirtualAuthenticator, blocker: NetworkRequestBlocker) { - this.page = page; - this.authenticator = authenticator; - this.blocker = blocker; - this.login = new LoginModel(page, authenticator); - this.append = new AppendModel(page, authenticator); - this.signup = new SignupModel(page, this.append); - this.mfa = new MFAModel(page, this.append); - this.home = new HomeModel(page); - this.passkeyList = new PasskeyListModel(page, authenticator); - this.webhook = new WebhookModel(page); - this.storage = new StorageModel(page); - } - - loadSignup(port: number) { - return this.page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/signup`); - } - - loadLogin(port: number) { - return this.page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login`); - } - - loadHome(port: number) { - return this.page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/home`); - } - - expectScreen(screenName: ScreenNames) { - return expectScreen(this.page, screenName); - } - - expectError(message: ErrorTexts) { - return expectError(this.page, message); - } - - async createUser(invited: boolean, append: boolean) { - this.email = await this.signup.autofillCredentials(); - await this.signup.submit(); - this.mfa.registerTokenUsed(); - if (invited) { - if (append) { - await this.expectScreen(ScreenNames.PasskeyAppended); - await this.append.confirmAppended(); - } else { - await this.expectScreen(ScreenNames.PasskeyAppend); - await this.append.skipAppend(); - } - } - } -} diff --git a/packages/tests-e2e/src/connect2/models/BasePage.ts b/packages/tests-e2e/src/connect/models/BasePage.ts similarity index 72% rename from packages/tests-e2e/src/connect2/models/BasePage.ts rename to packages/tests-e2e/src/connect/models/BasePage.ts index 4a5d0a1d6..e695a4b91 100644 --- a/packages/tests-e2e/src/connect2/models/BasePage.ts +++ b/packages/tests-e2e/src/connect/models/BasePage.ts @@ -17,6 +17,10 @@ export abstract class BasePage { return this.page.getByRole('link', { name: label }).click(); } + clickText(text: string): Promise { + return this.page.getByText(text).click(); + } + async waitForHeading(text: string): Promise { try { await this.page.getByRole('heading', { name: text }).waitFor({ state: 'visible', timeout: 10000 }); @@ -56,4 +60,20 @@ export abstract class BasePage { expectText(text: string): Promise { return this.page.getByText(text).waitFor({ state: 'visible' }); } + + // client-state + async clearProcessState(): Promise { + return this.localStorageClearByPrefix('cbo_connect_process'); + } + + private localStorageClearByPrefix(prefix: string): Promise { + return this.page.evaluate(prefix => { + for (let i = localStorage.length - 1; i >= 0; i--) { + const key = localStorage.key(i); + if (key && key.startsWith(prefix)) { + localStorage.removeItem(key); + } + } + }, prefix); + } } diff --git a/packages/tests-e2e/src/connect/models/HomeModel.ts b/packages/tests-e2e/src/connect/models/HomeModel.ts deleted file mode 100644 index d412a4cd1..000000000 --- a/packages/tests-e2e/src/connect/models/HomeModel.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Page } from '@playwright/test'; - -export class HomeModel { - page: Page; - - constructor(page: Page) { - this.page = page; - } - - logout(): Promise { - return this.page.getByRole('button', { name: 'Logout' }).click(); - } - - gotoPasskeyList(): Promise { - return this.page.getByRole('button', { name: 'Passkey List' }).click(); - } -} diff --git a/packages/tests-e2e/src/connect/models/LoginModel.ts b/packages/tests-e2e/src/connect/models/LoginModel.ts deleted file mode 100644 index bdc9114df..000000000 --- a/packages/tests-e2e/src/connect/models/LoginModel.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { Page } from '@playwright/test'; -import { expect } from '@playwright/test'; - -import { ScreenNames } from '../utils/Constants'; -import { expectScreen } from '../utils/ExpectScreen'; -import type { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; - -export class LoginModel { - page: Page; - authenticator: VirtualAuthenticator; - - constructor(page: Page, authenticator: VirtualAuthenticator) { - this.page = page; - this.authenticator = authenticator; - } - - submitPasskeyButton(complete: boolean) { - const operationTrigger = () => this.page.locator('.cb-passkey-button').click(); - if (complete) { - return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); - } else { - return this.authenticator.startAndCancelPasskeyOperation(operationTrigger, () => - expectScreen(this.page, ScreenNames.PasskeyError1), - ); - } - } - - removePasskeyButton() { - return this.page.locator('.cb-switch').click(); - } - - async repeatedlyFailPasskeyInput() { - const operationTrigger1 = () => this.page.getByRole('button', { name: 'Continue' }).click(); - await this.authenticator.startAndCancelPasskeyOperation(operationTrigger1, () => - expectScreen(this.page, ScreenNames.PasskeyError2), - ); - - const operationTrigger2 = () => this.page.getByRole('button', { name: 'Try again' }).click(); - await this.authenticator.startAndCancelPasskeyOperation(operationTrigger2, () => this.page.waitForTimeout(100)); - await this.authenticator.startAndCancelPasskeyOperation(operationTrigger2, () => this.page.waitForTimeout(100)); - await this.authenticator.startAndCancelPasskeyOperation(operationTrigger2, () => - expectScreen(this.page, ScreenNames.InitLoginFallback), - ); - } - - async submitEmail(email: string, withPasskey: boolean) { - await this.page.getByLabel('Email address').fill(email); - if (withPasskey) { - const operationTrigger = () => this.page.getByRole('button', { name: 'Login' }).click(); - await this.authenticator.startAndCompletePasskeyOperation(operationTrigger); - } else { - await this.page.getByRole('button', { name: 'Login' }).click(); - } - } - - async submitFallbackCredentials(email: string, password: string, emailAutofilled = false) { - if (emailAutofilled) { - await expect(this.page.getByPlaceholder('Email')).toHaveValue(email); - } else { - await this.page.getByPlaceholder('Email').fill(email); - } - await this.page.getByPlaceholder('Password').fill(password); - await this.page.getByRole('button', { name: 'Login' }).click(); - } - - submitConditionalUI(operationTrigger: () => Promise) { - return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); - } -} diff --git a/packages/tests-e2e/src/connect/models/LoginPage.ts b/packages/tests-e2e/src/connect/models/LoginPage.ts new file mode 100644 index 000000000..38bc96f20 --- /dev/null +++ b/packages/tests-e2e/src/connect/models/LoginPage.ts @@ -0,0 +1,112 @@ +import type { Page } from '@playwright/test'; + +import { BasePage } from './BasePage'; +import { PostLoginPage } from './PostLoginPage'; +import { ProfilePage } from './ProfilePage'; +import { SignupPage } from './SignupPage'; + +export enum LoginStatus { + PasskeyOneTap, + PasskeyTextField, + FallbackFirst, + FallbackSecondTOTP, + PasskeyErrorSoft, +} + +export class LoginPage extends BasePage { + page: Page; + + constructor(page: Page) { + super(page); + this.page = page; + } + + visible(): Promise { + return this.waitForHeading('Login'); + } + + static async awaitPage(page: Page): Promise { + const loginPage = new LoginPage(page); + if (!(await loginPage.visible())) { + throw new Error('Login page not visible'); + } + + return loginPage; + } + + async navigateToSignup(): Promise { + await this.clickLink('Sign up'); + + return SignupPage.awaitPage(this.page); + } + + async awaitState(status: LoginStatus): Promise { + switch (status) { + case LoginStatus.PasskeyOneTap: + return this.waitForButton('Login with Passkey'); + case LoginStatus.PasskeyTextField: + return this.waitForButton('Login'); + case LoginStatus.FallbackFirst: + return this.waitForButton('Login'); + case LoginStatus.FallbackSecondTOTP: + return this.waitForHeading('Check your authenticator'); + case LoginStatus.PasskeyErrorSoft: + default: + return true; + } + } + + async loginWithOneTap(): Promise { + await this.clickButton('Login with Passkey'); + + return new ProfilePage(this.page); + } + + async loginWithOneTapAndCancel(): Promise { + await this.clickButton('Login with Passkey'); + // Stay on login page after cancel + return LoginPage.awaitPage(this.page); + } + + async switchAccount(): Promise { + await this.clickText('Switch account'); + } + + loginWithCUI(): ProfilePage { + return new ProfilePage(this.page); + } + + async loginWithIdentifier(email: string): Promise { + await this.page.getByLabel('Email address').fill(email); + await this.clickButton('Login'); + + return new ProfilePage(this.page); + } + + async loginWithIdentifierAndPasswordIdentifierFirst(email: string, password: string): Promise { + await this.page.getByLabel('Email address').fill(email); + await this.clickButton('Login'); + await this.page.getByLabel('Password').fill(password); + await this.clickButton('Login'); + } + + async loginWithIdentifierAndPassword(email: string, password: string) { + await this.page.getByLabel('Email address').fill(email); + await this.page.getByLabel('Password').fill(password); + await this.clickButton('Login'); + } + + async completeLoginWithTOTP(code: string): Promise { + await this.page.getByRole('textbox').fill(code); + return new PostLoginPage(this.page); + } + + async loginWithIdentifierButNoSuccess(email: string): Promise { + await this.page.getByLabel('Email address').fill(email); + await this.clickButton('Login'); + } + + async awaitErrorMessage(text: string): Promise { + return this.waitForText(text); + } +} diff --git a/packages/tests-e2e/src/connect/models/MFAModel.ts b/packages/tests-e2e/src/connect/models/MFAModel.ts deleted file mode 100644 index c2425b0bd..000000000 --- a/packages/tests-e2e/src/connect/models/MFAModel.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Page } from '@playwright/test'; - -import type { AppendModel } from './AppendModel'; - -export class MFAModel { - page: Page; - append: AppendModel; - timestamp?: number; - - constructor(page: Page, append: AppendModel) { - this.page = page; - this.append = append; - } - - registerTokenUsed() { - this.timestamp = Date.now(); - } - - async autofillTOTP() { - if (this.timestamp) { - await this.page.waitForTimeout(31000 - (Date.now() - this.timestamp)); - } - - await this.page.getByRole('button', { name: 'Autofill' }).click(); - this.registerTokenUsed(); - } - - submit(invited: boolean, autoAppend: boolean) { - return this.page.getByRole('button', { name: 'Submit' }).click(); - } -} diff --git a/packages/tests-e2e/src/connect/models/MFAPage.ts b/packages/tests-e2e/src/connect/models/MFAPage.ts new file mode 100644 index 000000000..7ca80b5aa --- /dev/null +++ b/packages/tests-e2e/src/connect/models/MFAPage.ts @@ -0,0 +1,48 @@ +import type { Page } from '@playwright/test'; + +import type { AuthenticatorApp } from '../utils/AuthenticatorApp'; +import { BasePage } from './BasePage'; +import { PostLoginPage } from './PostLoginPage'; +import { ProfilePage } from './ProfilePage'; + +export class MFAPage extends BasePage { + constructor(page: Page) { + super(page); + } + + async visible(): Promise { + return this.waitForHeading('Protect your account'); + } + + async awaitPage(): Promise { + if (!(await this.visible())) { + throw new Error('MFA page not visible'); + } + + return this; + } + + async setupAndConfirmTOTPReturnProfile(authenticator: AuthenticatorApp): Promise<[string, ProfilePage]> { + await this.clickButton('Use Authenticator instead'); + + const sharedKey = await this.page.getByRole('img').getAttribute('aria-label'); + if (!sharedKey) throw new Error('sharedKey not found'); + + await this.clickButton('Continue'); + + const code1 = await authenticator.addBySecret(sharedKey); + if (!code1) throw new Error('Failed to add TOTP secret'); + await this.inputTOTP(code1); + + return [sharedKey, new ProfilePage(this.page)]; + } + + async confirm(code: string): Promise { + await this.page.keyboard.type(code); + return new PostLoginPage(this.page); + } + + async inputTOTP(code: string): Promise { + await this.page.getByRole('textbox').fill(code); + } +} diff --git a/packages/tests-e2e/src/connect/models/PasskeyListModel.ts b/packages/tests-e2e/src/connect/models/PasskeyListModel.ts deleted file mode 100644 index 7644268f4..000000000 --- a/packages/tests-e2e/src/connect/models/PasskeyListModel.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Page } from '@playwright/test'; -import { expect } from '@playwright/test'; - -import type { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; - -export class PasskeyListModel { - page: Page; - authenticator: VirtualAuthenticator; - - constructor(page: Page, authenticator: VirtualAuthenticator) { - this.page = page; - this.authenticator = authenticator; - } - - expectPasskeys(n: number) { - return expect(this.page.locator('.cb-passkey-list-item')).toHaveCount(n); - } - - async deletePasskey(index: number) { - await this.page.locator('.cb-passkey-list-item-delete-icon').nth(index).click(); - await this.page.getByRole('button', { name: 'Delete' }).click(); - } - - createPasskey(complete: boolean, postOperationCheck: (() => Promise) | null = null) { - const operationTrigger = (): Promise => this.page.getByRole('button', { name: 'Add a passkey' }).click(); - if (complete) { - if (postOperationCheck === null) { - return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); - } else { - return this.authenticator.startAndCompletePasskeyOperation(operationTrigger, postOperationCheck); - } - } else { - return this.authenticator.startAndCancelPasskeyOperation(operationTrigger, () => - expect(this.page.locator('.cb-notification-text')).toHaveText( - 'You have cancelled setting up your passkey. Please try again.', - ), - ); - } - } - - checkCreatePasskeyDisabled() { - return expect(this.page.getByRole('button', { name: 'Add a passkey' })).not.toBeVisible(); - } - - confirmModal() { - return this.page.getByRole('button', { name: 'Okay' }).click(); - } -} diff --git a/packages/tests-e2e/src/connect2/models/PostLoginPage.ts b/packages/tests-e2e/src/connect/models/PostLoginPage.ts similarity index 70% rename from packages/tests-e2e/src/connect2/models/PostLoginPage.ts rename to packages/tests-e2e/src/connect/models/PostLoginPage.ts index dc7bec9b1..e87ba3994 100644 --- a/packages/tests-e2e/src/connect2/models/PostLoginPage.ts +++ b/packages/tests-e2e/src/connect/models/PostLoginPage.ts @@ -13,13 +13,20 @@ export class PostLoginPage extends BasePage { return this.waitForHeading('Simplify Your Login'); } - static async awaitPage(page: Page): Promise { - const postLoginPage = new PostLoginPage(page); - if (!(await postLoginPage.visible())) { + autoSkipAfterSignup(): MFAPage { + return new MFAPage(this.page); + } + + autoSkip(): ProfilePage { + return new ProfilePage(this.page); + } + + async awaitPage(): Promise { + if (!(await this.visible())) { throw new Error('Post login page not visible'); } - return postLoginPage; + return this; } async awaitErrorMessage(text: string): Promise { @@ -34,26 +41,24 @@ export class PostLoginPage extends BasePage { await this.expectText('Passkey Created Successfully'); await this.clickButton('Continue'); - return ProfilePage.awaitPage(this.page); + return new ProfilePage(this.page); } - async continueWithCancel(expectAutoAppend: boolean): Promise { + async continueWithCancel(expectAutoAppend: boolean): Promise { if (!expectAutoAppend) { await this.clickButton('Continue'); } - - return PostLoginPage.awaitPage(this.page); } async skip(): Promise { - await this.clickButton('Skip'); + await this.clickText('Skip'); return new ProfilePage(this.page); } async skipAfterSignup(): Promise { - await this.clickButton('Skip'); + await this.clickText('Skip'); - return new MFAPage(this.page); + return new MFAPage(this.page).awaitPage(); } } diff --git a/packages/tests-e2e/src/connect2/models/ProfilePage.ts b/packages/tests-e2e/src/connect/models/ProfilePage.ts similarity index 56% rename from packages/tests-e2e/src/connect2/models/ProfilePage.ts rename to packages/tests-e2e/src/connect/models/ProfilePage.ts index cf24b3278..360d088e0 100644 --- a/packages/tests-e2e/src/connect2/models/ProfilePage.ts +++ b/packages/tests-e2e/src/connect/models/ProfilePage.ts @@ -7,6 +7,7 @@ export enum ProfileStatus { ListEmpty, ListWithInitialError, ListWithPasskeys, + ListWithoutPasskeySupport, } export class ProfilePage extends BasePage { @@ -17,11 +18,12 @@ export class ProfilePage extends BasePage { this.page = page; } - static async awaitPage(page: Page): Promise { - const profilePage = new ProfilePage(page); - await profilePage.visible(); + async awaitPage(): Promise { + if (!(await this.visible())) { + throw new Error('Profile page not visible'); + } - return profilePage; + return this; } visible(): Promise { @@ -38,16 +40,28 @@ export class ProfilePage extends BasePage { return this.clickButton('Add a passkey'); } - async deletePasskeyByIndex(index: string, complete: boolean) {} + async deletePasskeyByIndex(index: number, complete: boolean) { + const item = this.page.locator('div.cb-passkey-list-item-delete-icon').nth(index); + await item.click(); + if (complete) { + await this.page.locator('.cb-modal').getByRole('button', { name: 'Delete' }).click(); + } else { + await this.page.locator('.cb-modal').getByRole('button', { name: 'Cancel' }).click(); + } + } async awaitState(status: ProfileStatus): Promise { switch (status) { case ProfileStatus.ListEmpty: - return this.waitForButton('No passkeys'); + return this.waitForText('There is currently no passkey saved for this account.'); case ProfileStatus.ListWithInitialError: - return this.waitForText('Error loading passkeys'); + return this.waitForText('We were unable to show you your list of passkeys due to an error. Try again later.'); case ProfileStatus.ListWithPasskeys: return this.waitBySelector('div.cb-passkey-list-item-delete-icon'); + case ProfileStatus.ListWithoutPasskeySupport: { + const appendAllowed = await this.waitForText('Add a passkey'); + return !appendAllowed; + } } } diff --git a/packages/tests-e2e/src/connect/models/SignupModel.ts b/packages/tests-e2e/src/connect/models/SignupModel.ts deleted file mode 100644 index d98db76a0..000000000 --- a/packages/tests-e2e/src/connect/models/SignupModel.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Page } from '@playwright/test'; - -import type { AppendModel } from './AppendModel'; - -export class SignupModel { - page: Page; - append: AppendModel; - - constructor(page: Page, append: AppendModel) { - this.page = page; - this.append = append; - } - - async autofillCredentials(): Promise { - await this.page.getByRole('button', { name: 'auto' }).click(); - return await this.page.getByPlaceholder('Email').inputValue(); - } - - submit() { - return this.page.getByRole('button', { name: 'Sign up' }).click(); - } -} diff --git a/packages/tests-e2e/src/connect2/models/SignupPage.ts b/packages/tests-e2e/src/connect/models/SignupPage.ts similarity index 96% rename from packages/tests-e2e/src/connect2/models/SignupPage.ts rename to packages/tests-e2e/src/connect/models/SignupPage.ts index cd05bb11a..25f026314 100644 --- a/packages/tests-e2e/src/connect2/models/SignupPage.ts +++ b/packages/tests-e2e/src/connect/models/SignupPage.ts @@ -1,8 +1,8 @@ import type { Page } from '@playwright/test'; +import { BasePage } from './BasePage'; import { LoginPage } from './LoginPage'; import { PostLoginPage } from './PostLoginPage'; -import { BasePage } from './BasePage'; export class SignupPage extends BasePage { private readonly page: Page; @@ -37,6 +37,6 @@ export class SignupPage extends BasePage { await this.page.getByPlaceholder('Password').fill(password); await this.page.getByRole('button', { name: 'Sign up' }).click(); - return PostLoginPage.awaitPage(this.page); + return new PostLoginPage(this.page); } } diff --git a/packages/tests-e2e/src/connect/models/StorageModel.ts b/packages/tests-e2e/src/connect/models/StorageModel.ts deleted file mode 100644 index def5c3283..000000000 --- a/packages/tests-e2e/src/connect/models/StorageModel.ts +++ /dev/null @@ -1,136 +0,0 @@ -import type { Page } from '@playwright/test'; -import { expect } from '@playwright/test'; - -import { ScreenNames } from '../utils/Constants'; -import { expectScreen } from '../utils/ExpectScreen'; - -export class StorageModel { - page: Page; - - constructor(page: Page) { - this.page = page; - } - - async loadInvitationToken(port: number) { - await this.page.goto( - `${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`, - ); - await expectScreen(this.page, ScreenNames.InitLogin); - } - - async checkInvitationToken() { - const cboConnectInvitationRaw = await this.page.evaluate(k => localStorage.getItem(k), 'cbo_connect_invitation'); - if (!cboConnectInvitationRaw) { - throw new Error('cbo_connect_invitation not found in local storage'); - } - const cboConnectInvitation = JSON.parse(cboConnectInvitationRaw); - expect(cboConnectInvitation.token).toEqual('inv-token-correct'); - } - - deleteInvitationToken() { - return this.page.evaluate(k => localStorage.removeItem(k), 'cbo_connect_invitation'); - } - - async getProcessID(): Promise { - const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; - const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); - if (!cboConnectProcessRaw) { - throw new Error(`cbo_connect_process not found in local storage ${key}`); - } - const cboConnectProcess = JSON.parse(cboConnectProcessRaw); - expect(cboConnectProcess.id).not.toBeNull(); - - return cboConnectProcess.id; - } - - async checkProcessID(expectedID: string) { - expect(await this.getProcessID()).toEqual(expectedID); - } - - async getLoginLifetime(): Promise { - const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; - const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); - if (!cboConnectProcessRaw) { - throw new Error(`cbo_connect_process not found in local storage ${key}`); - } - const cboConnectProcess = JSON.parse(cboConnectProcessRaw); - return cboConnectProcess.loginData.expiresAt; - } - - async setLoginLifetime(newLifetime: number) { - const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; - const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); - if (!cboConnectProcessRaw) { - throw new Error(`cbo_connect_process not found in local storage ${key}`); - } - const cboConnectProcess = JSON.parse(cboConnectProcessRaw); - cboConnectProcess.loginData.expiresAt = newLifetime; - await this.page.evaluate(({ k, p }) => localStorage.setItem(k, JSON.stringify(p)), { - k: key, - p: cboConnectProcess, - }); - } - - async getAppendLifetime(): Promise { - const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; - const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); - if (!cboConnectProcessRaw) { - throw new Error(`cbo_connect_process not found in local storage ${key}`); - } - const cboConnectProcess = JSON.parse(cboConnectProcessRaw); - return cboConnectProcess.appendData.expiresAt; - } - - async setAppendLifetime(newLifetime: number) { - const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; - const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); - if (!cboConnectProcessRaw) { - throw new Error(`cbo_connect_process not found in local storage ${key}`); - } - const cboConnectProcess = JSON.parse(cboConnectProcessRaw); - cboConnectProcess.appendData.expiresAt = newLifetime; - await this.page.evaluate(({ k, p }) => localStorage.setItem(k, JSON.stringify(p)), { - k: key, - p: cboConnectProcess, - }); - } - - async getManageLifetime(): Promise { - const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; - const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); - if (!cboConnectProcessRaw) { - throw new Error(`cbo_connect_process not found in local storage ${key}`); - } - const cboConnectProcess = JSON.parse(cboConnectProcessRaw); - return cboConnectProcess.manageData.expiresAt; - } - - async setManageLifetime(newLifetime: number) { - const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; - const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); - if (!cboConnectProcessRaw) { - throw new Error(`cbo_connect_process not found in local storage ${key}`); - } - const cboConnectProcess = JSON.parse(cboConnectProcessRaw); - cboConnectProcess.manageData.expiresAt = newLifetime; - await this.page.evaluate(({ k, p }) => localStorage.setItem(k, JSON.stringify(p)), { - k: key, - p: cboConnectProcess, - }); - } - - async checkLoginDataDeleted() { - const key = `cbo_connect_process-${process.env.PLAYWRIGHT_CONNECT_PROJECT_ID}`; - const cboConnectProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); - if (!cboConnectProcessRaw) { - throw new Error(`cbo_connect_process not found in local storage ${key}`); - } - const cboConnectProcess = JSON.parse(cboConnectProcessRaw); - expect(cboConnectProcess.loginData).toBeNull(); - } - - async clearLocalStorageAndCookies() { - await this.page.evaluate(() => localStorage.clear()); - await this.page.context().clearCookies(); - } -} diff --git a/packages/tests-e2e/src/connect/models/WebhookModel.ts b/packages/tests-e2e/src/connect/models/WebhookModel.ts deleted file mode 100644 index 8c99f12a9..000000000 --- a/packages/tests-e2e/src/connect/models/WebhookModel.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { createServer } from 'node:http'; -import type { Server } from 'node:net'; - -import type { Page } from '@playwright/test'; -import { expect } from '@playwright/test'; -import ngrok from 'ngrok'; - -import { WebhookTypes } from '../utils/Constants'; - -export class WebhookModel { - page: Page; - webhookServer: Server | null = null; - webhookEndpointID: string | null = null; - latestWebhookType: WebhookTypes | null = null; - - constructor(page: Page) { - this.page = page; - } - - async createWebhookEndpoint(webhookTypes: WebhookTypes[]) { - if (this.webhookServer || this.webhookEndpointID) { - throw new Error('Webhook endpoint already created'); - } - - if (!process.env.PLAYWRIGHT_CONNECT_PROJECT_ID) { - throw new Error('PLAYWRIGHT_CONNECT_PROJECT_ID not set'); - } - - const port = 3001; - - await new Promise(resolve => { - this.webhookServer = createServer((req, res) => { - if (req.method === 'POST' && req.url === '/webhook') { - let body = ''; - req.on('data', chunk => (body += chunk)); - req.on('end', () => { - const receivedWebhookType = JSON.parse(body).type; - expect(Object.values(WebhookTypes)).toContain(receivedWebhookType); - this.latestWebhookType = receivedWebhookType as WebhookTypes; - res.writeHead(200); - res.end('OK'); - }); - } else { - res.writeHead(404); - res.end(); - } - }); - this.webhookServer.listen(port, () => { - console.log(`Webhook server running at ${process.env.PLAYWRIGHT_TEST_URL}:${port}`); - resolve(this.webhookServer); - }); - }); - - const publicUrl = await ngrok.connect({ - addr: port, - authtoken: process.env.PLAYWRIGHT_NGROK_AUTH_TOKEN, - }); - const createRes = await fetch(`${process.env.CORBADO_BACKEND_API_URL}/v2/webhookEndpoints`, { - method: 'POST', - headers: { - Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - url: `${publicUrl}/webhook`, - subscribedEvents: webhookTypes, - customHeaders: { 'X-Custom-Header': 'custom-value' }, - }), - }); - expect(createRes.ok).toBeTruthy(); - - const res_data = await createRes.json(); - console.log(res_data); - this.webhookEndpointID = res_data.id; - } - - expectWebhookRequest(webhookType: WebhookTypes) { - expect(this.latestWebhookType).toEqual(webhookType); - } - - async deleteWebhookEndpoint() { - if (!this.webhookServer || !this.webhookEndpointID) { - throw new Error('Webhook endpoint not yet created'); - } - - if (!process.env.PLAYWRIGHT_CONNECT_PROJECT_ID) { - throw new Error('PLAYWRIGHT_CONNECT_PROJECT_ID not set'); - } - - const deleteRes = await fetch( - `${process.env.CORBADO_BACKEND_API_URL}/v2/webhookEndpoints/${this.webhookEndpointID}`, - { - method: 'DELETE', - headers: { - Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, - 'Content-Type': 'application/json', - }, - }, - ); - expect(deleteRes.ok).toBeTruthy(); - - this.webhookServer.close(); - await ngrok.disconnect(); - } -} diff --git a/packages/tests-e2e/src/connect/scenarios/append.spec.ts b/packages/tests-e2e/src/connect/scenarios/append.spec.ts index 363b96ab8..365abf8b3 100644 --- a/packages/tests-e2e/src/connect/scenarios/append.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/append.spec.ts @@ -1,13 +1,15 @@ import type { ChildProcess } from 'node:child_process'; -import { expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; -import { test } from '../fixtures/BaseTest'; -import { password, ScreenNames } from '../utils/Constants'; -import { loadBeforePasskeyAppend, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; +import { LoginPage, LoginStatus } from '../models/LoginPage'; +import { ProfileStatus } from '../models/ProfilePage'; +import { AuthenticatorApp } from '../utils/AuthenticatorApp'; +import { TestDataFactory } from '../utils/TestDataFactory'; +import { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; -test.describe('append component', () => { +test.describe('append flows', () => { let server: ChildProcess; let port: number; @@ -19,83 +21,89 @@ test.describe('append component', () => { killPlaygroundNew(server); }); - setupVirtualAuthenticator(test); - setupNetworkBlocker(test); - setupUser(test, () => port, true, false); - loadBeforePasskeyAppend(test); - - test('successful passkey append on login', async ({ model }) => { - await model.authenticator.runWithComplete(async () => { - await model.mfa.autofillTOTP(); - model.append.autoAppendPasskey(); - await model.expectScreen(ScreenNames.PasskeyAppended); - await model.append.confirmAppended(); - }); - - await model.expectScreen(ScreenNames.Home); - }); - - test('failed passkey append on login', async ({ model }) => { - await model.mfa.submit(true, false); - }); - - test('Corbado FAPI unavailable after authentication', async ({ model }) => { - await model.blocker.blockCorbadoFAPIFinishEndpoint(); - - await model.mfa.submit(true, true); - await model.expectScreen(ScreenNames.Home); - }); -}); - -test.describe('skip append component', () => { - let server: ChildProcess; - let port: number; - - test.beforeAll(async () => { - ({ server, port } = await spawnPlaygroundNew()); + test('testAppendAfterSignUp', async ({ page }) => { + await page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); + const loginPage = new LoginPage(page); + const signupPage = await loginPage.navigateToSignup(); + const virtualAuthenticator = await VirtualAuthenticator.init(page); + + const email = TestDataFactory.generateEmail(); + + await virtualAuthenticator.modeCancel(); + const postLoginPage = await signupPage.submit(email, TestDataFactory.phoneNumber, TestDataFactory.password); + await postLoginPage.continueWithCancel(true); + await postLoginPage.continueWithCancel(false); + expect( + await postLoginPage.awaitErrorMessage('You have cancelled setting up your passkey. Please try again.'), + ).toBeTruthy(); + + await virtualAuthenticator.modeComplete(); + const profilePage = await postLoginPage.continue(false); + expect(await profilePage.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); + expect(await profilePage.getPasskeyCount()).toBe(1); + const loginPage2 = await profilePage.logout(); + + expect(await loginPage2.awaitState(LoginStatus.PasskeyOneTap)).toBeTruthy(); + const postLoginPage4 = await loginPage2.loginWithOneTap(); + expect(await profilePage.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); + await postLoginPage4.appendPasskey(); + await postLoginPage4.awaitErrorMessage('No passkey created'); + expect(await profilePage.getPasskeyCount()).toBe(1); }); - test.afterAll(() => { - killPlaygroundNew(server); - }); - - setupVirtualAuthenticator(test); - setupNetworkBlocker(test); - setupUser(test, () => port, true, false); - - test('Corbado FAPI unavailable', async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLogin); - - await model.login.submitEmail(model.email, false); - await model.expectScreen(ScreenNames.InitLoginFallback); - - await model.blocker.blockCorbadoFAPI(); - - await model.login.submitFallbackCredentials(model.email, password, true); - await model.expectScreen(ScreenNames.MFA); - - await model.mfa.autofillTOTP(); - await model.mfa.submit(false, false); - - await model.expectScreen(ScreenNames.Home); - }); - - test('expired append lifetime leads to skipped append screen', async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLogin); - - await model.login.submitEmail(model.email, false); - await model.expectScreen(ScreenNames.InitLoginFallback); - expect(await model.storage.getAppendLifetime()).toBeGreaterThan(Math.floor(Date.now() / 1000)); - - await model.storage.setAppendLifetime(Math.floor(Date.now() / 1000) - 1); - await model.storage.deleteInvitationToken(); - await model.login.submitFallbackCredentials(model.email, password, true); - await model.expectScreen(ScreenNames.MFA); - - await model.mfa.autofillTOTP(); - await model.mfa.submit(false, false); - await model.expectScreen(ScreenNames.Home); + test('testAppendAfterSignUpSkipped', async ({ page }) => { + test.setTimeout(120000); // 120 seconds + await page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); + const loginPage = new LoginPage(page); + const signupPage = await loginPage.navigateToSignup(); + const virtualAuthenticator = await VirtualAuthenticator.init(page); + + const email = TestDataFactory.generateEmail(); + + // First attempt to create passkey is cancelled, user skips, then sets up TOTP and logs in later + await virtualAuthenticator.modeCancel(); + const postLoginPage = await signupPage.submit(email, TestDataFactory.phoneNumber, TestDataFactory.password); + const mfaPage = await postLoginPage.skipAfterSignup(); + + // Confirm TOTP to end on profile + const authenticator = new AuthenticatorApp(); + const [sharedKey, profilePage] = await mfaPage.setupAndConfirmTOTPReturnProfile(authenticator); + + // Initially no passkeys + expect(await profilePage.getPasskeyCount()).toBe(0); + + const loginPage2 = await profilePage.logout(); + await virtualAuthenticator.modeCancel(); + expect(await loginPage2.awaitState(LoginStatus.PasskeyTextField)).toBeTruthy(); + await loginPage2.loginWithIdentifierAndPasswordIdentifierFirst(email, TestDataFactory.password); + expect(await loginPage2.awaitState(LoginStatus.FallbackSecondTOTP)).toBeTruthy(); + + // MFA page appears, confirm with next code + const codeFirst = await authenticator.getCode(sharedKey); + const postLoginPage2 = await loginPage2.completeLoginWithTOTP(codeFirst!); + const profilePage2 = await postLoginPage2.skip(); + + await virtualAuthenticator.modeComplete(); + expect(await profilePage2.awaitState(ProfileStatus.ListEmpty)).toBeTruthy(); + await profilePage2.appendPasskey(); + expect(await profilePage2.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); + const loginPage3 = await profilePage2.logout(); + + expect(await loginPage3.awaitState(LoginStatus.PasskeyOneTap)).toBeTruthy(); + const profilePage3 = await loginPage3.loginWithOneTap(); + expect(await profilePage3.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); + expect(await profilePage3.getPasskeyCount()).toBe(1); + await profilePage3.deletePasskeyByIndex(0, true); + expect(await profilePage3.awaitState(ProfileStatus.ListEmpty)).toBeTruthy(); + + await virtualAuthenticator.modeCancel(); + const loginPage4 = await profilePage3.logout(); + expect(await loginPage4.awaitState(LoginStatus.PasskeyTextField)).toBeTruthy(); + await virtualAuthenticator.modeComplete(); + await loginPage4.loginWithIdentifierAndPasswordIdentifierFirst(email, TestDataFactory.password); + expect(await loginPage4.awaitState(LoginStatus.FallbackSecondTOTP)).toBeTruthy(); + const codeSecond = await authenticator.getCode(sharedKey); + const postLoginPage3 = await loginPage4.completeLoginWithTOTP(codeSecond!); + await postLoginPage3.awaitPage(); }); }); diff --git a/packages/tests-e2e/src/connect/scenarios/hooks.ts b/packages/tests-e2e/src/connect/scenarios/hooks.ts deleted file mode 100644 index e3d592237..000000000 --- a/packages/tests-e2e/src/connect/scenarios/hooks.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type { - PlaywrightTestArgs, - PlaywrightTestOptions, - PlaywrightWorkerArgs, - PlaywrightWorkerOptions, - TestType, -} from '@playwright/test'; - -import type { BaseModel } from '../models/BaseModel'; -import type { WebhookTypes } from '../utils/Constants'; -import { password, ScreenNames } from '../utils/Constants'; - -export function setupVirtualAuthenticator( - test: TestType< - PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, - PlaywrightWorkerArgs & PlaywrightWorkerOptions - >, -) { - test.beforeEach(async ({ model, page }) => { - await model.authenticator.addWebAuthn(); - }); - - test.afterEach(async ({ model }) => { - await model.authenticator.removeWebAuthn(); - }); -} - -export function setupNetworkBlocker( - test: TestType< - PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, - PlaywrightWorkerArgs & PlaywrightWorkerOptions - >, -) { - test.beforeEach(async ({ model }) => { - await model.blocker.enableBlocking(); - }); -} - -export function setupWebhooks( - test: TestType< - PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, - PlaywrightWorkerArgs & PlaywrightWorkerOptions - >, - webhookTypes: WebhookTypes[], -) { - test.beforeEach(async ({ model }) => { - await model.webhook.createWebhookEndpoint(webhookTypes); - }); - - test.afterEach(async ({ model }) => { - await model.webhook.deleteWebhookEndpoint(); - }); -} - -export function loadInvitationToken( - test: TestType< - PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, - PlaywrightWorkerArgs & PlaywrightWorkerOptions - >, - getPort: () => number, -) { - test.beforeEach(async ({ model }) => { - await model.storage.loadInvitationToken(getPort()); - }); -} - -export function setupUser( - test: TestType< - PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, - PlaywrightWorkerArgs & PlaywrightWorkerOptions - >, - getPort: () => number, - invited = true, - append = true, -) { - test.beforeEach(async ({ model }) => { - if (invited) { - await model.storage.loadInvitationToken(getPort()); - } - await model.loadSignup(getPort()); - await model.expectScreen(ScreenNames.InitSignup); - await model.createUser(invited, append); - await model.expectScreen(ScreenNames.Home); - }); -} - -// assumes that setupUser(test, true, false) has been called right before -export function loadBeforePasskeyAppend( - test: TestType< - PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, - PlaywrightWorkerArgs & PlaywrightWorkerOptions - >, -) { - test.beforeEach(async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLogin); - - await model.login.submitEmail(model.email, false); - await model.expectScreen(ScreenNames.InitLoginFallback); - - await model.login.submitFallbackCredentials(model.email, password, true); - await model.expectScreen(ScreenNames.MFA); - }); -} - -// assumes that setupUser(test, true, true) has been called right before -export function loadPasskeyList( - test: TestType< - PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, - PlaywrightWorkerArgs & PlaywrightWorkerOptions - >, -) { - test.beforeEach(async ({ model }) => { - await model.home.gotoPasskeyList(); - await model.expectScreen(ScreenNames.PasskeyList); - }); -} diff --git a/packages/tests-e2e/src/connect/scenarios/login.spec.ts b/packages/tests-e2e/src/connect/scenarios/login.spec.ts index 7c0c8dacd..066dfcda2 100644 --- a/packages/tests-e2e/src/connect/scenarios/login.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/login.spec.ts @@ -1,13 +1,15 @@ import type { ChildProcess } from 'node:child_process'; -import { expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; -import { test } from '../fixtures/BaseTest'; -import { ErrorTexts, password, ScreenNames } from '../utils/Constants'; +import { LoginPage, LoginStatus } from '../models/LoginPage'; +import { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; +import { TestDataFactory } from '../utils/TestDataFactory'; +import { ProfileStatus } from '../models/ProfilePage'; +import { AuthenticatorApp } from '../utils/AuthenticatorApp'; import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; -import { loadInvitationToken, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; -test.describe('login component (without invitation token)', () => { +test.describe('login flows', () => { let server: ChildProcess; let port: number; @@ -19,222 +21,125 @@ test.describe('login component (without invitation token)', () => { killPlaygroundNew(server); }); - setupUser(test, () => port, false); + test('testLoginWithTextField', async ({ page }) => { + await page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); + const loginPage = new LoginPage(page); + const signupPage = await loginPage.navigateToSignup(); + const virtualAuthenticator = await VirtualAuthenticator.init(page); + await virtualAuthenticator.modeComplete(); - test('successful login with credentials', async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLoginFallback); + const email = TestDataFactory.generateEmail(); + const postLoginPage = await signupPage.submit(email, TestDataFactory.phoneNumber, TestDataFactory.password); + const profilePage = await postLoginPage.continue(true); + await profilePage.awaitPage(); - await model.login.submitFallbackCredentials(model.email, password); - await model.expectScreen(ScreenNames.MFA); - - await model.mfa.autofillTOTP(); - await model.expectScreen(ScreenNames.Home); - }); -}); - -test.describe('login component (with invitation token, without passkeys)', () => { - let server: ChildProcess; - let port: number; - - test.beforeAll(async () => { - ({ server, port } = await spawnPlaygroundNew()); - }); - - test.afterAll(() => { - killPlaygroundNew(server); - }); - - setupVirtualAuthenticator(test); - setupUser(test, () => port, true, false); - - test('successful login with credentials', async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLogin); - - await model.login.submitEmail(model.email, false); - await model.expectScreen(ScreenNames.InitLoginFallback); - - await model.login.submitFallbackCredentials(model.email, password, true); - await model.expectScreen(ScreenNames.MFA); - - await model.authenticator.runWithCancel(async () => { - await model.mfa.autofillTOTP(); - await model.append.skipAppend(); - }); - - await model.expectScreen(ScreenNames.Home); - }); -}); - -test.describe('login component (with invitation token, with passkeys)', () => { - let server: ChildProcess; - let port: number; - - test.beforeAll(async () => { - ({ server, port } = await spawnPlaygroundNew()); - }); - - test.afterAll(() => { - killPlaygroundNew(server); - }); - - setupVirtualAuthenticator(test); - setupNetworkBlocker(test); - setupUser(test, () => port, true, true); - - test('successful login with passkey', async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLoginOneTap); - - await model.login.removePasskeyButton(); - await model.expectScreen(ScreenNames.InitLogin); - - await model.login.submitEmail(model.email, true); - await model.expectScreen(ScreenNames.Home); - }); - - test('successful login with passkey (conditional UI)', async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLoginOneTap); - - await model.login.submitConditionalUI(async () => { - await model.login.removePasskeyButton(); - }); - await model.expectScreen(ScreenNames.Home); - }); - - test('successful login with passkey (one-tap)', async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLoginOneTap); - - await model.login.submitPasskeyButton(true); - await model.expectScreen(ScreenNames.Home); - }); - - test('attempt login with repeated failed passkey input', async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLoginOneTap); - - await model.login.submitPasskeyButton(false); - await model.login.repeatedlyFailPasskeyInput(); - }); - - test('Corbado FAPI unavailable after authentication', async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLoginOneTap); - - await model.blocker.blockCorbadoFAPIFinishEndpoint(); - - await model.login.submitPasskeyButton(true); - await model.expectScreen(ScreenNames.InitLoginFallback); - }); - - test('passkey signature validation fails', async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLoginOneTap); - - await model.authenticator.clearCredentials(); - await model.authenticator.addDummyCredential(); - - await model.login.submitConditionalUI(async () => { - await model.login.removePasskeyButton(); - }); - await model.expectScreen(ScreenNames.InitLoginFallback); - await model.expectError(ErrorTexts.PasskeySignatureValidationFail); - }); - - test('attempt login with server-side deleted passkey', async ({ model }) => { - await model.home.gotoPasskeyList(); - await model.expectScreen(ScreenNames.PasskeyList); - - await model.passkeyList.expectPasskeys(1); - await model.passkeyList.deletePasskey(0); - await model.passkeyList.expectPasskeys(0); - - await model.loadHome(port); - await model.expectScreen(ScreenNames.Home); - - await model.login.submitConditionalUI(async () => { - await model.home.logout(); - }); - await model.expectScreen(ScreenNames.InitLoginFallback); - await model.expectError(ErrorTexts.DeletedPasskeyUsed); - }); - - // TODO: unskip when loginData reset feature is fixed - test.skip('successful login deletes loginData', async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLoginOneTap); - - await model.login.removePasskeyButton(); - await model.expectScreen(ScreenNames.InitLogin); - - await model.login.submitEmail(model.email, true); - await model.expectScreen(ScreenNames.Home); - await model.storage.checkLoginDataDeleted(); - }); -}); - -test.describe('login component (without user)', () => { - let server: ChildProcess; - let port: number; - - test.beforeAll(async () => { - ({ server, port } = await spawnPlaygroundNew()); - }); - - test.afterAll(() => { - killPlaygroundNew(server); - }); - - setupVirtualAuthenticator(test); - setupNetworkBlocker(test); - loadInvitationToken(test, () => port); - - test('attempt login with incomplete credentials', async ({ model }) => { - await model.loadLogin(port); - await model.expectScreen(ScreenNames.InitLogin); - - await model.login.submitEmail('', false); - await model.expectError(ErrorTexts.EmptyEmail); - }); - - test('attempt login with unknown credentials', async ({ model }) => { - await model.loadLogin(port); - await model.expectScreen(ScreenNames.InitLogin); - - await model.login.submitEmail('integration-test+unknown@corbado.com', false); - await model.expectError(ErrorTexts.UnknownEmail); - }); - - test('Corbado FAPI unavailable', async ({ model }) => { - await model.blocker.blockCorbadoFAPI(); - - await model.loadLogin(port); - // It seems that the InitLogin page is now cached so that email needs to be submitted before reaching the InitLoginFallback screen. - await model.login.submitEmail('integration-test+dummy@corbado.com', false); - await model.expectScreen(ScreenNames.InitLoginFallback); - }); - - test('invitation token and process id persists after page refresh', async ({ model }) => { - await model.expectScreen(ScreenNames.InitLogin); - await model.storage.checkInvitationToken(); - const processId = await model.storage.getProcessID(); - - await model.loadLogin(port); - await model.expectScreen(ScreenNames.InitLogin); - await model.storage.checkInvitationToken(); - await model.storage.checkProcessID(processId); - }); - - test('expired login lifetime leads to fallback screen', async ({ model }) => { - await model.expectScreen(ScreenNames.InitLogin); - expect(await model.storage.getLoginLifetime()).toBeGreaterThan(Math.floor(Date.now() / 1000)); - - await model.storage.setLoginLifetime(Math.floor(Date.now() / 1000) - 1); - await model.storage.deleteInvitationToken(); - await model.loadLogin(port); - await model.expectScreen(ScreenNames.InitLoginFallback); + await virtualAuthenticator.modeCancel(); + const loginPage2 = await profilePage.logout(); + + await loginPage2.switchAccount(); + + await virtualAuthenticator.modeComplete(); + const profilePage2 = await loginPage2.loginWithIdentifier(email); + await profilePage2.awaitPage(); + + expect(await profilePage2.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); + }); + + test('testLoginWithOneTap', async ({ page }) => { + await page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); + const loginPage = new LoginPage(page); + const signupPage = await loginPage.navigateToSignup(); + const virtualAuthenticator = await VirtualAuthenticator.init(page); + await virtualAuthenticator.modeComplete(); + + const email = TestDataFactory.generateEmail(); + const postLoginPage = await signupPage.submit(email, TestDataFactory.phoneNumber, TestDataFactory.password); + const profilePage = await postLoginPage.continue(true); + await profilePage.awaitPage(); + const loginPage2 = await profilePage.logout(); + + // cancel first one-tap + await virtualAuthenticator.modeCancel(); + await loginPage2.loginWithOneTapAndCancel(); + + // then succeed + await virtualAuthenticator.modeComplete(); + const profilePage2 = await loginPage2.loginWithOneTap(); + await profilePage2.awaitPage(); + + expect(await profilePage2.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); + }); + + test('testLoginWithCUI', async ({ page }) => { + await page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); + const loginPage = new LoginPage(page); + const signupPage = await loginPage.navigateToSignup(); + const virtualAuthenticator = await VirtualAuthenticator.init(page); + await virtualAuthenticator.modeComplete(); + + const email = TestDataFactory.generateEmail(); + const postLoginPage = await signupPage.submit(email, TestDataFactory.phoneNumber, TestDataFactory.password); + const profilePage = await postLoginPage.continue(true); + await profilePage.awaitPage(); + + const loginPage2 = await profilePage.logout(); + await loginPage2.switchAccount(); + await loginPage2.loginWithCUI().awaitPage(); + }); + + test('testLoginErrorStates', async ({ page }) => { + await page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); + const loginPage = new LoginPage(page); + await VirtualAuthenticator.init(page); + const nonExistingEmail = 'integration-test+0000000000@corbado.com'; + + expect(await loginPage.awaitState(LoginStatus.PasskeyTextField)).toBeTruthy(); + await loginPage.loginWithIdentifierButNoSuccess(nonExistingEmail); + expect(await loginPage.awaitState(LoginStatus.PasskeyTextField)).toBeTruthy(); + expect(loginPage.awaitErrorMessage('There is no account registered to that email address.')).toBeTruthy(); + }); + + test('testLoginErrorStatesGradualRollout', async ({ page }) => { + await page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login`); + const loginPage = new LoginPage(page); + const signupPage = await loginPage.navigateToSignup(); + const virtualAuthenticator = await VirtualAuthenticator.init(page); + await virtualAuthenticator.modeComplete(); + + const email = TestDataFactory.generateEmail(); + const postLoginPage = await signupPage.submit(email, TestDataFactory.phoneNumber, TestDataFactory.password); + const mfaPage = await postLoginPage.autoSkipAfterSignup().awaitPage(); + + const authenticator = new AuthenticatorApp(); + const [sharedKey, profilePage] = await mfaPage.setupAndConfirmTOTPReturnProfile(authenticator); + await profilePage.awaitPage(); + expect(await profilePage.awaitState(ProfileStatus.ListWithoutPasskeySupport)).toBeTruthy(); + + const loginPage2 = await profilePage.logout(); + expect(await loginPage2.awaitState(LoginStatus.FallbackFirst)).toBeTruthy(); + await loginPage2.loginWithIdentifierAndPassword(email, TestDataFactory.password); + expect(await loginPage2.awaitState(LoginStatus.FallbackSecondTOTP)).toBeTruthy(); + const codeFirst = await authenticator.getCode(sharedKey); + const postLoginPage2 = await loginPage2.completeLoginWithTOTP(codeFirst!); + await postLoginPage2.autoSkip().awaitPage(); + }); + + test('testLoginErrorStatesPasskeyDeletedServerSide', async ({ page }) => { + await page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); + const loginPage = new LoginPage(page); + const signupPage = await loginPage.navigateToSignup(); + const virtualAuthenticator = await VirtualAuthenticator.init(page); + await virtualAuthenticator.modeComplete(); + + const email = TestDataFactory.generateEmail(); + const postLoginPage = await signupPage.submit(email, TestDataFactory.phoneNumber, TestDataFactory.password); + const profilePage = await postLoginPage.continue(true); + await profilePage.awaitPage(); + expect(await profilePage.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); + await profilePage.deletePasskeyByIndex(0, true); + const loginPage2 = await profilePage.logout(); + + await loginPage2.awaitErrorMessage('You previously deleted this passkey. Use your password to log in instead.'); + expect(await loginPage2.awaitState(LoginStatus.FallbackFirst)).toBeTruthy(); }); }); diff --git a/packages/tests-e2e/src/connect/scenarios/misc.spec.ts b/packages/tests-e2e/src/connect/scenarios/misc.spec.ts deleted file mode 100644 index 4a736d9cf..000000000 --- a/packages/tests-e2e/src/connect/scenarios/misc.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -import type { ChildProcess } from 'node:child_process'; - -import { test } from '../fixtures/BaseTest'; -import { ScreenNames, WebhookTypes } from '../utils/Constants'; -import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; -import { - loadBeforePasskeyAppend, - loadPasskeyList, - setupNetworkBlocker, - setupUser, - setupVirtualAuthenticator, - setupWebhooks, -} from './hooks'; - -test.describe.serial('webhook tests', () => { - test.describe('login component (webhook)', () => { - let server: ChildProcess; - let port: number; - - test.beforeAll(async () => { - ({ server, port } = await spawnPlaygroundNew()); - }); - - test.afterAll(() => { - killPlaygroundNew(server); - }); - - setupVirtualAuthenticator(test); - setupNetworkBlocker(test); - setupUser(test, () => port, true, true); - setupWebhooks(test, [WebhookTypes.Login]); - - test('successful login with passkey (+ webhook)', async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLoginOneTap); - - await model.login.removePasskeyButton(); - await model.expectScreen(ScreenNames.InitLogin); - - await model.login.submitEmail(model.email, true); - await model.expectScreen(ScreenNames.Home); - - model.webhook.expectWebhookRequest(WebhookTypes.Login); - }); - }); - - test.describe('append component (webhook)', () => { - let server: ChildProcess; - let port: number; - - test.beforeAll(async () => { - ({ server, port } = await spawnPlaygroundNew()); - }); - - test.afterAll(() => { - killPlaygroundNew(server); - }); - - setupVirtualAuthenticator(test); - setupNetworkBlocker(test); - setupUser(test, () => port, true, false); - loadBeforePasskeyAppend(test); - setupWebhooks(test, [WebhookTypes.Create]); - - test('successful passkey append on login (+ webhook)', async ({ model }) => { - await model.mfa.submit(true, true); - await model.expectScreen(ScreenNames.PasskeyAppended); - - await model.append.confirmAppended(); - await model.expectScreen(ScreenNames.Home); - - model.webhook.expectWebhookRequest(WebhookTypes.Create); - }); - }); - - test.describe('passkey-list component (webhook)', () => { - let server: ChildProcess; - let port: number; - - test.beforeAll(async () => { - ({ server, port } = await spawnPlaygroundNew()); - }); - - test.afterAll(() => { - killPlaygroundNew(server); - }); - - setupVirtualAuthenticator(test); - setupNetworkBlocker(test); - setupUser(test, () => port, true, false); - loadPasskeyList(test); - setupWebhooks(test, [WebhookTypes.Create, WebhookTypes.Delete]); - - test('list, delete, create passkey (+ webhook)', async ({ model }) => { - await model.passkeyList.expectPasskeys(0); - await model.passkeyList.createPasskey(true); - await model.passkeyList.expectPasskeys(1); - model.webhook.expectWebhookRequest(WebhookTypes.Create); - - await model.passkeyList.deletePasskey(0); - await model.passkeyList.expectPasskeys(0); - model.webhook.expectWebhookRequest(WebhookTypes.Delete); - }); - }); -}); diff --git a/packages/tests-e2e/src/connect/scenarios/network-blocking.spec.ts b/packages/tests-e2e/src/connect/scenarios/network-blocking.spec.ts new file mode 100644 index 000000000..d7e55f121 --- /dev/null +++ b/packages/tests-e2e/src/connect/scenarios/network-blocking.spec.ts @@ -0,0 +1,147 @@ +import type { ChildProcess } from 'node:child_process'; + +import { expect, test } from '@playwright/test'; + +import { LoginPage, LoginStatus } from '../models/LoginPage'; +import { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; +import { TestDataFactory } from '../utils/TestDataFactory'; +import { NetworkRequestBlocker } from '../utils/NetworkRequestBlocker'; +import { AuthenticatorApp } from '../utils/AuthenticatorApp'; +import { ProfileStatus } from '../models/ProfilePage'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; + +test.describe('network blocking flows', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + + test('testLoginErrorStatesNetworkBlocking', async ({ page }) => { + await page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); + const loginPage = new LoginPage(page); + const signupPage = await loginPage.navigateToSignup(); + const virtualAuthenticator = await VirtualAuthenticator.init(page); + await virtualAuthenticator.modeComplete(); + const networkBlocker = await NetworkRequestBlocker.init(page); + + const email = TestDataFactory.generateEmail(); + const postLoginPage = await signupPage.submit(email, TestDataFactory.phoneNumber, TestDataFactory.password); + const profilePage = await postLoginPage.continue(true); + await profilePage.awaitPage(); + + await networkBlocker.loginInit(); + const loginPage2 = await profilePage.logout(); + expect(await loginPage2.awaitState(LoginStatus.FallbackFirst)).toBeTruthy(); + + await networkBlocker.loginStart(); + await page.reload(); + expect(await loginPage2.awaitState(LoginStatus.PasskeyOneTap)).toBeTruthy(); + await virtualAuthenticator.modeCancel(); + await loginPage2.switchAccount(); + await virtualAuthenticator.modeComplete(); + await loginPage2.loginWithIdentifierButNoSuccess(email); + expect(await loginPage2.awaitState(LoginStatus.FallbackFirst)).toBeTruthy(); + + await networkBlocker.loginFinish(); + await page.reload(); + expect(await loginPage2.awaitState(LoginStatus.FallbackFirst)).toBeTruthy(); + + await networkBlocker.unblockAll(); + await page.reload(); + await loginPage2.loginWithCUI().awaitPage(); + }); + + test('testAppendErrorStatesPasskeyAppendBlocked', async ({ page }) => { + await page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); + const loginPage = new LoginPage(page); + const signupPage = await loginPage.navigateToSignup(); + const virtualAuthenticator = await VirtualAuthenticator.init(page); + await virtualAuthenticator.modeComplete(); + const networkBlocker = await NetworkRequestBlocker.init(page); + + const email = TestDataFactory.generateEmail(); + + await networkBlocker.appendInit(); + const postLoginPage = await signupPage.submit(email, TestDataFactory.phoneNumber, TestDataFactory.password); + const mfaPage = postLoginPage.autoSkipAfterSignup(); + + const authenticator = new AuthenticatorApp(); + const [sharedKey, profilePage] = await mfaPage.setupAndConfirmTOTPReturnProfile(authenticator); + expect(await profilePage.awaitState(ProfileStatus.ListEmpty)).toBeTruthy(); + + await networkBlocker.appendStart(); + const loginPage2 = await profilePage.logout(); + await loginPage2.loginWithIdentifierAndPasswordIdentifierFirst(email, TestDataFactory.password); + const codeFirst = await authenticator.getCode(sharedKey); + const postLoginPage2 = await loginPage2.completeLoginWithTOTP(codeFirst!); + await postLoginPage2.autoSkip().awaitPage(); + + await networkBlocker.appendFinish(); + const loginPage3 = await profilePage.logout(); + await loginPage3.loginWithIdentifierAndPasswordIdentifierFirst(email, TestDataFactory.password); + const codeSecond = await authenticator.getCode(sharedKey); + const postLoginPage3 = await loginPage3.completeLoginWithTOTP(codeSecond!); + await postLoginPage3.autoSkip().awaitPage(); + }); + + test('testManageErrorStatesNetworkBlocking', async ({ page }) => { + await page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); + const loginPage = new LoginPage(page); + const signupPage = await loginPage.navigateToSignup(); + const virtualAuthenticator = await VirtualAuthenticator.init(page); + await virtualAuthenticator.modeComplete(); + const networkBlocker = await NetworkRequestBlocker.init(page); + + const email = TestDataFactory.generateEmail(); + const postLoginPage = await signupPage.submit(email, TestDataFactory.phoneNumber, TestDataFactory.password); + const profilePage = await postLoginPage.continue(true); + await profilePage.awaitPage(); + expect(await profilePage.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); + + await profilePage.clearProcessState(); + await networkBlocker.manageInit(); + await page.reload(); + expect(await profilePage.awaitState(ProfileStatus.ListWithInitialError)).toBeTruthy(); + expect( + await profilePage.awaitErrorMessage('Unable to access passkeys. Check your connection and try again.'), + ).toBeTruthy(); + + await networkBlocker.manageList(); + await page.reload(); + expect(await profilePage.awaitState(ProfileStatus.ListWithInitialError)).toBeTruthy(); + expect( + await profilePage.awaitErrorMessage('Unable to access passkeys. Check your connection and try again.'), + ).toBeTruthy(); + + await networkBlocker.appendStart(); + await page.reload(); + expect(await profilePage.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); + expect(await profilePage.getPasskeyCount()).toBe(1); + await profilePage.appendPasskey(); + expect(await profilePage.awaitErrorMessage('Passkey creation failed. Please try again later.')).toBeTruthy(); + expect(await profilePage.getPasskeyCount()).toBe(1); + + await networkBlocker.appendFinish(); + await page.reload(); + expect(await profilePage.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); + await profilePage.deletePasskeyByIndex(0, true); + expect(await profilePage.awaitState(ProfileStatus.ListEmpty)).toBeTruthy(); + await profilePage.appendPasskey(); + expect(await profilePage.awaitErrorMessage('Passkey creation failed. Please try again later.')).toBeTruthy(); + expect(await profilePage.awaitState(ProfileStatus.ListEmpty)).toBeTruthy(); + + await networkBlocker.manageDelete(); + await page.reload(); + expect(await profilePage.awaitState(ProfileStatus.ListEmpty)).toBeTruthy(); + await profilePage.appendPasskey(); + expect(await profilePage.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); + await profilePage.deletePasskeyByIndex(0, true); + expect(await profilePage.awaitErrorMessage('Passkey deletion failed. Please try again later.')).toBeTruthy(); + }); +}); diff --git a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts deleted file mode 100644 index 5d39061c8..000000000 --- a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts +++ /dev/null @@ -1,146 +0,0 @@ -import type { ChildProcess } from 'node:child_process'; - -import { expect, test } from '../fixtures/BaseTest'; -import { ErrorTexts, ScreenNames } from '../utils/Constants'; -import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; -import { loadPasskeyList, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; - -test.describe('passkey-list component', () => { - let server: ChildProcess; - let port: number; - - test.beforeAll(async () => { - ({ server, port } = await spawnPlaygroundNew()); - }); - - test.afterAll(() => { - killPlaygroundNew(server); - }); - - setupVirtualAuthenticator(test); - setupNetworkBlocker(test); - setupUser(test, () => port, true, false); - loadPasskeyList(test); - - test('list, delete, create passkey', async ({ model }) => { - await model.passkeyList.expectPasskeys(0); - await model.passkeyList.createPasskey(true); - await model.passkeyList.expectPasskeys(1); - await model.passkeyList.deletePasskey(0); - await model.passkeyList.expectPasskeys(0); - }); - - test('abort passkey creation', async ({ model }) => { - await model.passkeyList.expectPasskeys(0); - await model.passkeyList.createPasskey(false); - await model.passkeyList.expectPasskeys(0); - }); - - test('Connect Token endpoint unavailable during passkey creation', async ({ model }) => { - await model.passkeyList.expectPasskeys(0); - - await model.blocker.blockCorbadoConnectTokenEndpoint(port); - - await model.page.getByRole('button', { name: 'Add a passkey' }).click(); - await model.expectError(ErrorTexts.PasskeyCreateFail); - await model.passkeyList.expectPasskeys(0); - }); - - test('Corbado FAPI unavailable during passkey creation', async ({ model }) => { - await model.passkeyList.expectPasskeys(0); - - await model.blocker.blockCorbadoFAPI(); - - await model.page.getByRole('button', { name: 'Add a passkey' }).click(); - await model.expectError(ErrorTexts.PasskeyCreateFail); - await model.passkeyList.expectPasskeys(0); - }); - - test('passkey already registered', async ({ model }) => { - await model.passkeyList.expectPasskeys(0); - await model.passkeyList.createPasskey(true); - await model.passkeyList.expectPasskeys(1); - - await model.passkeyList.createPasskey(true, () => - expect(model.page.getByRole('heading', { name: 'No passkey created' })).toBeVisible(), - ); - await expect(model.page.getByText('No passkey created')).toBeVisible(); - - await model.passkeyList.confirmModal(); - await model.passkeyList.expectPasskeys(1); - }); - - test('Connect Token endpoint unavailable during passkey deletion', async ({ model }) => { - await model.passkeyList.expectPasskeys(0); - await model.passkeyList.createPasskey(true); - await model.passkeyList.expectPasskeys(1); - - await model.blocker.blockCorbadoConnectTokenEndpoint(port); - - await model.passkeyList.deletePasskey(0); - await model.expectError(ErrorTexts.PasskeyDeleteFail); - await model.passkeyList.expectPasskeys(1); - }); - - test('Corbado FAPI unavailable during passkey deletion', async ({ model }) => { - await model.passkeyList.expectPasskeys(0); - await model.passkeyList.createPasskey(true); - await model.passkeyList.expectPasskeys(1); - - await model.blocker.blockCorbadoFAPI(); - - await model.passkeyList.deletePasskey(0); - await model.expectError(ErrorTexts.PasskeyDeleteFail); - await model.passkeyList.expectPasskeys(1); - }); -}); - -test.describe('skip passkey-list component', () => { - let server: ChildProcess; - let port: number; - - test.beforeAll(async () => { - ({ server, port } = await spawnPlaygroundNew()); - }); - - test.afterAll(() => { - killPlaygroundNew(server); - }); - - setupVirtualAuthenticator(test); - setupNetworkBlocker(test); - setupUser(test, () => port, true, true); - - test('Connect Token endpoint unavailable', async ({ model }) => { - await model.blocker.blockCorbadoConnectTokenEndpoint(port); - - await model.home.gotoPasskeyList(); - await model.expectScreen(ScreenNames.PasskeyList); - await model.expectError(ErrorTexts.PasskeyFetchFail); - }); - - test('Corbado FAPI unavailable', async ({ model }) => { - await model.blocker.blockCorbadoFAPI(); - - await model.home.gotoPasskeyList(); - await model.expectScreen(ScreenNames.PasskeyList); - await model.expectError(ErrorTexts.PasskeyFetchFail); - }); - - test('expired manage lifetime leads to skipped passkey-list screen', async ({ model }) => { - await model.home.gotoPasskeyList(); - await model.expectScreen(ScreenNames.PasskeyList); - await model.passkeyList.expectPasskeys(1); - await model.loadHome(port); - await model.expectScreen(ScreenNames.Home); - expect(await model.storage.getManageLifetime()).toBeGreaterThan(Math.floor(Date.now() / 1000)); - - await model.storage.setManageLifetime(Math.floor(Date.now() / 1000) - 1); - await model.storage.deleteInvitationToken(); - - await model.home.gotoPasskeyList(); - await model.expectScreen(ScreenNames.PasskeyList); - await model.passkeyList.expectPasskeys(1); - await model.passkeyList.checkCreatePasskeyDisabled(); - }); -}); diff --git a/packages/tests-e2e/src/connect/utils/AuthenticatorApp.ts b/packages/tests-e2e/src/connect/utils/AuthenticatorApp.ts new file mode 100644 index 000000000..9960c2468 --- /dev/null +++ b/packages/tests-e2e/src/connect/utils/AuthenticatorApp.ts @@ -0,0 +1,55 @@ +// A lightweight TOTP helper mirroring the Android AuthenticatorApp behavior +// - addBySecret(secret): registers a secret and returns the current code +// - getCode(secret): waits until next 30s boundary if needed, then returns next code + +import { generateToken } from 'node-2fa'; + +type WrappedTOTP = { + secret: string; + nextBoundary: number; // ms timestamp of next 30s boundary +}; + +export class AuthenticatorApp { + #existing = new Map(); + + async addBySecret(secret: string): Promise { + try { + const now = Date.now(); + const result = generateToken(secret); + if (!result) return null; + + const boundary = this.#calculateNextBoundary(now, 30_000); + this.#existing.set(secret, { secret, nextBoundary: boundary }); + return result.token; + } catch { + return null; + } + } + + async getCode(secret: string): Promise { + const wrapped = this.#existing.get(secret); + if (!wrapped) return null; + + const now = Date.now(); + if (now < wrapped.nextBoundary) { + await new Promise(resolve => setTimeout(resolve, wrapped.nextBoundary - now)); + } + + const then = Date.now(); + const result = generateToken(secret); + if (!result) return null; + + wrapped.nextBoundary = this.#calculateNextBoundary(then, 30_000); + this.#existing.set(secret, wrapped); + return result.token; + } + + clear() { + this.#existing.clear(); + } + + #calculateNextBoundary(timeMillis: number, periodMillis: number): number { + const periods = Math.floor(timeMillis / periodMillis); + return (periods + 1) * periodMillis; + } +} diff --git a/packages/tests-e2e/src/connect/utils/CDPSessionManager.ts b/packages/tests-e2e/src/connect/utils/CDPSessionManager.ts deleted file mode 100644 index 49d28660e..000000000 --- a/packages/tests-e2e/src/connect/utils/CDPSessionManager.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { CDPSession, Page } from '@playwright/test'; - -export class CDPSessionManager { - #cdpClient: CDPSession | null = null; - - async initialize(page: Page) { - this.#cdpClient = await page.context().newCDPSession(page); - } - - getClient(): CDPSession { - if (!this.#cdpClient) { - throw new Error('CDP client not initialized'); - } - return this.#cdpClient; - } -} diff --git a/packages/tests-e2e/src/connect/utils/Constants.ts b/packages/tests-e2e/src/connect/utils/Constants.ts deleted file mode 100644 index cc5bfd6a8..000000000 --- a/packages/tests-e2e/src/connect/utils/Constants.ts +++ /dev/null @@ -1,36 +0,0 @@ -export enum ScreenNames { - InitSignup, - InitLogin, - InitLoginFallback, - InitLoginOneTap, - PasskeyAppend, - PasskeyAppended, - Home, - PasskeyList, - PasskeyError1, - PasskeyError2, - MFA, -} - -export enum ErrorTexts { - EmptyEmail = 'Enter your email address.', - UnknownEmail = 'There is no account registered to that email address.', - CancelledPasskey = 'You have cancelled setting up your passkey. Please try again.', - PasskeyFetchFail = 'Unable to access passkeys. Check your connection and try again.', - PasskeyCreateFail = 'Passkey creation failed. Please try again later.', - PasskeyDeleteFail = 'Passkey deletion failed. Please try again later.', - DeletedPasskeyUsed = 'You previously deleted this passkey. Use your password to log in instead.', - PasskeySignatureValidationFail = "We couldn't log you in with your passkey due to a system error. Use your password to log in instead.", -} - -export enum WebhookTypes { - Login = 'passkey-login.completed', - Create = 'passkey.created', - Delete = 'passkey.deleted', -} - -export const phone = '+4915121609839'; -export const password = 'asdfasdf'; - -export const totalTimeout = 45000; -export const operationTimeout = 10000; diff --git a/packages/tests-e2e/src/connect/utils/ExpectScreen.ts b/packages/tests-e2e/src/connect/utils/ExpectScreen.ts deleted file mode 100644 index 100066ce0..000000000 --- a/packages/tests-e2e/src/connect/utils/ExpectScreen.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { Page } from '@playwright/test'; -import { expect } from '@playwright/test'; - -import { ErrorTexts } from './Constants'; -import { ScreenNames } from './Constants'; - -export const expectScreen = async (page: Page, screenName: ScreenNames): Promise => { - switch (screenName) { - case ScreenNames.InitSignup: - await expect(page.locator('div.font-bold.text-xl')).toHaveText('Signup'); - return; - - case ScreenNames.InitLogin: - await expect(page.locator('div.cb-connect-login').getByRole('textbox', { name: 'Email address' })).toBeVisible(); - return; - - case ScreenNames.InitLoginFallback: - await expect(page.locator('h3.text-xl')).toHaveText('Login'); - return; - - case ScreenNames.InitLoginOneTap: - await expect(page.locator('div.cb-connect-login')).toContainText('Welcome back'); - return; - - case ScreenNames.PasskeyAppend: - await expect(page.locator('.cb-connect-container').locator('.cb-append-header')).toContainText( - 'Simplify Your Login', - ); - return; - - case ScreenNames.PasskeyAppended: - await expect(page.locator('.cb-connect-container').locator('.cb-append-success-header')).toContainText( - 'Passkey Created Successfully', - ); - return; - - case ScreenNames.Home: - await expect(page.locator('div.font-bold.text-xl')).toHaveText('Home'); - return; - - case ScreenNames.PasskeyList: - await expect(page.locator('.cb-connect-passkey-list')).toBeVisible(); - return; - - case ScreenNames.PasskeyError1: - await expect(page.locator('.cb-connect-container').locator('.cb-login-header')).toContainText( - 'Use your passkey to confirm it’s really you', - ); - return; - - case ScreenNames.PasskeyError2: - await expect(page.locator('.cb-connect-container').locator('.cb-login-header')).toContainText( - 'Something went wrong!', - ); - return; - - case ScreenNames.MFA: - await expect(page.locator('h3.text-xl')).toHaveText('MFA'); - return; - - default: - throw new Error('Invalid screen'); - } -}; - -export const expectError = (page: Page, message: ErrorTexts): Promise => { - // This error message isn't a part of cb-container, so it doesn't come as cb-notification-text. - if (message === ErrorTexts.DeletedPasskeyUsed || message === ErrorTexts.PasskeySignatureValidationFail) { - return expect(page.getByText(message)).toBeVisible(); - } - return expect(page.locator('.cb-notification-text')).toHaveText(message); -}; diff --git a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts index 612c405b1..da5864826 100644 --- a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts +++ b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts @@ -1,28 +1,48 @@ -import type { CDPSession } from '@playwright/test'; - -import type { CDPSessionManager } from './CDPSessionManager'; - export class NetworkRequestBlocker { #cdpClient: CDPSession; - constructor(cdpManager: CDPSessionManager) { - this.#cdpClient = cdpManager.getClient(); + static async init(page: Page): Promise { + const blocker = new NetworkRequestBlocker(); + blocker.#cdpClient = await page.context().newCDPSession(page); + await blocker.#cdpClient.send('Network.enable'); + + return blocker; } - enableBlocking() { - return this.#cdpClient.send('Network.enable'); + loginInit() { + return this.#setBlockedURLs(['*/v2/connect/login/init']); } - blockCorbadoFAPI() { - return this.#cdpClient.send('Network.setBlockedURLs', { - urls: ['*.frontendapi.cloud.corbado-staging.io/v2/connect'], - }); + loginStart() { + return this.#setBlockedURLs(['*/v2/connect/login/start']); } - blockCorbadoFAPIFinishEndpoint() { - return this.#cdpClient.send('Network.setBlockedURLs', { - urls: ['*.frontendapi.cloud.corbado-staging.io/v2/connect/*/finish'], - }); + loginFinish() { + return this.#setBlockedURLs(['*/v2/connect/login/finish']); + } + + appendInit() { + return this.#setBlockedURLs(['*/v2/connect/append/init']); + } + + appendStart() { + return this.#setBlockedURLs(['*/v2/connect/append/start']); + } + + appendFinish() { + return this.#setBlockedURLs(['*/v2/connect/append/finish']); + } + + manageInit() { + return this.#setBlockedURLs(['*/v2/connect/manage/init']); + } + + manageList() { + return this.#setBlockedURLs(['*/v2/connect/manage/list']); + } + + manageDelete() { + return this.#setBlockedURLs(['*/v2/connect/manage/delete']); } blockCorbadoConnectTokenEndpoint(port: number) { @@ -31,4 +51,14 @@ export class NetworkRequestBlocker { urls: [`localhost:${port.toString()}/passkey-list`], }); } + + unblockAll() { + return this.#setBlockedURLs([]); + } + + #setBlockedURLs(urls: string[]) { + return this.#cdpClient.send('Network.setBlockedURLs', { urls }); + } } + +import type { CDPSession, Page } from '@playwright/test'; diff --git a/packages/tests-e2e/src/connect2/utils/TestDataFactory.ts b/packages/tests-e2e/src/connect/utils/TestDataFactory.ts similarity index 100% rename from packages/tests-e2e/src/connect2/utils/TestDataFactory.ts rename to packages/tests-e2e/src/connect/utils/TestDataFactory.ts diff --git a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts index 45bd46176..b874990cc 100644 --- a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts +++ b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts @@ -1,15 +1,15 @@ import type { CDPSession, Page } from '@playwright/test'; -import type { CDPSessionManager } from './CDPSessionManager'; -import { operationTimeout } from './Constants'; - export class VirtualAuthenticator { #cdpClient: CDPSession; #authenticatorId = ''; - async init(page: Page, passkeysSupported = true) { - this.#cdpClient = await page.context().newCDPSession(page); - await this.addWebAuthn(passkeysSupported); + static async init(page: Page, passkeysSupported = true): Promise { + const authenticator = new VirtualAuthenticator(); + authenticator.#cdpClient = await page.context().newCDPSession(page); + await authenticator.addWebAuthn(passkeysSupported); + + return authenticator; } async addWebAuthn(passkeySupported = true) { @@ -32,60 +32,30 @@ export class VirtualAuthenticator { this.#authenticatorId = result.authenticatorId; } - removeWebAuthn() { - return this.#cdpClient.send('WebAuthn.removeVirtualAuthenticator', { - authenticatorId: this.#authenticatorId, + async runWithComplete(cb: () => Promise) { + const postOperationPromise = new Promise(resolve => { + this.#cdpClient?.on('WebAuthn.credentialAdded', () => resolve()); + this.#cdpClient?.on('WebAuthn.credentialAsserted', () => resolve()); }); - } - - async startAndCompletePasskeyOperation( - operationTrigger: () => Promise, - postOperationCheck: (() => Promise) | null = null, - ) { - let postOperationPromise: Promise; - if (postOperationCheck === null) { - postOperationPromise = new Promise(resolve => { - this.#cdpClient?.on('WebAuthn.credentialAdded', () => resolve()); - this.#cdpClient?.on('WebAuthn.credentialAsserted', () => resolve()); - }); - } else { - postOperationPromise = postOperationCheck(); - } + const wait = new Promise(resolve => setTimeout(resolve, 5000)); - const wait = new Promise(resolve => setTimeout(resolve, operationTimeout)); await this.#setWebAuthnUserVerified(this.#authenticatorId, true); await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); - await operationTrigger(); + await cb(); await Promise.race([postOperationPromise, wait.then(() => Promise.reject('Passkey input timeout'))]); await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, false); } - async startAndCancelPasskeyOperation(operationTrigger: () => Promise, postOperationCheck: () => Promise) { + async modeCancel() { await this.#setWebAuthnUserVerified(this.#authenticatorId, false); await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); - - await operationTrigger(); - - await postOperationCheck(); - await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, false); } - async runWithComplete(cb: () => Promise) { - const postOperationPromise = new Promise(resolve => { - this.#cdpClient?.on('WebAuthn.credentialAdded', () => resolve()); - this.#cdpClient?.on('WebAuthn.credentialAsserted', () => resolve()); - }); - const wait = new Promise(resolve => setTimeout(resolve, operationTimeout)); - + async modeComplete() { await this.#setWebAuthnUserVerified(this.#authenticatorId, true); await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); - - await cb(); - - await Promise.race([postOperationPromise, wait.then(() => Promise.reject('Passkey input timeout'))]); - await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, false); } async runWithCancel(cb: () => Promise) { diff --git a/packages/tests-e2e/src/connect2/models/LoginPage.ts b/packages/tests-e2e/src/connect2/models/LoginPage.ts deleted file mode 100644 index 53b43999d..000000000 --- a/packages/tests-e2e/src/connect2/models/LoginPage.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { Page } from '@playwright/test'; - -import { BasePage } from './BasePage'; -import { SignupPage } from './SignupPage'; -import { PostLoginPage } from './PostLoginPage'; -import { ProfilePage } from './ProfilePage'; - -export enum LoginStatus { - PasskeyOneTap, - PasskeyTextField, - FallbackFirst, - FallbackSecondTOTP, - PasskeyErrorSoft, -} - -export class LoginPage extends BasePage { - page: Page; - - constructor(page: Page) { - super(page); - this.page = page; - } - - visible(): Promise { - return this.waitForHeading('Login'); - } - - static async awaitPage(page: Page): Promise { - const loginPage = new LoginPage(page); - if (!(await loginPage.visible())) { - throw new Error('Login page not visible'); - } - - return loginPage; - } - - async navigateToSignup(): Promise { - await this.clickLink('Sign up'); - - return SignupPage.awaitPage(this.page); - } - - async awaitState(status: LoginStatus): Promise { - switch (status) { - case LoginStatus.PasskeyOneTap: - return this.waitForButton('Login with Passkey'); - default: - return true; - } - } - - async loginWithOneTap(): Promise { - await this.clickButton('Login with Passkey'); - - return ProfilePage.awaitPage(this.page); - } -} diff --git a/packages/tests-e2e/src/connect2/models/MFAPage.ts b/packages/tests-e2e/src/connect2/models/MFAPage.ts deleted file mode 100644 index 94157da17..000000000 --- a/packages/tests-e2e/src/connect2/models/MFAPage.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Page } from '@playwright/test'; - -import { BasePage } from './BasePage'; - -export class MFAPage extends BasePage { - private readonly page: Page; - - constructor(page: Page) { - super(page); - this.page = page; - } -} diff --git a/packages/tests-e2e/src/connect2/scenarios/append.spec.ts b/packages/tests-e2e/src/connect2/scenarios/append.spec.ts deleted file mode 100644 index 86029138d..000000000 --- a/packages/tests-e2e/src/connect2/scenarios/append.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { ChildProcess } from 'node:child_process'; - -import { expect, test } from '@playwright/test'; - -import { killPlaygroundNew, spawnPlaygroundNew } from '../../connect/utils/Playground'; -import { LoginPage, LoginStatus } from '../models/LoginPage'; -import { TestDataFactory } from '../utils/TestDataFactory'; -import { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; -import { ProfileStatus } from '../models/ProfilePage'; - -test.describe('append flows', () => { - let server: ChildProcess; - let port: number; - - test.beforeAll(async () => { - ({ server, port } = await spawnPlaygroundNew()); - }); - - test.afterAll(() => { - killPlaygroundNew(server); - }); - - test('testAppendAfterSignUp', async ({ page }) => { - await page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`); - const loginPage = new LoginPage(page); - const signupPage = await loginPage.navigateToSignup(); - const virtualAuthenticator = await VirtualAuthenticator.init(page); - - const email = TestDataFactory.generateEmail(); - - await virtualAuthenticator.modeCancel(); - const postLoginPage = await signupPage.submit(email, TestDataFactory.phoneNumber, TestDataFactory.password); - const postLoginPage2 = await postLoginPage.continueWithCancel(true); - const postLoginPage3 = await postLoginPage2.continueWithCancel(false); - expect( - await postLoginPage3.awaitErrorMessage('You have cancelled setting up your passkey. Please try again.'), - ).toBeTruthy(); - - await virtualAuthenticator.modeComplete(); - const profilePage = await postLoginPage3.continue(false); - expect(await profilePage.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); - expect(await profilePage.getPasskeyCount()).toBe(1); - const loginPage2 = await profilePage.logout(); - - expect(await loginPage2.awaitState(LoginStatus.PasskeyOneTap)).toBeTruthy(); - const postLoginPage4 = await loginPage2.loginWithOneTap(); - expect(await profilePage.awaitState(ProfileStatus.ListWithPasskeys)).toBeTruthy(); - await postLoginPage4.appendPasskey(); - await postLoginPage4.awaitErrorMessage('No passkey created'); - expect(await profilePage.getPasskeyCount()).toBe(1); - }); - - test.skip('testAppendAfterSignUpSkipped', async ({ page }) => {}); -}); diff --git a/packages/tests-e2e/src/connect2/scenarios/login.spec.ts b/packages/tests-e2e/src/connect2/scenarios/login.spec.ts deleted file mode 100644 index ebdd6a411..000000000 --- a/packages/tests-e2e/src/connect2/scenarios/login.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { ChildProcess } from 'node:child_process'; - -import { test } from '@playwright/test'; - -import { killPlaygroundNew, spawnPlaygroundNew } from '../../connect/utils/Playground'; - -test.describe('append flows', () => { - let server: ChildProcess; - let port: number; - - test.beforeAll(async () => { - ({ server, port } = await spawnPlaygroundNew()); - }); - - test.afterAll(() => { - killPlaygroundNew(server); - }); - - test.skip('testLoginWithOneTap', async ({ page }) => {}); - - test.skip('testLoginWithCUI', async ({ page }) => {}); - - test.skip('testLoginErrorStates', async ({ page }) => {}); - - test.skip('testLoginErrorStatesGradualRollout', async ({ page }) => {}); - - test.skip('testLoginErrorStatesPasskeyDeletedClientSide', async ({ page }) => {}); -}); diff --git a/packages/tests-e2e/src/connect2/scenarios/network-blocking.spec.ts b/packages/tests-e2e/src/connect2/scenarios/network-blocking.spec.ts deleted file mode 100644 index 6859b082c..000000000 --- a/packages/tests-e2e/src/connect2/scenarios/network-blocking.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { ChildProcess } from 'node:child_process'; - -import { test } from '@playwright/test'; - -import { killPlaygroundNew, spawnPlaygroundNew } from '../../connect/utils/Playground'; - -test.describe('append flows', () => { - let server: ChildProcess; - let port: number; - - test.beforeAll(async () => { - ({ server, port } = await spawnPlaygroundNew()); - }); - - test.afterAll(() => { - killPlaygroundNew(server); - }); - - test.skip('testLoginErrorStatesNetworkBlocking', async ({ page }) => {}); - - test.skip('testLoginErrorStatesPasskeyAppendBlocked', async ({ page }) => {}); - - test.skip('testManageErrorStatesNetworkBlocking', async ({ page }) => {}); -}); diff --git a/packages/tests-e2e/src/connect2/utils/NetworkRequestBlocker.ts b/packages/tests-e2e/src/connect2/utils/NetworkRequestBlocker.ts deleted file mode 100644 index 612c405b1..000000000 --- a/packages/tests-e2e/src/connect2/utils/NetworkRequestBlocker.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { CDPSession } from '@playwright/test'; - -import type { CDPSessionManager } from './CDPSessionManager'; - -export class NetworkRequestBlocker { - #cdpClient: CDPSession; - - constructor(cdpManager: CDPSessionManager) { - this.#cdpClient = cdpManager.getClient(); - } - - enableBlocking() { - return this.#cdpClient.send('Network.enable'); - } - - blockCorbadoFAPI() { - return this.#cdpClient.send('Network.setBlockedURLs', { - urls: ['*.frontendapi.cloud.corbado-staging.io/v2/connect'], - }); - } - - blockCorbadoFAPIFinishEndpoint() { - return this.#cdpClient.send('Network.setBlockedURLs', { - urls: ['*.frontendapi.cloud.corbado-staging.io/v2/connect/*/finish'], - }); - } - - blockCorbadoConnectTokenEndpoint(port: number) { - // This is sufficient, as the connectTokens endpoint is called from /passkey-list handler - return this.#cdpClient.send('Network.setBlockedURLs', { - urls: [`localhost:${port.toString()}/passkey-list`], - }); - } -} diff --git a/packages/tests-e2e/src/connect2/utils/Playground.ts b/packages/tests-e2e/src/connect2/utils/Playground.ts deleted file mode 100644 index a2d526421..000000000 --- a/packages/tests-e2e/src/connect2/utils/Playground.ts +++ /dev/null @@ -1,100 +0,0 @@ -import type { ChildProcess } from 'node:child_process'; -import { spawn } from 'node:child_process'; - -import getPort from 'get-port'; -import path from 'path'; -import waitPort from 'wait-port'; - -type PlaygroundType = 'connect-next' | 'connect-web-js'; -const PLAYGROUND_TYPE: PlaygroundType = (process.env.PLAYGROUND_TYPE as PlaygroundType) || 'connect-next'; - -export type PlaygroundInfo = { - server: ChildProcess; - port: number; -}; - -function getPlaygroundDir(): string { - switch (PLAYGROUND_TYPE) { - case 'connect-next': - return path.resolve(__dirname, '../../../../../playground/connect-next'); - case 'connect-web-js': - return path.resolve(__dirname, '../../../../../playground/connect-web-js'); - default: - throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); - } -} - -function getPlaygroundArgs(port: number): string[] { - switch (PLAYGROUND_TYPE) { - case 'connect-next': - return ['run', 'start', '--', '--port', port.toString()]; - case 'connect-web-js': - throw new Error(`Unimplemented: ${PLAYGROUND_TYPE}`); - default: - throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); - } -} - -export async function spawnPlaygroundNew(): Promise { - const port = await getPort(); - - const playgroundDir = getPlaygroundDir(); - const server = spawn('npm', getPlaygroundArgs(port), { - cwd: playgroundDir, - env: { - ...process.env, - }, - stdio: 'ignore', - shell: true, - }); - const ok = await waitPort({ host: 'localhost', port, timeout: 15_000, output: 'silent' }); - if (!ok) { - server.kill(); - throw new Error(`Server never came up on port ${port}`); - } - - return { server, port }; -} - -export function killPlaygroundNew(server: ChildProcess) { - server.kill(); -} - -export default async function installPlaygroundDeps() { - const playgroundDir = getPlaygroundDir(); - - const installProcess = spawn('npm', ['install'], { - cwd: playgroundDir, - stdio: 'inherit', - shell: true, - }); - - await new Promise((resolve, reject) => { - installProcess.on('close', (code: number) => { - if (code === 0) { - console.log(`[Global Setup] Dependencies installed successfully in ${playgroundDir}.`); - const buildProcess = spawn('npm', ['run', 'build'], { - cwd: playgroundDir, - stdio: 'inherit', - shell: true, - }); - buildProcess.on('close', (buildCode: number) => { - if (buildCode === 0) { - console.log(`[Global Setup] Playground built successfully in ${playgroundDir}.`); - resolve(); - } else { - reject(new Error(`[Global Setup] npm run build failed in ${playgroundDir} with code ${buildCode}`)); - } - }); - buildProcess.on('error', (err: Error) => { - reject(new Error(`[Global Setup] Failed to start build process: ${err.message}`)); - }); - } else { - reject(new Error(`[Global Setup] npm install failed in ${playgroundDir} with code ${code}`)); - } - }); - installProcess.on('error', (err: Error) => { - reject(new Error(`[Global Setup] Failed to start npm install process: ${err.message}`)); - }); - }); -} diff --git a/packages/tests-e2e/src/connect2/utils/VirtualAuthenticator.ts b/packages/tests-e2e/src/connect2/utils/VirtualAuthenticator.ts deleted file mode 100644 index b874990cc..000000000 --- a/packages/tests-e2e/src/connect2/utils/VirtualAuthenticator.ts +++ /dev/null @@ -1,109 +0,0 @@ -import type { CDPSession, Page } from '@playwright/test'; - -export class VirtualAuthenticator { - #cdpClient: CDPSession; - #authenticatorId = ''; - - static async init(page: Page, passkeysSupported = true): Promise { - const authenticator = new VirtualAuthenticator(); - authenticator.#cdpClient = await page.context().newCDPSession(page); - await authenticator.addWebAuthn(passkeysSupported); - - return authenticator; - } - - async addWebAuthn(passkeySupported = true) { - await this.#cdpClient.send('WebAuthn.enable'); - const result = await this.#cdpClient.send('WebAuthn.addVirtualAuthenticator', { - options: passkeySupported - ? { - protocol: 'ctap2', - transport: 'internal', - hasResidentKey: true, - hasUserVerification: true, - automaticPresenceSimulation: false, - } - : { - protocol: 'u2f', - transport: 'usb', - }, - }); - - this.#authenticatorId = result.authenticatorId; - } - - async runWithComplete(cb: () => Promise) { - const postOperationPromise = new Promise(resolve => { - this.#cdpClient?.on('WebAuthn.credentialAdded', () => resolve()); - this.#cdpClient?.on('WebAuthn.credentialAsserted', () => resolve()); - }); - const wait = new Promise(resolve => setTimeout(resolve, 5000)); - - await this.#setWebAuthnUserVerified(this.#authenticatorId, true); - await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); - - await cb(); - - await Promise.race([postOperationPromise, wait.then(() => Promise.reject('Passkey input timeout'))]); - await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, false); - } - - async modeCancel() { - await this.#setWebAuthnUserVerified(this.#authenticatorId, false); - await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); - } - - async modeComplete() { - await this.#setWebAuthnUserVerified(this.#authenticatorId, true); - await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); - } - - async runWithCancel(cb: () => Promise) { - await this.#setWebAuthnUserVerified(this.#authenticatorId, false); - await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); - - await cb(); - - await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, false); - } - - clearCredentials() { - return this.#cdpClient.send('WebAuthn.clearCredentials', { - authenticatorId: this.#authenticatorId, - }); - } - - async addDummyCredential() { - try { - await this.#cdpClient.send('WebAuthn.addCredential', { - authenticatorId: this.#authenticatorId, - credential: { - credentialId: '', // 'WZuSfPDeCfXUMqO3vcVZ6ZYY0w2W4NpLcLzTjMl4qns=', - isResidentCredential: true, - rpId: 'localhost', - privateKey: - 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgz/eSahk8R0fk3Jjpcbd1LPc2gGKyzEG23UFIbFTqSbyhRANCAAQ4a8dJ559cf0cZcg0U7k5oCofmtOzuqXDSwzP8LLhv0InronrySiaWAGuWFpVsbNyOnWSd6VZJU8wiFKSMiDWN', - userHandle: '', // 'TDBlaFVpNnRNQg==', - signCount: 1, - }, - }); - } catch (e) { - console.error(e); - throw e; - } - } - - #setWebAuthnAutomaticPresenceSimulation(authenticatorId: string, automatic: boolean) { - return this.#cdpClient.send('WebAuthn.setAutomaticPresenceSimulation', { - authenticatorId: authenticatorId, - enabled: automatic, - }); - } - - #setWebAuthnUserVerified(authenticatorId: string, isUserVerified: boolean) { - return this.#cdpClient.send('WebAuthn.setUserVerified', { - authenticatorId, - isUserVerified, - }); - } -} diff --git a/playground/connect-next/app/(auth-required)/profile/AccountSection.tsx b/playground/connect-next/app/(auth-required)/profile/AccountSection.tsx index e7c6f73b7..12d5cb347 100644 --- a/playground/connect-next/app/(auth-required)/profile/AccountSection.tsx +++ b/playground/connect-next/app/(auth-required)/profile/AccountSection.tsx @@ -1,15 +1,15 @@ -import { useEffect, useState } from "react"; -import { Skeleton } from "@/components/ui/skeleton"; -import { CognitoUserInfo, getCognitoUserInfo } from "@/lib/utils"; -import { Button } from "@/components/ui/button"; +import { useEffect, useState } from 'react'; +import { Skeleton } from '@/components/ui/skeleton'; +import { CognitoUserInfo, getCognitoUserInfo } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; -import { updateUserAttribute, confirmUserAttribute } from "aws-amplify/auth"; -import { ConfirmOTP } from "@/components/ConfirmOTP"; +import { updateUserAttribute, confirmUserAttribute } from 'aws-amplify/auth'; +import { ConfirmOTP } from '@/components/ConfirmOTP'; export const AccountSection = () => { const [userInfo, setUserInfo] = useState(); const [editMode, setEditMode] = useState(false); - const [phone, setPhone] = useState(""); + const [phone, setPhone] = useState(''); const [saving, setSaving] = useState(false); const [verificationRequired, setVerificationRequired] = useState(false); const [message, setMessage] = useState(null); @@ -20,9 +20,9 @@ export const AccountSection = () => { try { const userInfo = await getCognitoUserInfo(); setUserInfo(userInfo); - setPhone(userInfo.phoneNumber || ""); + setPhone(userInfo.phoneNumber || ''); } catch { - setMessage("Failed to load user info"); + setMessage('Failed to load user info'); } finally { setLoading(false); } @@ -33,14 +33,14 @@ export const AccountSection = () => { const handleEdit = () => { setEditMode(true); setMessage(null); - setPhone(userInfo?.phoneNumber || ""); + setPhone(userInfo?.phoneNumber || ''); }; const handleCancel = () => { setEditMode(false); setVerificationRequired(false); setMessage(null); - setPhone(userInfo?.phoneNumber || ""); + setPhone(userInfo?.phoneNumber || ''); }; const handleSave = async (e: React.FormEvent) => { @@ -49,23 +49,18 @@ export const AccountSection = () => { setMessage(null); try { const output = await updateUserAttribute({ - userAttribute: { attributeKey: "phone_number", value: phone }, + userAttribute: { attributeKey: 'phone_number', value: phone }, }); - if ( - output.nextStep && - output.nextStep.updateAttributeStep === "CONFIRM_ATTRIBUTE_WITH_CODE" - ) { + if (output.nextStep && output.nextStep.updateAttributeStep === 'CONFIRM_ATTRIBUTE_WITH_CODE') { setVerificationRequired(true); - setMessage( - `A verification code was sent to your new phone number. Please enter it below.` - ); + setMessage(`A verification code was sent to your new phone number. Please enter it below.`); } else { - setMessage("Phone number updated successfully."); + setMessage('Phone number updated successfully.'); setEditMode(false); - setUserInfo((prev) => prev && { ...prev, phoneNumber: phone }); + setUserInfo(prev => prev && { ...prev, phoneNumber: phone }); } } catch { - setMessage("Failed to update phone number."); + setMessage('Failed to update phone number.'); } finally { setSaving(false); } @@ -75,85 +70,95 @@ export const AccountSection = () => { setMessage(null); try { await confirmUserAttribute({ - userAttributeKey: "phone_number", + userAttributeKey: 'phone_number', confirmationCode: code, }); - setMessage("Phone number verified and updated successfully."); + setMessage('Phone number verified and updated successfully.'); setEditMode(false); setVerificationRequired(false); - setUserInfo((prev) => prev && { ...prev, phoneNumber: phone }); + setUserInfo(prev => prev && { ...prev, phoneNumber: phone }); return undefined; } catch { - return "Failed to verify phone number. Please check the code and try again."; + return 'Failed to verify phone number. Please check the code and try again.'; } }; if (loading) { - return ; + return ; } const profileData = { Username: userInfo?.username, Email: userInfo?.email, - "Phone Number": userInfo?.phoneNumber, - "Email Verified": userInfo?.emailVerified?.toString(), + 'Phone Number': userInfo?.phoneNumber, + 'Email Verified': userInfo?.emailVerified?.toString(), }; return ( -
+
{Object.entries(profileData).map(([label, value]) => (
-
{label}
-
- {label === "Phone Number" && editMode ? ( +
{label}
+
+ {label === 'Phone Number' && editMode ? ( setPhone(e.target.value)} - className="border border-gray-300 rounded px-3 py-2" + onChange={e => setPhone(e.target.value)} + className='border border-gray-300 rounded px-3 py-2' required /> ) : ( - value || "-" + value || '-' )}
))} {editMode ? ( verificationRequired ? ( -
- - {message && ( -
{message}
- )} +
+ + {message &&
{message}
}
) : ( -
-
- -
- {message && ( -
{message}
- )} + {message &&
{message}
}
) ) : ( -
-
)} - {!editMode && message && ( -
{message}
- )} + {!editMode && message &&
{message}
}
); }; diff --git a/playground/connect-next/app/(auth-required)/profile/DangerSection.tsx b/playground/connect-next/app/(auth-required)/profile/DangerSection.tsx index 51a300617..5fa3546c0 100644 --- a/playground/connect-next/app/(auth-required)/profile/DangerSection.tsx +++ b/playground/connect-next/app/(auth-required)/profile/DangerSection.tsx @@ -1,33 +1,38 @@ -import { deleteUser, signOut } from "aws-amplify/auth"; -import { Button } from "@/components/ui/button"; -import { useRouter } from "next/navigation"; +import { deleteUser, signOut } from 'aws-amplify/auth'; +import { Button } from '@/components/ui/button'; +import { useRouter } from 'next/navigation'; export const DangerSection = () => { const router = useRouter(); const logout = async () => { await signOut(); - router.push("/login"); + router.push('/login'); }; const deleteAccount = async () => { await deleteUser(); - router.push("/"); + router.push('/'); }; return ( -
-
-
- Log the user out on this device. -
-
-
-
Delete this user.
-
diff --git a/playground/connect-next/app/(auth-required)/profile/MFASection.tsx b/playground/connect-next/app/(auth-required)/profile/MFASection.tsx index 86e597149..9fe4decc0 100644 --- a/playground/connect-next/app/(auth-required)/profile/MFASection.tsx +++ b/playground/connect-next/app/(auth-required)/profile/MFASection.tsx @@ -1,72 +1,72 @@ -import {useEffect, useState} from "react"; -import {fetchMFAPreference} from "aws-amplify/auth"; -import {Button} from "@/components/ui/button"; -import {useRouter} from "next/navigation"; -import {Skeleton} from "@/components/ui/skeleton"; +import { useEffect, useState } from 'react'; +import { fetchMFAPreference } from 'aws-amplify/auth'; +import { Button } from '@/components/ui/button'; +import { useRouter } from 'next/navigation'; +import { Skeleton } from '@/components/ui/skeleton'; type MfaInfo = { - preferred?: string; - enabledSMS: boolean; - enabledTOTP: boolean; -} + preferred?: string; + enabledSMS: boolean; + enabledTOTP: boolean; +}; export const MFASection = () => { - const [mfaInfo, setMfaInfo] = useState(); - const router = useRouter(); + const [mfaInfo, setMfaInfo] = useState(); + const router = useRouter(); - useEffect(() => { - async function loadUser() { - try { - const mfaPrefs = await fetchMFAPreference(); - let enabledSMS = false, enabledTOTP = false; - (mfaPrefs.enabled ?? []).forEach((mfaType: string) => { - if (mfaType === 'SMS') { - enabledSMS = true; - } else if (mfaType === 'TOTP') { - enabledTOTP = true; - } - }); + useEffect(() => { + async function loadUser() { + try { + const mfaPrefs = await fetchMFAPreference(); + let enabledSMS = false, + enabledTOTP = false; + (mfaPrefs.enabled ?? []).forEach((mfaType: string) => { + if (mfaType === 'SMS') { + enabledSMS = true; + } else if (mfaType === 'TOTP') { + enabledTOTP = true; + } + }); - setMfaInfo({ - preferred: mfaPrefs.preferred?.toString(), - enabledSMS, - enabledTOTP, - }); - } catch (err) { - console.error('Failed to load user info:', err); - } - - } - - void loadUser(); - }, []); - - if (!mfaInfo) { - return ; + setMfaInfo({ + preferred: mfaPrefs.preferred?.toString(), + enabledSMS, + enabledTOTP, + }); + } catch (err) { + console.error('Failed to load user info:', err); + } } + void loadUser(); + }, []); - const mfaData = mfaInfo && { - 'Preferred MFA': mfaInfo.preferred || 'Not Set', - 'SMS Enabled': mfaInfo.enabledSMS ? 'Yes' : 'No', - 'TOTP Enabled': mfaInfo.enabledTOTP ? 'Yes' : 'No', - }; + if (!mfaInfo) { + return ; + } - return ( -
-
- {mfaData && Object.entries(mfaData).map(([label, value]) => ( -
-
{label}
-
{value}
-
- ))} -
-
- + const mfaData = mfaInfo && { + 'Preferred MFA': mfaInfo.preferred || 'Not Set', + 'SMS Enabled': mfaInfo.enabledSMS ? 'Yes' : 'No', + 'TOTP Enabled': mfaInfo.enabledTOTP ? 'Yes' : 'No', + }; + + return ( +
+
+ {mfaData && + Object.entries(mfaData).map(([label, value]) => ( +
+
{label}
+
{value}
-
- ) -} + ))} +
+
+ +
+
+ ); +}; -export default MFASection; \ No newline at end of file +export default MFASection; diff --git a/playground/connect-next/app/(auth-required)/profile/Section.tsx b/playground/connect-next/app/(auth-required)/profile/Section.tsx index 17518311c..3e28e2c13 100644 --- a/playground/connect-next/app/(auth-required)/profile/Section.tsx +++ b/playground/connect-next/app/(auth-required)/profile/Section.tsx @@ -1,16 +1,14 @@ -import {Separator} from "@/components/ui/separator"; -import React from "react"; +import { Separator } from '@/components/ui/separator'; +import React from 'react'; -const Section = ({children, headline}: {children: React.ReactNode, headline: string}) => { - return ( -
-
{headline}
- -
- {children} -
-
- ) -} +const Section = ({ children, headline }: { children: React.ReactNode; headline: string }) => { + return ( +
+
{headline}
+ +
{children}
+
+ ); +}; -export default Section; \ No newline at end of file +export default Section; diff --git a/playground/connect-next/app/(auth-required)/setup-mfa/page.tsx b/playground/connect-next/app/(auth-required)/setup-mfa/page.tsx index b69b4109b..c7381c68f 100644 --- a/playground/connect-next/app/(auth-required)/setup-mfa/page.tsx +++ b/playground/connect-next/app/(auth-required)/setup-mfa/page.tsx @@ -117,13 +117,7 @@ export default function Page() { const navigateForward = () => { const searchParams = new URLSearchParams(window.location.search); - const postSignup = searchParams.get('post-signup'); - - if (postSignup) { - router.push('/post-login'); - } else { - router.push('/profile'); - } + router.push('/profile'); }; const updatePhoneNumber = async () => { diff --git a/playground/connect-next/app/(no-auth)/login/ConventionalLogin.tsx b/playground/connect-next/app/(no-auth)/login/ConventionalLogin.tsx index 266e00887..3e3afed92 100644 --- a/playground/connect-next/app/(no-auth)/login/ConventionalLogin.tsx +++ b/playground/connect-next/app/(no-auth)/login/ConventionalLogin.tsx @@ -6,6 +6,7 @@ import PasswordForm from './PasswordForm'; type Props = { initialUserProvidedIdentifier: string; + initialError: string; }; enum State { @@ -14,7 +15,7 @@ enum State { ProvideTOTPCode, } -export const ConventionalLogin = ({ initialUserProvidedIdentifier }: Props) => { +export const ConventionalLogin = ({ initialUserProvidedIdentifier, initialError }: Props) => { const [state, setState] = useState(State.ProvidePassword); const router = useRouter(); @@ -84,6 +85,7 @@ export const ConventionalLogin = ({ initialUserProvidedIdentifier }: Props) => { ); diff --git a/playground/connect-next/app/(no-auth)/login/PasswordForm.tsx b/playground/connect-next/app/(no-auth)/login/PasswordForm.tsx index 137e91347..854583130 100644 --- a/playground/connect-next/app/(no-auth)/login/PasswordForm.tsx +++ b/playground/connect-next/app/(no-auth)/login/PasswordForm.tsx @@ -1,75 +1,74 @@ -import {FormEvent, useState} from "react"; -import {Button} from "@/components/ui/button"; +import { FormEvent, useState } from 'react'; +import { Button } from '@/components/ui/button'; type Props = { - initialUserProvidedIdentifier: string; - initialError?: string; - onClick: (username: string, password: string) => Promise; -} + initialUserProvidedIdentifier: string; + initialError: string; + onClick: (username: string, password: string) => Promise; +}; -export const PasswordForm = ({onClick, initialUserProvidedIdentifier}: Props) => { - const [username, setUsername] = useState(initialUserProvidedIdentifier); - const [password, setPassword] = useState(''); - const [message, setMessage] = useState(''); +export const PasswordForm = ({ onClick, initialUserProvidedIdentifier, initialError }: Props) => { + const [username, setUsername] = useState(initialUserProvidedIdentifier); + const [password, setPassword] = useState(''); + const [message, setMessage] = useState(initialError); - const handleLogin = async (e: FormEvent) => { - e.preventDefault(); - setMessage('Loading...'); - const maybeError = await onClick(username, password); - if (maybeError) { - setMessage(maybeError); - } + const handleLogin = async (e: FormEvent) => { + e.preventDefault(); + setMessage('Loading...'); + const maybeError = await onClick(username, password); + if (maybeError) { + setMessage(maybeError); } + }; - return ( -
+
+ + setUsername(e.target.value)} + /> +
+
+ + setPassword(e.target.value)} + /> +
+
{message}
+ + + ); +}; -export default PasswordForm; \ No newline at end of file +export default PasswordForm; diff --git a/playground/connect-next/app/(no-auth)/login/WrappedLogin.tsx b/playground/connect-next/app/(no-auth)/login/WrappedLogin.tsx index 6b2d173f4..0ae125564 100644 --- a/playground/connect-next/app/(no-auth)/login/WrappedLogin.tsx +++ b/playground/connect-next/app/(no-auth)/login/WrappedLogin.tsx @@ -56,7 +56,12 @@ const WrappedLogin = ({ clientState }: Props) => { return (
- {conventionalLoginVisible ? : null} + {conventionalLoginVisible ? ( + + ) : null} {!conventionalLoginVisible ? ( <>
diff --git a/playground/connect-next/app/(no-auth)/login/actions.ts b/playground/connect-next/app/(no-auth)/login/actions.ts index e84c93f2f..fbe989559 100644 --- a/playground/connect-next/app/(no-auth)/login/actions.ts +++ b/playground/connect-next/app/(no-auth)/login/actions.ts @@ -1,13 +1,13 @@ 'use server'; -import {cookies} from "next/headers"; +import { cookies } from 'next/headers'; export async function postPasskeyLogin(clientState: string) { - const cookieStore = await cookies(); - cookieStore.set({ - name: 'cbo_client_state', - value: clientState, - httpOnly: true, - expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), - }); + const cookieStore = await cookies(); + cookieStore.set({ + name: 'cbo_client_state', + value: clientState, + httpOnly: true, + expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), + }); } diff --git a/playground/connect-next/app/(no-auth)/login/page.tsx b/playground/connect-next/app/(no-auth)/login/page.tsx index 138e76ada..fa09670c5 100644 --- a/playground/connect-next/app/(no-auth)/login/page.tsx +++ b/playground/connect-next/app/(no-auth)/login/page.tsx @@ -1,11 +1,11 @@ -import React from "react"; -import { cookies } from "next/headers"; -import WrappedLogin from "./WrappedLogin"; +import React from 'react'; +import { cookies } from 'next/headers'; +import WrappedLogin from './WrappedLogin'; export default async function Page() { const cookieStore = await cookies(); - const clientState = cookieStore.get("cbo_client_state"); - console.log("clientState", clientState); + const clientState = cookieStore.get('cbo_client_state'); + console.log('clientState', clientState); return ; } diff --git a/playground/connect-next/app/globals.css b/playground/connect-next/app/globals.css index 1a4a65284..581cc23c8 100644 --- a/playground/connect-next/app/globals.css +++ b/playground/connect-next/app/globals.css @@ -94,20 +94,20 @@ html { ui-sans-serif, system-ui, sans-serif, - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol", - "Noto Color Emoji" + 'Apple Color Emoji', + 'Segoe UI Emoji', + 'Segoe UI Symbol', + 'Noto Color Emoji' ); --cb-font-family-secondary: var( --default-font-family, ui-sans-serif, system-ui, sans-serif, - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol", - "Noto Color Emoji" + 'Apple Color Emoji', + 'Segoe UI Emoji', + 'Segoe UI Symbol', + 'Noto Color Emoji' ); --cb-color-error-container: #ffd6da; diff --git a/playground/connect-next/components/ui/separator.tsx b/playground/connect-next/components/ui/separator.tsx index 12d81c4a8..32d6c72b4 100644 --- a/playground/connect-next/components/ui/separator.tsx +++ b/playground/connect-next/components/ui/separator.tsx @@ -1,31 +1,22 @@ -"use client" +'use client'; -import * as React from "react" -import * as SeparatorPrimitive from "@radix-ui/react-separator" +import * as React from 'react'; +import * as SeparatorPrimitive from '@radix-ui/react-separator'; -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; const Separator = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->( - ( - { className, orientation = "horizontal", decorative = true, ...props }, - ref - ) => ( - - ) -) -Separator.displayName = SeparatorPrimitive.Root.displayName +>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => ( + +)); +Separator.displayName = SeparatorPrimitive.Root.displayName; -export { Separator } +export { Separator }; diff --git a/playground/connect-next/components/ui/skeleton.tsx b/playground/connect-next/components/ui/skeleton.tsx index d7e45f7bd..bf495da75 100644 --- a/playground/connect-next/components/ui/skeleton.tsx +++ b/playground/connect-next/components/ui/skeleton.tsx @@ -1,15 +1,12 @@ -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; -function Skeleton({ - className, - ...props -}: React.HTMLAttributes) { +function Skeleton({ className, ...props }: React.HTMLAttributes) { return (
- ) + ); } -export { Skeleton } +export { Skeleton }; From 226bae32f4340e27334893c7934ade2afe227fc6 Mon Sep 17 00:00:00 2001 From: Martin Kellner Date: Fri, 8 Aug 2025 19:23:11 +0200 Subject: [PATCH 58/60] Remove external endpoint --- .../src/connect/scenarios/append.spec.ts | 2 +- .../connect/scenarios/client-state.spec.ts | 20 +++++++ .../app/(api)/connectTokenExternal/route.ts | 58 ------------------- 3 files changed, 21 insertions(+), 59 deletions(-) create mode 100644 packages/tests-e2e/src/connect/scenarios/client-state.spec.ts delete mode 100644 playground/connect-next/app/(api)/connectTokenExternal/route.ts diff --git a/packages/tests-e2e/src/connect/scenarios/append.spec.ts b/packages/tests-e2e/src/connect/scenarios/append.spec.ts index 365abf8b3..486ecbf47 100644 --- a/packages/tests-e2e/src/connect/scenarios/append.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/append.spec.ts @@ -5,9 +5,9 @@ import { expect, test } from '@playwright/test'; import { LoginPage, LoginStatus } from '../models/LoginPage'; import { ProfileStatus } from '../models/ProfilePage'; import { AuthenticatorApp } from '../utils/AuthenticatorApp'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; import { TestDataFactory } from '../utils/TestDataFactory'; import { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; -import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; test.describe('append flows', () => { let server: ChildProcess; diff --git a/packages/tests-e2e/src/connect/scenarios/client-state.spec.ts b/packages/tests-e2e/src/connect/scenarios/client-state.spec.ts new file mode 100644 index 000000000..412988b79 --- /dev/null +++ b/packages/tests-e2e/src/connect/scenarios/client-state.spec.ts @@ -0,0 +1,20 @@ +import type { ChildProcess } from 'node:child_process'; + +import { test } from '@playwright/test'; + +import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; + +test.describe('client-state flows', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + + test('testCookieBasedSync', async ({ page }) => {}); +}); diff --git a/playground/connect-next/app/(api)/connectTokenExternal/route.ts b/playground/connect-next/app/(api)/connectTokenExternal/route.ts deleted file mode 100644 index 5bb84f008..000000000 --- a/playground/connect-next/app/(api)/connectTokenExternal/route.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { NextRequest } from 'next/server'; -import { getCorbadoConnectTokenExternal, verifyAmplifyTokenExternal } from '@/lib/utils'; - -type Payload = { - idToken: string; - connectTokenType: string; -}; - -export async function POST(req: NextRequest) { - const body = (await req.json()) as Payload; - - const { idToken, connectTokenType } = body; - console.log('creating connectTokenType', connectTokenType); - - try { - const { displayName, identifier } = await verifyAmplifyTokenExternal(idToken); - - const connectToken = await getCorbadoConnectTokenExternal(connectTokenType, { - displayName: displayName, - identifier: identifier, - }); - - const simulateError = process.env.SIMULATE_ERROR; - if (simulateError && displayName.endsWith('@corbado.com')) { - console.warn('Simulating error for testing purposes'); - - switch (simulateError) { - case 'error_response': - return new Response(JSON.stringify({ error: 'Simulated error' }), { - status: 500, - headers: { 'Content-Type': 'application/json' }, - }); - case 'invalid_token': - return new Response(JSON.stringify({ token: 'invalid_token' }), { - status: 201, - headers: { 'Content-Type': 'application/json' }, - }); - case 'empty_token': - return new Response(JSON.stringify({ token: '' }), { - status: 201, - headers: { 'Content-Type': 'application/json' }, - }); - } - } - - return new Response(JSON.stringify({ token: connectToken }), { - status: 201, - headers: { 'Content-Type': 'application/json' }, - }); - } catch (e) { - console.error('Error verifying token or getting connect token', e); - - return new Response(JSON.stringify({ error: 'Failed to verify token or get connect token' }), { - status: 500, - headers: { 'Content-Type': 'application/json' }, - }); - } -} From f82aa57b8599fcc3bb2e358246b297d94c02b75f Mon Sep 17 00:00:00 2001 From: Martin Kellner Date: Fri, 8 Aug 2025 19:30:34 +0200 Subject: [PATCH 59/60] Missing ENVs for flow --- .github/workflows/test-all.yml | 2 ++ .github/workflows/test.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 3fb601c85..2f297c464 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -224,7 +224,9 @@ jobs: PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} AWS_COGNITO_USER_POOL_ID: ${{ secrets.AWS_COGNITO_USER_POOL_ID }} + NEXT_PUBLIC_AWS_COGNITO_USER_POOL_ID: ${{ secrets.AWS_COGNITO_USER_POOL_ID }} AWS_COGNITO_CLIENT_ID: ${{ secrets.AWS_COGNITO_CLIENT_ID }} + NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID: ${{ secrets.AWS_COGNITO_CLIENT_ID }} AWS_COGNITO_CLIENT_SECRET: ${{ secrets.AWS_COGNITO_CLIENT_SECRET }} AWS_REGION: ${{ secrets.AWS_REGION }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_CONNECT_PLAYGROUND }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 805aab6cb..bebc9ba99 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,7 +36,9 @@ jobs: PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} AWS_COGNITO_USER_POOL_ID: ${{ secrets.AWS_COGNITO_USER_POOL_ID }} + NEXT_PUBLIC_AWS_COGNITO_USER_POOL_ID: ${{ secrets.AWS_COGNITO_USER_POOL_ID }} AWS_COGNITO_CLIENT_ID: ${{ secrets.AWS_COGNITO_CLIENT_ID }} + NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID: ${{ secrets.AWS_COGNITO_CLIENT_ID }} AWS_COGNITO_CLIENT_SECRET: ${{ secrets.AWS_COGNITO_CLIENT_SECRET }} AWS_REGION: ${{ secrets.AWS_REGION }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_CONNECT_PLAYGROUND }} From 8addd95c6a9e6cd9bc494986247bf5a84e39ca2b Mon Sep 17 00:00:00 2001 From: Martin Kellner Date: Fri, 8 Aug 2025 19:46:11 +0200 Subject: [PATCH 60/60] Correct NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID --- .github/workflows/test-all.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 2f297c464..6440c7db3 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -226,7 +226,7 @@ jobs: AWS_COGNITO_USER_POOL_ID: ${{ secrets.AWS_COGNITO_USER_POOL_ID }} NEXT_PUBLIC_AWS_COGNITO_USER_POOL_ID: ${{ secrets.AWS_COGNITO_USER_POOL_ID }} AWS_COGNITO_CLIENT_ID: ${{ secrets.AWS_COGNITO_CLIENT_ID }} - NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID: ${{ secrets.AWS_COGNITO_CLIENT_ID }} + NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID: ${{ secrets.NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID }} AWS_COGNITO_CLIENT_SECRET: ${{ secrets.AWS_COGNITO_CLIENT_SECRET }} AWS_REGION: ${{ secrets.AWS_REGION }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_CONNECT_PLAYGROUND }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bebc9ba99..49ebba426 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,7 +38,7 @@ jobs: AWS_COGNITO_USER_POOL_ID: ${{ secrets.AWS_COGNITO_USER_POOL_ID }} NEXT_PUBLIC_AWS_COGNITO_USER_POOL_ID: ${{ secrets.AWS_COGNITO_USER_POOL_ID }} AWS_COGNITO_CLIENT_ID: ${{ secrets.AWS_COGNITO_CLIENT_ID }} - NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID: ${{ secrets.AWS_COGNITO_CLIENT_ID }} + NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID: ${{ vars.NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID }} AWS_COGNITO_CLIENT_SECRET: ${{ secrets.AWS_COGNITO_CLIENT_SECRET }} AWS_REGION: ${{ secrets.AWS_REGION }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_CONNECT_PLAYGROUND }}