From f06b22f18aedc78b198a3b5337214d3dfff7932f Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:22:22 +0100 Subject: [PATCH 1/5] chore: fixes to tests --- .github/workflows/test.yml | 2 +- acceptance/docker-compose.yaml | 14 ++- acceptance/setup.sh | 57 ++++++++++ acceptance/sink/main.go | 104 ++++++++++++++++++ acceptance/tests/code-screen.ts | 12 ++ acceptance/tests/code.ts | 19 ++++ acceptance/tests/login.ts | 9 +- acceptance/tests/loginname-screen.ts | 6 +- acceptance/tests/otp.ts | 31 ------ acceptance/tests/register.spec.ts | 18 ++- acceptance/tests/sink.ts | 28 +++++ acceptance/tests/user.ts | 22 ++-- acceptance/tests/username-passkey.spec.ts | 12 +- .../tests/username-password-changed.spec.ts | 10 +- .../tests/username-password-otp_email.spec.ts | 49 ++++++++- .../tests/username-password-otp_sms.spec.ts | 47 +++++++- acceptance/tests/username-password.spec.ts | 10 +- apps/login/src/components/login-otp.tsx | 4 +- package.json | 4 +- pnpm-lock.yaml | 49 ++++----- 20 files changed, 404 insertions(+), 103 deletions(-) create mode 100644 acceptance/sink/main.go create mode 100644 acceptance/tests/code-screen.ts create mode 100644 acceptance/tests/code.ts delete mode 100644 acceptance/tests/otp.ts create mode 100644 acceptance/tests/sink.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ce61199e7f..703b48c58a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -76,7 +76,7 @@ jobs: if: ${{ matrix.command == 'test:acceptance' }} - name: Run ZITADEL - run: ZITADEL_DEV_UID=root pnpm run-zitadel + run: ZITADEL_DEV_UID=root pnpm run-sink if: ${{ matrix.command == 'test:acceptance' }} - name: Create Production Build diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index 4ba64880f3..7ed8913316 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -22,7 +22,7 @@ services: - POSTGRES_HOST_AUTH_METHOD=trust command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c shared_buffers=1GB -c work_mem=16MB -c effective_io_concurrency=100 -c wal_level=minimal -c archive_mode=off -c max_wal_senders=0 healthcheck: - test: ["CMD-SHELL", "pg_isready"] + test: [ "CMD-SHELL", "pg_isready" ] interval: "10s" timeout: "30s" retries: 5 @@ -52,3 +52,15 @@ services: depends_on: wait_for_zitadel: condition: "service_completed_successfully" + + sink: + image: golang:1.19-alpine + container_name: sink + command: go run /sink/main.go -port 3333 + ports: + - 3333:3333 + volumes: + - "./sink:/sink" + depends_on: + setup: + condition: "service_completed_successfully" diff --git a/acceptance/setup.sh b/acceptance/setup.sh index d490ce3f61..19ae9ed213 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -8,6 +8,9 @@ ZITADEL_API_DOMAIN="${ZITADEL_API_DOMAIN:-localhost}" ZITADEL_API_PORT="${ZITADEL_API_PORT:-8080}" ZITADEL_API_URL="${ZITADEL_API_URL:-${ZITADEL_API_PROTOCOL}://${ZITADEL_API_DOMAIN}:${ZITADEL_API_PORT}}" ZITADEL_API_INTERNAL_URL="${ZITADEL_API_INTERNAL_URL:-${ZITADEL_API_URL}}" +SINK_EMAIL_URL="${SINK_EMAIL_URL:-"http://localhost:3333/email"}}" +SINK_SMS_URL="${SINK_SMS_URL:-"http://localhost:3333/sms"}}" +SINK_NOTIFICATION_URL="${SINK_SMS_URL:-"http://localhost:3333/notification"}}" if [ -z "${PAT}" ]; then echo "Reading PAT from file ${PAT_FILE}" @@ -24,6 +27,10 @@ if [ -z "${ZITADEL_SERVICE_USER_ID}" ]; then ZITADEL_SERVICE_USER_ID=$(echo "${USERINFO_RESPONSE}" | jq --raw-output '.sub') fi +################################################################# +# Environment files +################################################################# + WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.local} echo "Writing environment file to ${WRITE_ENVIRONMENT_FILE} when done." WRITE_TEST_ENVIRONMENT_FILE=${WRITE_TEST_ENVIRONMENT_FILE:-$(dirname "$0")/../acceptance/tests/.env.local} @@ -32,6 +39,7 @@ echo "Writing environment file to ${WRITE_TEST_ENVIRONMENT_FILE} when done." echo "ZITADEL_API_URL=${ZITADEL_API_URL} ZITADEL_SERVICE_USER_ID=${ZITADEL_SERVICE_USER_ID} ZITADEL_SERVICE_USER_TOKEN=${PAT} +SINK_NOTIFICATION_URL=${SINK_NOTIFICATION_URL} DEBUG=true"| tee "${WRITE_ENVIRONMENT_FILE}" "${WRITE_TEST_ENVIRONMENT_FILE}" > /dev/null echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}" cat ${WRITE_ENVIRONMENT_FILE} @@ -39,6 +47,54 @@ cat ${WRITE_ENVIRONMENT_FILE} echo "Wrote environment file ${WRITE_TEST_ENVIRONMENT_FILE}" cat ${WRITE_TEST_ENVIRONMENT_FILE} +################################################################# +# SMS provider with HTTP +################################################################# + +SMSHTTP_RESPONSE=$(curl -s --request POST \ + --url "${ZITADEL_API_INTERNAL_URL}/admin/v1/sms/http" \ + --header "Authorization: Bearer ${PAT}" \ + --header "Host: ${ZITADEL_API_DOMAIN}" \ + --header "Content-Type: application/json" \ + -d "{\"endpoint\": \"${SINK_SMS_URL}\", \"description\": \"test\"}") +echo "Received SMS HTTP response: ${SMSHTTP_RESPONSE}" + +SMSHTTP_ID=$(echo ${SMSHTTP_RESPONSE} | jq -r '. | .id') +echo "Received SMS HTTP ID: ${SMSHTTP_ID}" + +SMS_ACTIVE_RESPONSE=$(curl -s --request POST \ + --url "${ZITADEL_API_INTERNAL_URL}/admin/v1/sms/${SMSHTTP_ID}/_activate" \ + --header "Authorization: Bearer ${PAT}" \ + --header "Host: ${ZITADEL_API_DOMAIN}" \ + --header "Content-Type: application/json") +echo "Received SMS active response: ${SMS_ACTIVE_RESPONSE}" + +################################################################# +# Email provider with HTTP +################################################################# + +EMAILHTTP_RESPONSE=$(curl -s --request POST \ + --url "${ZITADEL_API_INTERNAL_URL}/admin/v1/email/http" \ + --header "Authorization: Bearer ${PAT}" \ + --header "Host: ${ZITADEL_API_DOMAIN}" \ + --header "Content-Type: application/json" \ + -d "{\"endpoint\": \"${SINK_EMAIL_URL}\", \"description\": \"test\"}") +echo "Received Email HTTP response: ${EMAILHTTP_RESPONSE}" + +EMAILHTTP_ID=$(echo ${EMAILHTTP_RESPONSE} | jq -r '. | .id') +echo "Received Email HTTP ID: ${EMAILHTTP_ID}" + +EMAIL_ACTIVE_RESPONSE=$(curl -s --request POST \ + --url "${ZITADEL_API_INTERNAL_URL}/admin/v1/email/${EMAILHTTP_ID}/_activate" \ + --header "Authorization: Bearer ${PAT}" \ + --header "Host: ${ZITADEL_API_DOMAIN}" \ + --header "Content-Type: application/json") +echo "Received Email active response: ${EMAIL_ACTIVE_RESPONSE}" + +################################################################# +# Wait for projection of default organization in ZITADEL +################################################################# + DEFAULTORG_RESPONSE_RESULTS=0 # waiting for default organization until [ ${DEFAULTORG_RESPONSE_RESULTS} -eq 1 ] @@ -53,3 +109,4 @@ do DEFAULTORG_RESPONSE_RESULTS=$(echo $DEFAULTORG_RESPONSE | jq -r '.result | length') echo "Received default organization response result: ${DEFAULTORG_RESPONSE_RESULTS}" done + diff --git a/acceptance/sink/main.go b/acceptance/sink/main.go new file mode 100644 index 0000000000..d591981a34 --- /dev/null +++ b/acceptance/sink/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "net/http" +) + +type serializableData struct { + ContextInfo map[string]interface{} `json:"contextInfo,omitempty"` + Args map[string]interface{} `json:"args,omitempty"` +} + +type response struct { + Recipient string `json:"recipient,omitempty"` +} + +func main() { + port := flag.String("port", "3333", "used port for the sink") + email := flag.String("email", "/email", "path for a sent email") + emailKey := flag.String("email-key", "recipientEmailAddress", "value in the sent context info of the email used as key to retrieve the notification") + sms := flag.String("sms", "/sms", "path for a sent sms") + smsKey := flag.String("sms-key", "recipientPhoneNumber", "value in the sent context info of the sms used as key to retrieve the notification") + notification := flag.String("notification", "/notification", "path to receive the notification") + flag.Parse() + + messages := make(map[string]serializableData) + + http.HandleFunc(*email, func(w http.ResponseWriter, r *http.Request) { + data, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + serializableData := serializableData{} + if err := json.Unmarshal(data, &serializableData); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + email, ok := serializableData.ContextInfo[*emailKey].(string) + if !ok { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + fmt.Println(email + ": " + string(data)) + messages[email] = serializableData + io.WriteString(w, "Email!\n") + }) + + http.HandleFunc(*sms, func(w http.ResponseWriter, r *http.Request) { + data, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + serializableData := serializableData{} + if err := json.Unmarshal(data, &serializableData); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + phone, ok := serializableData.ContextInfo[*smsKey].(string) + if !ok { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + fmt.Println(phone + ": " + string(data)) + messages[phone] = serializableData + io.WriteString(w, "SMS!\n") + }) + + http.HandleFunc(*notification, func(w http.ResponseWriter, r *http.Request) { + data, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + response := response{} + if err := json.Unmarshal(data, &response); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + serializableData, err := json.Marshal(messages[response.Recipient]) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + io.WriteString(w, string(serializableData)) + }) + + fmt.Println("Starting server on", *port) + fmt.Println(*email, " for email handling") + fmt.Println(*sms, " for sms handling") + fmt.Println(*notification, " for retrieving notifications") + err := http.ListenAndServe(":"+*port, nil) + if err != nil { + panic("Server could not be started: " + err.Error()) + } +} diff --git a/acceptance/tests/code-screen.ts b/acceptance/tests/code-screen.ts new file mode 100644 index 0000000000..3ab9dad26d --- /dev/null +++ b/acceptance/tests/code-screen.ts @@ -0,0 +1,12 @@ +import { expect, Page } from "@playwright/test"; + +const codeTextInput = "code-text-input"; + +export async function codeScreen(page: Page, code: string) { + await page.getByTestId(codeTextInput).pressSequentially(code); +} + +export async function codeScreenExpect(page: Page, code: string) { + await expect(page.getByTestId(codeTextInput)).toHaveValue(code); + await expect(page.getByTestId("error").locator("div")).toContainText("Could not verify OTP code"); +} diff --git a/acceptance/tests/code.ts b/acceptance/tests/code.ts new file mode 100644 index 0000000000..a568e54999 --- /dev/null +++ b/acceptance/tests/code.ts @@ -0,0 +1,19 @@ +import { Page } from "@playwright/test"; +import { codeScreen } from "./code-screen"; +import { getCodeFromSink } from "./sink"; + +export async function codeFromSink(page: Page, key: string) { + // wait for send of the code + await page.waitForTimeout(2000); + const c = await getCodeFromSink(key); + await code(page, c); +} + +export async function code(page: Page, code: string) { + await codeScreen(page, code); + await page.getByTestId("submit-button").click(); +} + +export async function codeResend(page: Page) { + await page.getByTestId("resend-button").click(); +} diff --git a/acceptance/tests/login.ts b/acceptance/tests/login.ts index a9642573fe..f86bd712b2 100644 --- a/acceptance/tests/login.ts +++ b/acceptance/tests/login.ts @@ -1,4 +1,5 @@ import { expect, Page } from "@playwright/test"; +import { codeFromSink } from "./code"; import { loginname } from "./loginname"; import { password } from "./password"; @@ -23,6 +24,12 @@ export async function loginScreenExpect(page: Page, fullName: string) { await expect(page.getByRole("heading")).toContainText(fullName); } -export async function loginWithOTP(page: Page, username: string, password: string) { +export async function loginWithPasswordAndEmailOTP(page: Page, username: string, password: string, email: string) { await loginWithPassword(page, username, password); + await codeFromSink(page, email); +} + +export async function loginWithPasswordAndPhoneOTP(page: Page, username: string, password: string, phone: string) { + await loginWithPassword(page, username, password); + await codeFromSink(page, phone); } diff --git a/acceptance/tests/loginname-screen.ts b/acceptance/tests/loginname-screen.ts index 50f716d03b..0a7e247f9f 100644 --- a/acceptance/tests/loginname-screen.ts +++ b/acceptance/tests/loginname-screen.ts @@ -1,12 +1,12 @@ import { expect, Page } from "@playwright/test"; -const usernameUserInput = "username-text-input"; +const usernameTextInput = "username-text-input"; export async function loginnameScreen(page: Page, username: string) { - await page.getByTestId(usernameUserInput).pressSequentially(username); + await page.getByTestId(usernameTextInput).pressSequentially(username); } export async function loginnameScreenExpect(page: Page, username: string) { - await expect(page.getByTestId(usernameUserInput)).toHaveValue(username); + await expect(page.getByTestId(usernameTextInput)).toHaveValue(username); await expect(page.getByTestId("error").locator("div")).toContainText("Could not find user"); } diff --git a/acceptance/tests/otp.ts b/acceptance/tests/otp.ts deleted file mode 100644 index 85d3258442..0000000000 --- a/acceptance/tests/otp.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as http from "node:http"; - -let messages = new Map(); - -export function startSink() { - const hostname = "127.0.0.1"; - const port = 3030; - - const server = http.createServer((req, res) => { - console.log("Sink received message: "); - let body = ""; - req.on("data", (chunk) => { - body += chunk; - }); - - req.on("end", () => { - console.log(body); - const data = JSON.parse(body); - messages.set(data.contextInfo.recipientEmailAddress, data.args.code); - res.statusCode = 200; - res.setHeader("Content-Type", "text/plain"); - res.write("OK"); - res.end(); - }); - }); - - server.listen(port, hostname, () => { - console.log(`Sink running at http://${hostname}:${port}/`); - }); - return server; -} diff --git a/acceptance/tests/register.spec.ts b/acceptance/tests/register.spec.ts index 4f0b6d486d..98e5669e9e 100644 --- a/acceptance/tests/register.spec.ts +++ b/acceptance/tests/register.spec.ts @@ -1,13 +1,19 @@ +import { faker } from "@faker-js/faker"; import { test } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; import { loginScreenExpect } from "./login"; import { registerWithPasskey, registerWithPassword } from "./register"; import { removeUserByUsername } from "./zitadel"; +// Read from ".env" file. +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); + test("register with password", async ({ page }) => { - const username = "register-password@example.com"; + const username = faker.internet.email(); const password = "Password1!"; - const firstname = "firstname"; - const lastname = "lastname"; + const firstname = faker.person.firstName(); + const lastname = faker.person.lastName(); await removeUserByUsername(username); await registerWithPassword(page, firstname, lastname, username, password, password); @@ -15,9 +21,9 @@ test("register with password", async ({ page }) => { }); test("register with passkey", async ({ page }) => { - const username = "register-passkey@example.com"; - const firstname = "firstname"; - const lastname = "lastname"; + const username = faker.internet.email(); + const firstname = faker.person.firstName(); + const lastname = faker.person.lastName(); await removeUserByUsername(username); await registerWithPasskey(page, firstname, lastname, username); diff --git a/acceptance/tests/sink.ts b/acceptance/tests/sink.ts new file mode 100644 index 0000000000..2d6ad90135 --- /dev/null +++ b/acceptance/tests/sink.ts @@ -0,0 +1,28 @@ +import axios from "axios"; + +export async function getCodeFromSink(key: string): Promise { + try { + const response = await axios.post( + process.env.SINK_NOTIFICATION_URL, + { + recipient: key, + }, + { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, + }, + }, + ); + + if (response.status >= 400) { + const error = `HTTP Error: ${response.status} - ${response.statusText}`; + console.error(error); + throw new Error(error); + } + return response.data.args.oTP; + } catch (error) { + console.error("Error making request:", error); + throw error; + } +} diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index 522e76dc67..1e07b65a50 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -9,6 +9,7 @@ export interface userProps { lastName: string; organization: string; password: string; + phone: string; } class User { @@ -35,6 +36,10 @@ class User { email: this.props.email, isVerified: true, }, + phone: { + phone: this.props.phone!, + isVerified: true, + }, password: { password: this.props.password!, }, @@ -53,6 +58,7 @@ class User { console.error(error); throw new Error(error); } + this.setUserId(response.data.userId); } catch (error) { console.error("Error making request:", error); throw error; @@ -94,6 +100,10 @@ class User { return this.props.lastName; } + public getPhone() { + return this.props.phone; + } + public getFullName() { return `${this.props.firstName} ${this.props.lastName}`; } @@ -112,12 +122,12 @@ export interface otpUserProps { lastName: string; organization: string; password: string; + phone: string; type: OtpType; } export class PasswordUserWithOTP extends User { private type: OtpType; - private code: string; constructor(props: otpUserProps) { super({ @@ -126,6 +136,7 @@ export class PasswordUserWithOTP extends User { lastName: props.lastName, organization: props.organization, password: props.password, + phone: props.phone, }); this.type = props.type; } @@ -160,9 +171,6 @@ export class PasswordUserWithOTP extends User { console.error(error); throw new Error(error); } - - // TODO: get code from SMS or Email provider - this.code = ""; } catch (error) { console.error("Error making request:", error); throw error; @@ -171,10 +179,6 @@ export class PasswordUserWithOTP extends User { // wait for projection of user await page.waitForTimeout(2000); } - - public getCode() { - return this.code; - } } export interface passkeyUserProps { @@ -182,6 +186,7 @@ export interface passkeyUserProps { firstName: string; lastName: string; organization: string; + phone: string; } export class PasskeyUser extends User { @@ -194,6 +199,7 @@ export class PasskeyUser extends User { lastName: props.lastName, organization: props.organization, password: "", + phone: props.phone, }); } diff --git a/acceptance/tests/username-passkey.spec.ts b/acceptance/tests/username-passkey.spec.ts index d4d5e38ebe..304a17b16b 100644 --- a/acceptance/tests/username-passkey.spec.ts +++ b/acceptance/tests/username-passkey.spec.ts @@ -1,3 +1,4 @@ +import { faker } from "@faker-js/faker"; import { test as base } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; @@ -10,10 +11,11 @@ dotenv.config({ path: path.resolve(__dirname, ".env.local") }); const test = base.extend<{ user: PasskeyUser }>({ user: async ({ page }, use) => { const user = new PasskeyUser({ - email: "passkey@example.com", - firstName: "first", - lastName: "last", + email: faker.internet.email(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), organization: "", + phone: faker.phone.number(), }); await user.ensure(page); await use(user); @@ -25,7 +27,7 @@ test("username and passkey login", async ({ user, page }) => { await loginScreenExpect(page, user.getFullName()); }); -test("username and passkey login, if passkey enabled", async ({ user, page }) => { +test("username and passkey login, if passkey enabled", async ({ page }) => { // Given passkey is enabled on the organization of the user // Given the user has only passkey enabled as authentication // enter username @@ -34,7 +36,7 @@ test("username and passkey login, if passkey enabled", async ({ user, page }) => // user is redirected to app }); -test("username and passkey login, multiple auth methods", async ({ user, page }) => { +test("username and passkey login, multiple auth methods", async ({ page }) => { // Given passkey and password is enabled on the organization of the user // Given the user has password and passkey registered // enter username diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index 5e2b31344b..9a9e4b67e3 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -1,3 +1,4 @@ +import { faker } from "@faker-js/faker"; import { test as base } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; @@ -12,11 +13,12 @@ dotenv.config({ path: path.resolve(__dirname, ".env.local") }); const test = base.extend<{ user: PasswordUser }>({ user: async ({ page }, use) => { const user = new PasswordUser({ - email: "password-changed@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", + email: faker.internet.email(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), organization: "", + phone: faker.phone.number(), + password: "Password1!", }); await user.ensure(page); await use(user); diff --git a/acceptance/tests/username-password-otp_email.spec.ts b/acceptance/tests/username-password-otp_email.spec.ts index 08e878adc3..ca318e9fee 100644 --- a/acceptance/tests/username-password-otp_email.spec.ts +++ b/acceptance/tests/username-password-otp_email.spec.ts @@ -1,6 +1,33 @@ -import { test } from "@playwright/test"; +import { faker } from "@faker-js/faker"; +import { test as base } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { code, codeFromSink, codeResend } from "./code"; +import { codeScreenExpect } from "./code-screen"; +import { loginScreenExpect, loginWithPassword, loginWithPasswordAndEmailOTP } from "./login"; +import { OtpType, PasswordUserWithOTP } from "./user"; -test("username, password and email otp login, enter code manually", async ({ page }) => { +// Read from ".env" file. +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); + +const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({ + user: async ({ page }, use) => { + const user = new PasswordUserWithOTP({ + email: faker.internet.email(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + organization: "", + phone: faker.phone.number(), + password: "Password1!", + type: OtpType.email, + }); + + await user.ensure(page); + await use(user); + }, +}); + +test("username, password and email otp login, enter code manually", async ({ user, page }) => { // Given email otp is enabled on the organization of the user // Given the user has only email otp configured as second factor // User enters username @@ -8,6 +35,8 @@ test("username, password and email otp login, enter code manually", async ({ pag // User receives an email with a verification code // User enters the code into the ui // User is redirected to the app (default redirect url) + await loginWithPasswordAndEmailOTP(page, user.getUsername(), user.getPassword(), user.getUsername()); + await loginScreenExpect(page, user.getFullName()); }); test("username, password and email otp login, click link in email", async ({ page }) => { @@ -20,7 +49,7 @@ test("username, password and email otp login, click link in email", async ({ pag // User is redirected to the app (default redirect url) }); -test("username, password and email otp login, resend code", async ({ page }) => { +test("username, password and email otp login, resend code", async ({ user, page }) => { // Given email otp is enabled on the organization of the user // Given the user has only email otp configured as second factor // User enters username @@ -30,16 +59,24 @@ test("username, password and email otp login, resend code", async ({ page }) => // User receives a new email with a verification code // User enters the new code in the ui // User is redirected to the app (default redirect url) + await loginWithPassword(page, user.getUsername(), user.getPassword()); + await codeResend(page); + await codeFromSink(page, user.getUsername()); + await loginScreenExpect(page, user.getFullName()); }); -test("username, password and email otp login, wrong code", async ({ page }) => { +test("username, password and email otp login, wrong code", async ({ user, page }) => { // Given email otp is enabled on the organization of the user // Given the user has only email otp configured as second factor // User enters username // User enters password // User receives an email with a verification code - // User enters a wrond code + // User enters a wrong code // Error message - "Invalid code" is shown + const c = "wrongcode"; + await loginWithPassword(page, user.getUsername(), user.getPassword()); + await code(page, c); + await codeScreenExpect(page, c); }); test("username, password and email otp login, multiple mfa options", async ({ page }) => { @@ -49,7 +86,7 @@ test("username, password and email otp login, multiple mfa options", async ({ pa // User enters password // User receives an email with a verification code // User clicks button to use sms otp as second factor - // User receives an sms with a verification code + // User receives a sms with a verification code // User enters code in ui // User is redirected to the app (default redirect url) }); diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index 8f9cca152e..2cb5c91e77 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -1,6 +1,30 @@ -import { test } from "@playwright/test"; +import { faker } from "@faker-js/faker"; +import { test as base } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { OtpType, PasswordUserWithOTP } from "./user"; -test("username, password and sms otp login", async ({ page }) => { +// Read from ".env" file. +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); + +const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({ + user: async ({ page }, use) => { + const user = new PasswordUserWithOTP({ + email: faker.internet.email(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + organization: "", + phone: faker.phone.number(), + password: "Password1!", + type: OtpType.sms, + }); + + await user.ensure(page); + await use(user); + }, +}); + +test("username, password and sms otp login, enter code manually", async ({ user, page }) => { // Given sms otp is enabled on the organization of the user // Given the user has only sms otp configured as second factor // User enters username @@ -8,9 +32,13 @@ test("username, password and sms otp login", async ({ page }) => { // User receives a sms with a verification code // User enters the code into the ui // User is redirected to the app (default redirect url) + /* TODO fix on login, that sms is sent + await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone()); + await loginScreenExpect(page, user.getFullName()); + */ }); -test("username, password and sms otp login, resend code", async ({ page }) => { +test("username, password and sms otp login, resend code", async ({ user, page }) => { // Given sms otp is enabled on the organization of the user // Given the user has only sms otp configured as second factor // User enters username @@ -19,9 +47,14 @@ test("username, password and sms otp login, resend code", async ({ page }) => { // User clicks resend code // User receives a new sms with a verification code // User is redirected to the app (default redirect url) + /* TODO fix on login, that sms is sent +await loginWithPassword(page, user.getUsername(), user.getPassword()); +await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone()); +await loginScreenExpect(page, user.getFullName()); + */ }); -test("username, password and sms otp login, wrong code", async ({ page }) => { +test("username, password and sms otp login, wrong code", async ({ user, page }) => { // Given sms otp is enabled on the organization of the user // Given the user has only sms otp configured as second factor // User enters username @@ -29,4 +62,10 @@ test("username, password and sms otp login, wrong code", async ({ page }) => { // User receives a sms with a verification code // User enters a wrong code // Error message - "Invalid code" is shown + /* TODO fix on login, that sms is sent +const c = "wrongcode"; +await loginWithPassword(page, user.getUsername(), user.getPassword()); +await code(page, c); +await codeScreenExpect(page, c); + */ }); diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index d905170a44..3f6bfd48f7 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -1,3 +1,4 @@ +import { faker } from "@faker-js/faker"; import { test as base } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; @@ -14,11 +15,12 @@ dotenv.config({ path: path.resolve(__dirname, ".env.local") }); const test = base.extend<{ user: PasswordUser }>({ user: async ({ page }, use) => { const user = new PasswordUser({ - email: "password@example.com", - firstName: "first", - lastName: "last", - password: "Password1!", + email: faker.internet.email(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), organization: "", + phone: faker.phone.number(), + password: "Password1!", }); await user.ensure(page); await use(user); diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 971920606e..59a0979db0 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -214,6 +214,7 @@ export function LoginOTP({ setLoading(false); }); }} + data-testid="resend-button" > {t("verify.resendCode")} @@ -226,11 +227,12 @@ export function LoginOTP({ {...register("code", { required: "This field is required" })} label="Code" autoComplete="one-time-code" + data-testid="code-text-input" /> {error && ( -
+
{error}
)} diff --git a/package.json b/package.json index 3636acdfb6..2760d8e04b 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "changeset": "changeset", "version-packages": "changeset version", "release": "turbo run build --filter=login^... && changeset publish", - "run-zitadel": "docker compose -f ./acceptance/docker-compose.yaml run setup" + "run-zitadel": "docker compose -f ./acceptance/docker-compose.yaml run setup", + "run-sink": "docker compose -f ./acceptance/docker-compose.yaml up -d sink" }, "pnpm": { "overrides": { @@ -29,6 +30,7 @@ } }, "devDependencies": { + "@faker-js/faker": "^9.2.0", "@changesets/cli": "^2.27.9", "@playwright/test": "^1.48.2", "@types/node": "^22.9.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0381141c34..283bcc4864 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@changesets/cli': specifier: ^2.27.9 version: 2.27.9 + '@faker-js/faker': + specifier: ^9.2.0 + version: 9.2.0 '@playwright/test': specifier: ^1.48.2 version: 1.48.2 @@ -28,7 +31,7 @@ importers: version: link:packages/zitadel-prettier-config axios: specifier: ^1.7.7 - version: 1.7.7 + version: 1.7.7(debug@4.3.7) dotenv: specifier: ^16.4.5 version: 16.4.5 @@ -847,6 +850,10 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@faker-js/faker@9.2.0': + resolution: {integrity: sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==} + engines: {node: '>=18.0.0', npm: '>=9.0.0'} + '@fastify/busboy@2.1.1': resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} @@ -4633,7 +4640,7 @@ snapshots: '@babel/traverse': 7.25.9 '@babel/types': 7.26.0 convert-source-map: 2.0.0 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -4720,7 +4727,7 @@ snapshots: '@babel/parser': 7.26.2 '@babel/template': 7.25.9 '@babel/types': 7.26.0 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5110,7 +5117,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -5123,6 +5130,8 @@ snapshots: '@eslint/js@8.57.1': {} + '@faker-js/faker@9.2.0': {} + '@fastify/busboy@2.1.1': {} '@floating-ui/core@1.6.8': @@ -5209,7 +5218,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -5756,7 +5765,7 @@ snapshots: agent-base@7.1.1: dependencies: - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -5931,14 +5940,6 @@ snapshots: axe-core@4.10.0: {} - axios@1.7.7: - dependencies: - follow-redirects: 1.15.6 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - axios@1.7.7(debug@4.3.7): dependencies: follow-redirects: 1.15.6(debug@4.3.7) @@ -6285,10 +6286,6 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.3.7: - dependencies: - ms: 2.1.3 - debug@4.3.7(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -6765,7 +6762,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -6955,8 +6952,6 @@ snapshots: flatted@3.3.1: {} - follow-redirects@1.15.6: {} - follow-redirects@1.15.6(debug@4.3.7): optionalDependencies: debug: 4.3.7(supports-color@5.5.0) @@ -7191,7 +7186,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -7211,7 +7206,7 @@ snapshots: https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.1 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -8735,7 +8730,7 @@ snapshots: cac: 6.7.14 chokidar: 4.0.1 consola: 3.2.3 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) esbuild: 0.24.0 joycon: 3.1.1 picocolors: 1.1.1 @@ -8893,7 +8888,7 @@ snapshots: vite-node@2.1.4(@types/node@22.9.0)(sass@1.80.7): dependencies: cac: 6.7.14 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) pathe: 1.1.2 vite: 5.4.11(@types/node@22.9.0)(sass@1.80.7) transitivePeerDependencies: @@ -8909,7 +8904,7 @@ snapshots: vite-tsconfig-paths@5.1.2(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(sass@1.80.7)): dependencies: - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) globrex: 0.1.2 tsconfck: 3.1.4(typescript@5.6.3) optionalDependencies: @@ -8938,7 +8933,7 @@ snapshots: '@vitest/spy': 2.1.4 '@vitest/utils': 2.1.4 chai: 5.1.2 - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) expect-type: 1.1.0 magic-string: 0.30.12 pathe: 1.1.2 From f38b8b753caee255b281bdef33b9e04f56959d32 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:01:12 +0100 Subject: [PATCH 2/5] chore: add sms otp and password set acceptance tests --- README.md | 18 +++++++ acceptance/tests/code.ts | 6 +-- acceptance/tests/login.ts | 6 +-- acceptance/tests/password-screen.ts | 25 ++++++++++ acceptance/tests/password.ts | 13 ++++- acceptance/tests/sink.ts | 29 ++++++++++- acceptance/tests/user.ts | 13 ++--- acceptance/tests/username-passkey.spec.ts | 9 ---- .../tests/username-password-changed.spec.ts | 2 +- .../tests/username-password-otp_email.spec.ts | 4 +- .../tests/username-password-otp_sms.spec.ts | 25 ++++------ .../tests/username-password-set.spec.ts | 50 +++++++++++++++++++ apps/login/src/components/login-otp.tsx | 2 +- apps/login/src/components/password-form.tsx | 1 + .../src/components/set-password-form.tsx | 8 ++- 15 files changed, 165 insertions(+), 46 deletions(-) create mode 100644 acceptance/tests/username-password-set.spec.ts diff --git a/README.md b/README.md index f4878ecacb..429ce84c78 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,24 @@ pnpm test To satisfy your unique workflow requirements, check out the package.json in the root directory for more detailed scripts. +### Run Login UI Acceptance tests + +To run the acceptance tests you need a running ZITADEL environment and a component which receives HTTP requests for the emails and sms's. +This component should also be able to return the content of these notifications, as the codes and links are used in the login flows. +There is a basic implementation in Golang available under [the sink package](./acceptance/sink). + +To setup ZITADEL with the additional Sink container for handling the notifications: + +```sh +pnpm run-sink +``` + +Then you can start the acceptance tests with: + +```sh +pnpm test:acceptance +``` + ### Deploy to Vercel To deploy your own version on Vercel, navigate to your instance and create a service user. diff --git a/acceptance/tests/code.ts b/acceptance/tests/code.ts index ff8643e404..1ae8f69791 100644 --- a/acceptance/tests/code.ts +++ b/acceptance/tests/code.ts @@ -1,11 +1,11 @@ import { Page } from "@playwright/test"; import { codeScreen } from "./code-screen"; -import { getCodeFromSink } from "./sink"; +import { getOtpFromSink } from "./sink"; -export async function codeFromSink(page: Page, key: string) { +export async function otpFromSink(page: Page, key: string) { // wait for send of the code await page.waitForTimeout(3000); - const c = await getCodeFromSink(key); + const c = await getOtpFromSink(key); await code(page, c); } diff --git a/acceptance/tests/login.ts b/acceptance/tests/login.ts index f86bd712b2..9af0ecfb76 100644 --- a/acceptance/tests/login.ts +++ b/acceptance/tests/login.ts @@ -1,5 +1,5 @@ import { expect, Page } from "@playwright/test"; -import { codeFromSink } from "./code"; +import { otpFromSink } from "./code"; import { loginname } from "./loginname"; import { password } from "./password"; @@ -26,10 +26,10 @@ export async function loginScreenExpect(page: Page, fullName: string) { export async function loginWithPasswordAndEmailOTP(page: Page, username: string, password: string, email: string) { await loginWithPassword(page, username, password); - await codeFromSink(page, email); + await otpFromSink(page, email); } export async function loginWithPasswordAndPhoneOTP(page: Page, username: string, password: string, phone: string) { await loginWithPassword(page, username, password); - await codeFromSink(page, phone); + await otpFromSink(page, phone); } diff --git a/acceptance/tests/password-screen.ts b/acceptance/tests/password-screen.ts index 49e8843822..57334a07d2 100644 --- a/acceptance/tests/password-screen.ts +++ b/acceptance/tests/password-screen.ts @@ -1,5 +1,7 @@ import { expect, Page } from "@playwright/test"; +import { getCodeFromSink } from "./sink"; +const codeField = "code-text-input"; const passwordField = "password-text-input"; const passwordConfirmField = "password-confirm-text-input"; const lengthCheck = "length-check"; @@ -55,3 +57,26 @@ async function checkContent(page: Page, testid: string, match: boolean) { await expect(page.getByTestId(testid)).toContainText(noMatchText); } } + +export async function resetPasswordScreen(page: Page, username: string, password1: string, password2: string) { + // wait for send of the code + await page.waitForTimeout(3000); + const c = await getCodeFromSink(username); + await page.getByTestId(codeField).pressSequentially(c); + await page.getByTestId(passwordField).pressSequentially(password1); + await page.getByTestId(passwordConfirmField).pressSequentially(password2); +} + +export async function resetPasswordScreenExpect( + page: Page, + password1: string, + password2: string, + length: boolean, + symbol: boolean, + number: boolean, + uppercase: boolean, + lowercase: boolean, + equals: boolean, +) { + await changePasswordScreenExpect(page, password1, password2, length, symbol, number, uppercase, lowercase, equals); +} diff --git a/acceptance/tests/password.ts b/acceptance/tests/password.ts index e8cd787b04..b3d31fcaee 100644 --- a/acceptance/tests/password.ts +++ b/acceptance/tests/password.ts @@ -1,7 +1,8 @@ import { Page } from "@playwright/test"; -import { changePasswordScreen, passwordScreen } from "./password-screen"; +import { changePasswordScreen, passwordScreen, resetPasswordScreen } from "./password-screen"; const passwordSubmitButton = "submit-button"; +const passwordResetButton = "reset-button"; export async function startChangePassword(page: Page, loginname: string) { await page.goto("/password/change?" + new URLSearchParams({ loginName: loginname })); @@ -17,3 +18,13 @@ export async function password(page: Page, password: string) { await passwordScreen(page, password); await page.getByTestId(passwordSubmitButton).click(); } + +export async function startResetPassword(page: Page) { + await page.getByTestId(passwordResetButton).click(); +} + +export async function resetPassword(page: Page, username: string, password: string) { + await startResetPassword(page); + await resetPasswordScreen(page, username, password, password); + await page.getByTestId(passwordSubmitButton).click(); +} diff --git a/acceptance/tests/sink.ts b/acceptance/tests/sink.ts index d3cfa7ddd7..fc13a98dc7 100644 --- a/acceptance/tests/sink.ts +++ b/acceptance/tests/sink.ts @@ -1,6 +1,6 @@ import axios from "axios"; -export async function getCodeFromSink(key: string): Promise { +export async function getOtpFromSink(key: string): Promise { try { const response = await axios.post( process.env.SINK_NOTIFICATION_URL!, @@ -26,3 +26,30 @@ export async function getCodeFromSink(key: string): Promise { throw error; } } + +export async function getCodeFromSink(key: string): Promise { + try { + const response = await axios.post( + process.env.SINK_NOTIFICATION_URL!, + { + recipient: key, + }, + { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, + }, + }, + ); + + if (response.status >= 400) { + const error = `HTTP Error: ${response.status} - ${response.statusText}`; + console.error(error); + throw new Error(error); + } + return response.data.args.code; + } catch (error) { + console.error("Error making request:", error); + throw error; + } +} diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index 1e07b65a50..9b2bfb46d5 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -21,8 +21,6 @@ class User { } async ensure(page: Page) { - await this.remove(); - const body = { username: this.props.email, organization: { @@ -53,7 +51,7 @@ class User { }, }); - if (response.status >= 400 && response.status !== 409) { + if (response.status >= 400) { const error = `HTTP Error: ${response.status} - ${response.statusText}`; console.error(error); throw new Error(error); @@ -65,7 +63,7 @@ class User { } // wait for projection of user - await page.waitForTimeout(3000); + await page.waitForTimeout(2000); } async remove() { @@ -166,7 +164,7 @@ export class PasswordUserWithOTP extends User { }, ); - if (response.status >= 400 && response.status !== 409) { + if (response.status >= 400) { const error = `HTTP Error: ${response.status} - ${response.statusText}`; console.error(error); throw new Error(error); @@ -204,7 +202,6 @@ export class PasskeyUser extends User { } public async ensure(page: Page) { - await this.remove(); const authId = await registerWithPasskey(page, this.getFirstname(), this.getLastname(), this.getUsername()); this.authenticatorId = authId; @@ -212,10 +209,6 @@ export class PasskeyUser extends User { await page.waitForTimeout(2000); } - public async remove() { - await super.remove(); - } - public getAuthenticatorId(): string { return this.authenticatorId; } diff --git a/acceptance/tests/username-passkey.spec.ts b/acceptance/tests/username-passkey.spec.ts index 304a17b16b..7fbf290d99 100644 --- a/acceptance/tests/username-passkey.spec.ts +++ b/acceptance/tests/username-passkey.spec.ts @@ -27,15 +27,6 @@ test("username and passkey login", async ({ user, page }) => { await loginScreenExpect(page, user.getFullName()); }); -test("username and passkey login, if passkey enabled", async ({ page }) => { - // Given passkey is enabled on the organization of the user - // Given the user has only passkey enabled as authentication - // enter username - // passkey popup is directly shown - // user verifies passkey - // user is redirected to app -}); - test("username and passkey login, multiple auth methods", async ({ page }) => { // Given passkey and password is enabled on the organization of the user // Given the user has password and passkey registered diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index 9a9e4b67e3..cf3af995f7 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -42,7 +42,7 @@ test("username and password changed login", async ({ user, page }) => { */ }); -test("password not with desired complexity", async ({ user, page }) => { +test("password change not with desired complexity", async ({ user, page }) => { const changedPw1 = "change"; const changedPw2 = "chang"; await loginWithPassword(page, user.getUsername(), user.getPassword()); diff --git a/acceptance/tests/username-password-otp_email.spec.ts b/acceptance/tests/username-password-otp_email.spec.ts index ca318e9fee..b684fa80c6 100644 --- a/acceptance/tests/username-password-otp_email.spec.ts +++ b/acceptance/tests/username-password-otp_email.spec.ts @@ -2,7 +2,7 @@ import { faker } from "@faker-js/faker"; import { test as base } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; -import { code, codeFromSink, codeResend } from "./code"; +import { code, codeResend, otpFromSink } from "./code"; import { codeScreenExpect } from "./code-screen"; import { loginScreenExpect, loginWithPassword, loginWithPasswordAndEmailOTP } from "./login"; import { OtpType, PasswordUserWithOTP } from "./user"; @@ -61,7 +61,7 @@ test("username, password and email otp login, resend code", async ({ user, page // User is redirected to the app (default redirect url) await loginWithPassword(page, user.getUsername(), user.getPassword()); await codeResend(page); - await codeFromSink(page, user.getUsername()); + await otpFromSink(page, user.getUsername()); await loginScreenExpect(page, user.getFullName()); }); diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index 2cb5c91e77..12cdc6c740 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -2,6 +2,9 @@ import { faker } from "@faker-js/faker"; import { test as base } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; +import { code } from "./code"; +import { codeScreenExpect } from "./code-screen"; +import { loginScreenExpect, loginWithPassword, loginWithPasswordAndPhoneOTP } from "./login"; import { OtpType, PasswordUserWithOTP } from "./user"; // Read from ".env" file. @@ -14,7 +17,7 @@ const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({ firstName: faker.person.firstName(), lastName: faker.person.lastName(), organization: "", - phone: faker.phone.number(), + phone: faker.phone.number({ style: "international" }), password: "Password1!", type: OtpType.sms, }); @@ -32,10 +35,8 @@ test("username, password and sms otp login, enter code manually", async ({ user, // User receives a sms with a verification code // User enters the code into the ui // User is redirected to the app (default redirect url) - /* TODO fix on login, that sms is sent await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone()); await loginScreenExpect(page, user.getFullName()); - */ }); test("username, password and sms otp login, resend code", async ({ user, page }) => { @@ -47,11 +48,9 @@ test("username, password and sms otp login, resend code", async ({ user, page }) // User clicks resend code // User receives a new sms with a verification code // User is redirected to the app (default redirect url) - /* TODO fix on login, that sms is sent -await loginWithPassword(page, user.getUsername(), user.getPassword()); -await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone()); -await loginScreenExpect(page, user.getFullName()); - */ + await loginWithPassword(page, user.getUsername(), user.getPassword()); + await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone()); + await loginScreenExpect(page, user.getFullName()); }); test("username, password and sms otp login, wrong code", async ({ user, page }) => { @@ -62,10 +61,8 @@ test("username, password and sms otp login, wrong code", async ({ user, page }) // User receives a sms with a verification code // User enters a wrong code // Error message - "Invalid code" is shown - /* TODO fix on login, that sms is sent -const c = "wrongcode"; -await loginWithPassword(page, user.getUsername(), user.getPassword()); -await code(page, c); -await codeScreenExpect(page, c); - */ + const c = "wrongcode"; + await loginWithPassword(page, user.getUsername(), user.getPassword()); + await code(page, c); + await codeScreenExpect(page, c); }); diff --git a/acceptance/tests/username-password-set.spec.ts b/acceptance/tests/username-password-set.spec.ts new file mode 100644 index 0000000000..c2a60bd410 --- /dev/null +++ b/acceptance/tests/username-password-set.spec.ts @@ -0,0 +1,50 @@ +import { faker } from "@faker-js/faker"; +import { test as base } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { loginScreenExpect, loginWithPassword, startLogin } from "./login"; +import { loginname } from "./loginname"; +import { resetPassword, startResetPassword } from "./password"; +import { resetPasswordScreen, resetPasswordScreenExpect } from "./password-screen"; +import { PasswordUser } from "./user"; + +// Read from ".env" file. +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); + +const test = base.extend<{ user: PasswordUser }>({ + user: async ({ page }, use) => { + const user = new PasswordUser({ + email: faker.internet.email(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + organization: "", + phone: faker.phone.number(), + password: "Password1!", + }); + await user.ensure(page); + await use(user); + }, +}); + +test("username and password set login", async ({ user, page }) => { + // commented, fix in https://github.com/zitadel/zitadel/pull/8807 + + const changedPw = "ChangedPw1!"; + await startLogin(page); + await loginname(page, user.getUsername()); + await resetPassword(page, user.getUsername(), changedPw); + await loginScreenExpect(page, user.getFullName()); + + await loginWithPassword(page, user.getUsername(), changedPw); + await loginScreenExpect(page, user.getFullName()); +}); + +test("password set not with desired complexity", async ({ user, page }) => { + const changedPw1 = "change"; + const changedPw2 = "chang"; + await startLogin(page); + await loginname(page, user.getUsername()); + await startResetPassword(page); + await resetPasswordScreen(page, user.getUsername(), changedPw1, changedPw2); + await resetPasswordScreenExpect(page, changedPw1, changedPw2, false, false, false, false, true, false); +}); diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 59a0979db0..922b43fa14 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -77,7 +77,7 @@ export function LoginOTP({ if (method === "sms") { challenges = create(RequestChallengesSchema, { - otpSms: { returnCode: true }, + otpSms: {}, }); } diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index 5885ecc6d4..490e5e0608 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -134,6 +134,7 @@ export function PasswordForm({ onClick={() => resetPasswordAndContinue()} type="button" disabled={loading} + data-testid="reset-button" > {t("verify.resetPassword")} diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 65405ed8f0..ca87c85c9f 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -170,11 +170,15 @@ export function SetPasswordForm({ label="Code" autoComplete="one-time-code" error={errors.code?.message as string} + data-testid="code-text-input" />
-
@@ -190,6 +194,7 @@ export function SetPasswordForm({ })} label="New Password" error={errors.password?.message as string} + data-testid="password-text-input" />
@@ -202,6 +207,7 @@ export function SetPasswordForm({ })} label="Confirm Password" error={errors.confirmPassword?.message as string} + data-testid="password-confirm-text-input" />
From f0d9d3b4291834ecbc5bf259b549778f0428e183 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 28 Nov 2024 09:57:20 +0100 Subject: [PATCH 3/5] chore: add totp acceptance tests --- acceptance/tests/login.ts | 8 +- acceptance/tests/user.ts | 94 ++---- .../tests/username-password-otp_sms.spec.ts | 1 - .../tests/username-password-totp.spec.ts | 38 ++- acceptance/tests/zitadel.ts | 105 ++++++- package.json | 3 + pnpm-lock.yaml | 295 ++++++++++-------- 7 files changed, 330 insertions(+), 214 deletions(-) diff --git a/acceptance/tests/login.ts b/acceptance/tests/login.ts index 9af0ecfb76..32c0007a3c 100644 --- a/acceptance/tests/login.ts +++ b/acceptance/tests/login.ts @@ -1,7 +1,8 @@ import { expect, Page } from "@playwright/test"; -import { otpFromSink } from "./code"; +import { code, otpFromSink } from "./code"; import { loginname } from "./loginname"; import { password } from "./password"; +import { totp } from "./zitadel"; export async function startLogin(page: Page) { await page.goto("/loginname"); @@ -33,3 +34,8 @@ export async function loginWithPasswordAndPhoneOTP(page: Page, username: string, await loginWithPassword(page, username, password); await otpFromSink(page, phone); } + +export async function loginWithPasswordAndTOTP(page: Page, username: string, password: string, secret: string) { + await loginWithPassword(page, username, password); + await code(page, totp(secret)); +} diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index 9b2bfb46d5..d8a967aed6 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -1,7 +1,6 @@ import { Page } from "@playwright/test"; -import axios from "axios"; import { registerWithPasskey } from "./register"; -import { getUserByUsername, removeUser } from "./zitadel"; +import { activateOTP, addTOTP, addUser, getUserByUsername, removeUser } from "./zitadel"; export interface userProps { email: string; @@ -21,47 +20,9 @@ class User { } async ensure(page: Page) { - const body = { - username: this.props.email, - organization: { - orgId: this.props.organization, - }, - profile: { - givenName: this.props.firstName, - familyName: this.props.lastName, - }, - email: { - email: this.props.email, - isVerified: true, - }, - phone: { - phone: this.props.phone!, - isVerified: true, - }, - password: { - password: this.props.password!, - }, - }; - - try { - const response = await axios.post(`${process.env.ZITADEL_API_URL}/v2/users/human`, body, { - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, - }, - }); - - if (response.status >= 400) { - const error = `HTTP Error: ${response.status} - ${response.statusText}`; - console.error(error); - throw new Error(error); - } - this.setUserId(response.data.userId); - } catch (error) { - console.error("Error making request:", error); - throw error; - } + const response = await addUser(this.props); + this.setUserId(response.userId); // wait for projection of user await page.waitForTimeout(2000); } @@ -142,43 +103,30 @@ export class PasswordUserWithOTP extends User { async ensure(page: Page) { await super.ensure(page); - let url = "otp_"; - switch (this.type) { - case OtpType.sms: - url = url + "sms"; - break; - case OtpType.email: - url = url + "email"; - break; - } - - try { - const response = await axios.post( - `${process.env.ZITADEL_API_URL}/v2/users/${this.getUserId()}/${url}`, - {}, - { - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, - }, - }, - ); - - if (response.status >= 400) { - const error = `HTTP Error: ${response.status} - ${response.statusText}`; - console.error(error); - throw new Error(error); - } - } catch (error) { - console.error("Error making request:", error); - throw error; - } + await activateOTP(this.getUserId(), this.type); // wait for projection of user await page.waitForTimeout(2000); } } +export class PasswordUserWithTOTP extends User { + private secret: string; + + async ensure(page: Page) { + await super.ensure(page); + + this.secret = await addTOTP(this.getUserId()); + + // wait for projection of user + await page.waitForTimeout(2000); + } + + public getSecret(): string { + return this.secret; + } +} + export interface passkeyUserProps { email: string; firstName: string; diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index 12cdc6c740..86220fd2d6 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -48,7 +48,6 @@ test("username, password and sms otp login, resend code", async ({ user, page }) // User clicks resend code // User receives a new sms with a verification code // User is redirected to the app (default redirect url) - await loginWithPassword(page, user.getUsername(), user.getPassword()); await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone()); await loginScreenExpect(page, user.getFullName()); }); diff --git a/acceptance/tests/username-password-totp.spec.ts b/acceptance/tests/username-password-totp.spec.ts index 8fc00f9fee..33b1d9c09f 100644 --- a/acceptance/tests/username-password-totp.spec.ts +++ b/acceptance/tests/username-password-totp.spec.ts @@ -1,6 +1,32 @@ -import { test } from "@playwright/test"; +import { faker } from "@faker-js/faker"; +import { test as base } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { code } from "./code"; +import { codeScreenExpect } from "./code-screen"; +import { loginScreenExpect, loginWithPassword, loginWithPasswordAndTOTP } from "./login"; +import { PasswordUserWithTOTP } from "./user"; -test("username, password and totp login", async ({ page }) => { +// Read from ".env" file. +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); + +const test = base.extend<{ user: PasswordUserWithTOTP; sink: any }>({ + user: async ({ page }, use) => { + const user = new PasswordUserWithTOTP({ + email: faker.internet.email(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + organization: "", + phone: faker.phone.number({ style: "international" }), + password: "Password1!", + }); + + await user.ensure(page); + await use(user); + }, +}); + +test("username, password and totp login", async ({ user, page }) => { // Given totp is enabled on the organization of the user // Given the user has only totp configured as second factor // User enters username @@ -8,9 +34,11 @@ test("username, password and totp login", async ({ page }) => { // Screen for entering the code is shown directly // User enters the code into the ui // User is redirected to the app (default redirect url) + await loginWithPasswordAndTOTP(page, user.getUsername(), user.getPassword(), user.getSecret()); + await loginScreenExpect(page, user.getFullName()); }); -test("username, password and totp otp login, wrong code", async ({ page }) => { +test("username, password and totp otp login, wrong code", async ({ user, page }) => { // Given totp is enabled on the organization of the user // Given the user has only totp configured as second factor // User enters username @@ -18,6 +46,10 @@ test("username, password and totp otp login, wrong code", async ({ page }) => { // Screen for entering the code is shown directly // User enters a wrond code // Error message - "Invalid code" is shown + const c = "wrongcode"; + await loginWithPassword(page, user.getUsername(), user.getPassword()); + await code(page, c); + await codeScreenExpect(page, c); }); test("username, password and totp login, multiple mfa options", async ({ page }) => { diff --git a/acceptance/tests/zitadel.ts b/acceptance/tests/zitadel.ts index aa0c03ffdf..587723f10d 100644 --- a/acceptance/tests/zitadel.ts +++ b/acceptance/tests/zitadel.ts @@ -1,4 +1,34 @@ +import { Authenticator } from "@otplib/core"; +import { createDigest, createRandomBytes } from "@otplib/plugin-crypto"; +import { keyDecoder, keyEncoder } from "@otplib/plugin-thirty-two"; // use your chosen base32 plugin import axios from "axios"; +import { OtpType, userProps } from "./user"; + +export async function addUser(props: userProps) { + const body = { + username: props.email, + organization: { + orgId: props.organization, + }, + profile: { + givenName: props.firstName, + familyName: props.lastName, + }, + email: { + email: props.email, + isVerified: true, + }, + phone: { + phone: props.phone!, + isVerified: true, + }, + password: { + password: props.password!, + }, + }; + + return await listCall(`${process.env.ZITADEL_API_URL}/v2/users/human`, body); +} export async function removeUserByUsername(username: string) { const resp = await getUserByUsername(username); @@ -9,8 +39,12 @@ export async function removeUserByUsername(username: string) { } export async function removeUser(id: string) { + await deleteCall(`${process.env.ZITADEL_API_URL}/v2/users/${id}`); +} + +async function deleteCall(url: string) { try { - const response = await axios.delete(`${process.env.ZITADEL_API_URL}/v2/users/${id}`, { + const response = await axios.delete(url, { headers: { Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, }, @@ -27,7 +61,7 @@ export async function removeUser(id: string) { } } -export async function getUserByUsername(username: string) { +export async function getUserByUsername(username: string): Promise { const listUsersBody = { queries: [ { @@ -38,8 +72,12 @@ export async function getUserByUsername(username: string) { ], }; + return await listCall(`${process.env.ZITADEL_API_URL}/v2/users`, listUsersBody); +} + +async function listCall(url: string, data: any): Promise { try { - const response = await axios.post(`${process.env.ZITADEL_API_URL}/v2/users`, listUsersBody, { + const response = await axios.post(url, data, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, @@ -58,3 +96,64 @@ export async function getUserByUsername(username: string) { throw error; } } + +export async function activateOTP(userId: string, type: OtpType) { + let url = "otp_"; + switch (type) { + case OtpType.sms: + url = url + "sms"; + break; + case OtpType.email: + url = url + "email"; + break; + } + + await pushCall(`${process.env.ZITADEL_API_URL}/v2/users/${userId}/${url}`, {}); +} + +async function pushCall(url: string, data: any) { + try { + const response = await axios.post(url, data, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, + }, + }); + + if (response.status >= 400) { + const error = `HTTP Error: ${response.status} - ${response.statusText}`; + console.error(error); + throw new Error(error); + } + } catch (error) { + console.error("Error making request:", error); + throw error; + } +} + +export async function addTOTP(userId: string): Promise { + const response = await listCall(`${process.env.ZITADEL_API_URL}/v2/users/${userId}/totp`, {}); + const code = totp(response.secret); + await pushCall(`${process.env.ZITADEL_API_URL}/v2/users/${userId}/totp/verify`, { code: code }); + return response.secret; +} + +export function totp(secret: string) { + const authenticator = new Authenticator({ + createDigest, + createRandomBytes, + keyDecoder, + keyEncoder, + }); + // google authenticator usage + const token = authenticator.generate(secret); + + // check if token can be used + if (!authenticator.verify({ token: token, secret: secret })) { + const error = `Generated token could not be verified`; + console.error(error); + throw new Error(error); + } + + return token; +} diff --git a/package.json b/package.json index 438380b2ff..c2777dc9cd 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,9 @@ } }, "devDependencies": { + "@otplib/core": "^12.0.0", + "@otplib/plugin-thirty-two": "^12.0.0", + "@otplib/plugin-crypto": "^12.0.0", "@faker-js/faker": "^9.2.0", "@changesets/cli": "^2.27.9", "@playwright/test": "^1.48.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5276526bd1..fbb4e116ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,15 @@ importers: '@faker-js/faker': specifier: ^9.2.0 version: 9.2.0 + '@otplib/core': + specifier: ^12.0.0 + version: 12.0.1 + '@otplib/plugin-crypto': + specifier: ^12.0.0 + version: 12.0.1 + '@otplib/plugin-thirty-two': + specifier: ^12.0.0 + version: 12.0.1 '@playwright/test': specifier: ^1.48.2 version: 1.48.2 @@ -415,79 +424,79 @@ packages: engines: {node: '>=6.9.0'} '@bufbuild/buf-darwin-arm64@1.46.0': - resolution: {integrity: sha512-lSmTKyRhg+71acXp9QeX/wm+vjkf0J3n38wph7KOwMfCEeK4A2AkqsGOkoXSiaIvidA2pRU9RJRQYfryzCA9Pg==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.46.0.tgz} + resolution: {integrity: sha512-lSmTKyRhg+71acXp9QeX/wm+vjkf0J3n38wph7KOwMfCEeK4A2AkqsGOkoXSiaIvidA2pRU9RJRQYfryzCA9Pg==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@bufbuild/buf-darwin-arm64@1.47.2': - resolution: {integrity: sha512-74WerFn06y+azgVfsnzhfbI5wla/OLPDnIvaNJBWHaqya/3bfascJkDylW2GVNHmwG1K/cscpmcc/RJPaO7ntQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.47.2.tgz} + resolution: {integrity: sha512-74WerFn06y+azgVfsnzhfbI5wla/OLPDnIvaNJBWHaqya/3bfascJkDylW2GVNHmwG1K/cscpmcc/RJPaO7ntQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@bufbuild/buf-darwin-x64@1.46.0': - resolution: {integrity: sha512-Oa9XTLJshsEjzowyt2mH9XrXW38DRFdz7ml+IYKXVQPotNLr04ix7QES7A1eOBJtxLwuTiri4ScXuBLQGNX8+A==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.46.0.tgz} + resolution: {integrity: sha512-Oa9XTLJshsEjzowyt2mH9XrXW38DRFdz7ml+IYKXVQPotNLr04ix7QES7A1eOBJtxLwuTiri4ScXuBLQGNX8+A==} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@bufbuild/buf-darwin-x64@1.47.2': - resolution: {integrity: sha512-adAiOacOQe8Ym/YXPCEiq9mrPeKRmDtF2TgqPWTcDy6mF7TqR7hMJINkEEuMd1EeACmXnzMOnXlm9ICtvdYgPg==, tarball: https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.47.2.tgz} + resolution: {integrity: sha512-adAiOacOQe8Ym/YXPCEiq9mrPeKRmDtF2TgqPWTcDy6mF7TqR7hMJINkEEuMd1EeACmXnzMOnXlm9ICtvdYgPg==} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@bufbuild/buf-linux-aarch64@1.46.0': - resolution: {integrity: sha512-CbxbLH5sQCRjEKVEcWJySvCKyAPAUhX0vCTifT/eQyZ70FUsqCJKJ6+dKl6Ajk0CgUHqf8jkU/wX/+aQFYXyaA==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.46.0.tgz} + resolution: {integrity: sha512-CbxbLH5sQCRjEKVEcWJySvCKyAPAUhX0vCTifT/eQyZ70FUsqCJKJ6+dKl6Ajk0CgUHqf8jkU/wX/+aQFYXyaA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@bufbuild/buf-linux-aarch64@1.47.2': - resolution: {integrity: sha512-52vY+Owffr5diw2PyfQJqH+Fld6zW6NhNZak4zojvc2MjZKubWM0TfNyM9jXz2YrwyB+cyxkabE60nBI80m37w==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.47.2.tgz} + resolution: {integrity: sha512-52vY+Owffr5diw2PyfQJqH+Fld6zW6NhNZak4zojvc2MjZKubWM0TfNyM9jXz2YrwyB+cyxkabE60nBI80m37w==} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@bufbuild/buf-linux-armv7@1.47.2': - resolution: {integrity: sha512-g9KtpObDeHZ/VG/0b5ZCieOao7L/WYZ0fPqFSs4N07D3APgEDhJG6vLyUcDgJMDgyLcgkNjNz0+XdYQb/tXyQw==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-armv7/-/buf-linux-armv7-1.47.2.tgz} + resolution: {integrity: sha512-g9KtpObDeHZ/VG/0b5ZCieOao7L/WYZ0fPqFSs4N07D3APgEDhJG6vLyUcDgJMDgyLcgkNjNz0+XdYQb/tXyQw==} engines: {node: '>=12'} cpu: [arm] os: [linux] '@bufbuild/buf-linux-x64@1.46.0': - resolution: {integrity: sha512-bMqp+Q+16KPbuwX34/OLDeiimnwt5sfvHqyeMeRz4LLwLshbmM3m+8dGCSHZRo3Lr+4gW1PfunrfaEmcGqPHLQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.46.0.tgz} + resolution: {integrity: sha512-bMqp+Q+16KPbuwX34/OLDeiimnwt5sfvHqyeMeRz4LLwLshbmM3m+8dGCSHZRo3Lr+4gW1PfunrfaEmcGqPHLQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] '@bufbuild/buf-linux-x64@1.47.2': - resolution: {integrity: sha512-MODCK2BzD1Mgoyr+5Sp8xA8qMNdytj8hYheyhA5NnCGTkQf8sfqAjpBSAAmKk6Zar8HOlVXML6tzE/ioDFFGwQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.47.2.tgz} + resolution: {integrity: sha512-MODCK2BzD1Mgoyr+5Sp8xA8qMNdytj8hYheyhA5NnCGTkQf8sfqAjpBSAAmKk6Zar8HOlVXML6tzE/ioDFFGwQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] '@bufbuild/buf-win32-arm64@1.46.0': - resolution: {integrity: sha512-geVYXp1PWJiAAFpwhgP8Cnct0+Rdr89BF/WZoIh5WwFGYITGiu5Hb1Ui9DTrEYwDzahPCyPxgIVwzzW6kPWSag==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.46.0.tgz} + resolution: {integrity: sha512-geVYXp1PWJiAAFpwhgP8Cnct0+Rdr89BF/WZoIh5WwFGYITGiu5Hb1Ui9DTrEYwDzahPCyPxgIVwzzW6kPWSag==} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@bufbuild/buf-win32-arm64@1.47.2': - resolution: {integrity: sha512-563YKYWJl3LrCY3G3+zuhb8HwOs6DzWslwGPFkKV2hwHyWyvd1DR1JjiLvw9zX64IKNctQ0HempSqc3kcboaqQ==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.47.2.tgz} + resolution: {integrity: sha512-563YKYWJl3LrCY3G3+zuhb8HwOs6DzWslwGPFkKV2hwHyWyvd1DR1JjiLvw9zX64IKNctQ0HempSqc3kcboaqQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@bufbuild/buf-win32-x64@1.46.0': - resolution: {integrity: sha512-6nsxkzj5a1L41NOJFKjli8j6GB/NkPHLIr0T/b27Y3GfprVYQawOComYD5HfojvBLuAiE2cD/kEQIWKK1YRcng==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.46.0.tgz} + resolution: {integrity: sha512-6nsxkzj5a1L41NOJFKjli8j6GB/NkPHLIr0T/b27Y3GfprVYQawOComYD5HfojvBLuAiE2cD/kEQIWKK1YRcng==} engines: {node: '>=12'} cpu: [x64] os: [win32] '@bufbuild/buf-win32-x64@1.47.2': - resolution: {integrity: sha512-Sqcdv7La2xBDh3bTdEYb2f4UTMMqCcYe/D0RELhvQ5wDn6I35V3/2YT1OF5fRuf0BZLCo0OdO37S9L47uHSz2g==, tarball: https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.47.2.tgz} + resolution: {integrity: sha512-Sqcdv7La2xBDh3bTdEYb2f4UTMMqCcYe/D0RELhvQ5wDn6I35V3/2YT1OF5fRuf0BZLCo0OdO37S9L47uHSz2g==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -498,7 +507,7 @@ packages: hasBin: true '@bufbuild/buf@1.47.2': - resolution: {integrity: sha512-glY5kCAoO4+a7HvDb+BLOdoHSdCk4mdXdkp53H8JFz7maOnkxCiHHXgRX+taFyEu25N8ybn7NjZFrZSdRwq2sA==, tarball: https://registry.npmjs.org/@bufbuild/buf/-/buf-1.47.2.tgz} + resolution: {integrity: sha512-glY5kCAoO4+a7HvDb+BLOdoHSdCk4mdXdkp53H8JFz7maOnkxCiHHXgRX+taFyEu25N8ybn7NjZFrZSdRwq2sA==} engines: {node: '>=12'} hasBin: true @@ -506,7 +515,7 @@ packages: resolution: {integrity: sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ==} '@bufbuild/protobuf@2.2.2': - resolution: {integrity: sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==, tarball: https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.2.tgz} + resolution: {integrity: sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==} '@bufbuild/protocompile@0.0.1': resolution: {integrity: sha512-cOTMtjcWLcbjF17dPYgeMtVC5jZyS0bSjz3jy8kDPjOgjgSYMD2u2It7w8aCc2z23hTPIKl/2SNdMnz0Jzu3Xg==} @@ -569,24 +578,24 @@ packages: resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==, tarball: https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz} + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} '@connectrpc/connect-node@2.0.0': - resolution: {integrity: sha512-DoI5T+SUvlS/8QBsxt2iDoUg15dSxqhckegrgZpWOtADtmGohBIVbx1UjtWmjLBrP4RdD0FeBw+XyRUSbpKnJQ==, tarball: https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-2.0.0.tgz} + resolution: {integrity: sha512-DoI5T+SUvlS/8QBsxt2iDoUg15dSxqhckegrgZpWOtADtmGohBIVbx1UjtWmjLBrP4RdD0FeBw+XyRUSbpKnJQ==} engines: {node: '>=18.14.1'} peerDependencies: '@bufbuild/protobuf': ^2.2.0 '@connectrpc/connect': 2.0.0 '@connectrpc/connect-web@2.0.0': - resolution: {integrity: sha512-oeCxqHXLXlWJdmcvp9L3scgAuK+FjNSn+twyhUxc8yvDbTumnt5Io+LnBzSYxAdUdYqTw5yHfTSCJ4hj0QID0g==, tarball: https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-2.0.0.tgz} + resolution: {integrity: sha512-oeCxqHXLXlWJdmcvp9L3scgAuK+FjNSn+twyhUxc8yvDbTumnt5Io+LnBzSYxAdUdYqTw5yHfTSCJ4hj0QID0g==} peerDependencies: '@bufbuild/protobuf': ^2.2.0 '@connectrpc/connect': 2.0.0 '@connectrpc/connect@2.0.0': - resolution: {integrity: sha512-Usm8jgaaULANJU8vVnhWssSA6nrZ4DJEAbkNtXSoZay2YD5fDyMukCxu8NEhCvFzfHvrhxhcjttvgpyhOM7xAQ==, tarball: https://registry.npmjs.org/@connectrpc/connect/-/connect-2.0.0.tgz} + resolution: {integrity: sha512-Usm8jgaaULANJU8vVnhWssSA6nrZ4DJEAbkNtXSoZay2YD5fDyMukCxu8NEhCvFzfHvrhxhcjttvgpyhOM7xAQ==} peerDependencies: '@bufbuild/protobuf': ^2.2.0 @@ -598,283 +607,283 @@ packages: resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz} + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] '@esbuild/aix-ppc64@0.24.0': - resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz} + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz} + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] '@esbuild/android-arm64@0.24.0': - resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz} + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} engines: {node: '>=18'} cpu: [arm64] os: [android] '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz} + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] '@esbuild/android-arm@0.24.0': - resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz} + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} engines: {node: '>=18'} cpu: [arm] os: [android] '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz} + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] '@esbuild/android-x64@0.24.0': - resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz} + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} engines: {node: '>=18'} cpu: [x64] os: [android] '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz} + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@esbuild/darwin-arm64@0.24.0': - resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz} + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz} + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@esbuild/darwin-x64@0.24.0': - resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz} + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz} + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-arm64@0.24.0': - resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz} + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] '@esbuild/freebsd-x64@0.24.0': - resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz} + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz} + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@esbuild/linux-arm64@0.24.0': - resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz} + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz} + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] '@esbuild/linux-arm@0.24.0': - resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz} + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} engines: {node: '>=18'} cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz} + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] '@esbuild/linux-ia32@0.24.0': - resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz} + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz} + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] '@esbuild/linux-loong64@0.24.0': - resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz} + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} engines: {node: '>=18'} cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz} + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] '@esbuild/linux-mips64el@0.24.0': - resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz} + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz} + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] '@esbuild/linux-ppc64@0.24.0': - resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz} + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz} + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] '@esbuild/linux-riscv64@0.24.0': - resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz} + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz} + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] '@esbuild/linux-s390x@0.24.0': - resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz} + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} engines: {node: '>=18'} cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz} + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] '@esbuild/linux-x64@0.24.0': - resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz} + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} engines: {node: '>=18'} cpu: [x64] os: [linux] '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] '@esbuild/netbsd-x64@0.24.0': - resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz} + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] '@esbuild/openbsd-arm64@0.24.0': - resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz} + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] '@esbuild/openbsd-x64@0.24.0': - resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz} + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz} + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] '@esbuild/sunos-x64@0.24.0': - resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz} + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz} + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@esbuild/win32-arm64@0.24.0': - resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz} + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz} + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] '@esbuild/win32-ia32@0.24.0': - resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz} + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz} + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] '@esbuild/win32-x64@0.24.0': - resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz} + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -901,10 +910,6 @@ packages: resolution: {integrity: sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==} engines: {node: '>=18.0.0', npm: '>=9.0.0'} - '@fastify/busboy@2.1.1': - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} - '@floating-ui/core@1.6.8': resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} @@ -1023,55 +1028,55 @@ packages: resolution: {integrity: sha512-+7xh142AdhZGjY9/L0iFo7mqRBMJHe+q+uOL+hto1Lfo9DeWCGcR6no4StlFbVSVcA6fQLKEX6y6qhMsSKbgNQ==} '@next/swc-darwin-arm64@14.2.14': - resolution: {integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==, tarball: https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.14.tgz} + resolution: {integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] '@next/swc-darwin-x64@14.2.14': - resolution: {integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==, tarball: https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.14.tgz} + resolution: {integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] '@next/swc-linux-arm64-gnu@14.2.14': - resolution: {integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==, tarball: https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.14.tgz} + resolution: {integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] '@next/swc-linux-arm64-musl@14.2.14': - resolution: {integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==, tarball: https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.14.tgz} + resolution: {integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] '@next/swc-linux-x64-gnu@14.2.14': - resolution: {integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==, tarball: https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.14.tgz} + resolution: {integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] '@next/swc-linux-x64-musl@14.2.14': - resolution: {integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==, tarball: https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.14.tgz} + resolution: {integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] '@next/swc-win32-arm64-msvc@14.2.14': - resolution: {integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==, tarball: https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.14.tgz} + resolution: {integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] '@next/swc-win32-ia32-msvc@14.2.14': - resolution: {integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==, tarball: https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.14.tgz} + resolution: {integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] '@next/swc-win32-x64-msvc@14.2.14': - resolution: {integrity: sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg==, tarball: https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.14.tgz} + resolution: {integrity: sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1092,90 +1097,99 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@otplib/core@12.0.1': + resolution: {integrity: sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==} + + '@otplib/plugin-crypto@12.0.1': + resolution: {integrity: sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==} + + '@otplib/plugin-thirty-two@12.0.1': + resolution: {integrity: sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==} + '@parcel/watcher-android-arm64@2.5.0': - resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==, tarball: https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz} + resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [android] '@parcel/watcher-darwin-arm64@2.5.0': - resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==, tarball: https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz} + resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [darwin] '@parcel/watcher-darwin-x64@2.5.0': - resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==, tarball: https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz} + resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [darwin] '@parcel/watcher-freebsd-x64@2.5.0': - resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==, tarball: https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz} + resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [freebsd] '@parcel/watcher-linux-arm-glibc@2.5.0': - resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz} + resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] '@parcel/watcher-linux-arm-musl@2.5.0': - resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz} + resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] '@parcel/watcher-linux-arm64-glibc@2.5.0': - resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz} + resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] '@parcel/watcher-linux-arm64-musl@2.5.0': - resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz} + resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] '@parcel/watcher-linux-x64-glibc@2.5.0': - resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz} + resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] '@parcel/watcher-linux-x64-musl@2.5.0': - resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==, tarball: https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz} + resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] '@parcel/watcher-win32-arm64@2.5.0': - resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==, tarball: https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz} + resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [win32] '@parcel/watcher-win32-ia32@2.5.0': - resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==, tarball: https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz} + resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==} engines: {node: '>= 10.0.0'} cpu: [ia32] os: [win32] '@parcel/watcher-win32-x64@2.5.0': - resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==, tarball: https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz} + resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [win32] '@parcel/watcher@2.5.0': - resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==, tarball: https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz} + resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==} engines: {node: '>= 10.0.0'} '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, tarball: https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz} + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} '@playwright/test@1.48.2': @@ -1245,92 +1259,92 @@ packages: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 '@rollup/rollup-android-arm-eabi@4.25.0': - resolution: {integrity: sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.25.0.tgz} + resolution: {integrity: sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==} cpu: [arm] os: [android] '@rollup/rollup-android-arm64@4.25.0': - resolution: {integrity: sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.25.0.tgz} + resolution: {integrity: sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==} cpu: [arm64] os: [android] '@rollup/rollup-darwin-arm64@4.25.0': - resolution: {integrity: sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.25.0.tgz} + resolution: {integrity: sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==} cpu: [arm64] os: [darwin] '@rollup/rollup-darwin-x64@4.25.0': - resolution: {integrity: sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.25.0.tgz} + resolution: {integrity: sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==} cpu: [x64] os: [darwin] '@rollup/rollup-freebsd-arm64@4.25.0': - resolution: {integrity: sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.25.0.tgz} + resolution: {integrity: sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==} cpu: [arm64] os: [freebsd] '@rollup/rollup-freebsd-x64@4.25.0': - resolution: {integrity: sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.25.0.tgz} + resolution: {integrity: sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==} cpu: [x64] os: [freebsd] '@rollup/rollup-linux-arm-gnueabihf@4.25.0': - resolution: {integrity: sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.25.0.tgz} + resolution: {integrity: sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==} cpu: [arm] os: [linux] '@rollup/rollup-linux-arm-musleabihf@4.25.0': - resolution: {integrity: sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.25.0.tgz} + resolution: {integrity: sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==} cpu: [arm] os: [linux] '@rollup/rollup-linux-arm64-gnu@4.25.0': - resolution: {integrity: sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.25.0.tgz} + resolution: {integrity: sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==} cpu: [arm64] os: [linux] '@rollup/rollup-linux-arm64-musl@4.25.0': - resolution: {integrity: sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.25.0.tgz} + resolution: {integrity: sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==} cpu: [arm64] os: [linux] '@rollup/rollup-linux-powerpc64le-gnu@4.25.0': - resolution: {integrity: sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.25.0.tgz} + resolution: {integrity: sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==} cpu: [ppc64] os: [linux] '@rollup/rollup-linux-riscv64-gnu@4.25.0': - resolution: {integrity: sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.25.0.tgz} + resolution: {integrity: sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==} cpu: [riscv64] os: [linux] '@rollup/rollup-linux-s390x-gnu@4.25.0': - resolution: {integrity: sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.25.0.tgz} + resolution: {integrity: sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==} cpu: [s390x] os: [linux] '@rollup/rollup-linux-x64-gnu@4.25.0': - resolution: {integrity: sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.25.0.tgz} + resolution: {integrity: sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==} cpu: [x64] os: [linux] '@rollup/rollup-linux-x64-musl@4.25.0': - resolution: {integrity: sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.25.0.tgz} + resolution: {integrity: sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==} cpu: [x64] os: [linux] '@rollup/rollup-win32-arm64-msvc@4.25.0': - resolution: {integrity: sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.25.0.tgz} + resolution: {integrity: sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==} cpu: [arm64] os: [win32] '@rollup/rollup-win32-ia32-msvc@4.25.0': - resolution: {integrity: sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.25.0.tgz} + resolution: {integrity: sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==} cpu: [ia32] os: [win32] '@rollup/rollup-win32-x64-msvc@4.25.0': - resolution: {integrity: sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.25.0.tgz} + resolution: {integrity: sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==} cpu: [x64] os: [win32] @@ -1376,7 +1390,7 @@ packages: resolution: {integrity: sha512-1giLc4dzgEKLMx5pgKjL6HlG5fjZMgCjzlKAlpr7yoUtetVPELgER1NtephAI910nMwfPTHNyWKSFmJdHkz2Cw==} '@testing-library/dom@10.4.0': - resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==, tarball: https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz} + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} '@testing-library/jest-dom@6.6.3': @@ -1399,7 +1413,7 @@ packages: optional: true '@types/aria-query@5.0.4': - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==, tarball: https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz} + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1450,7 +1464,7 @@ packages: resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==, tarball: https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz} + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} '@typescript-eslint/parser@7.18.0': resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} @@ -1591,7 +1605,7 @@ packages: engines: {node: '>=8'} ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==, tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz} + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} ansi-styles@6.2.1: @@ -2122,7 +2136,7 @@ packages: engines: {node: '>=6.0.0'} dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==, tarball: https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz} + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} @@ -2528,12 +2542,12 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz} + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz} + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] @@ -3143,7 +3157,7 @@ packages: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==, tarball: https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz} + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true magic-string@0.30.12: @@ -3709,7 +3723,7 @@ packages: engines: {node: '>=6'} pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==, tarball: https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz} + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} process@0.11.10: @@ -3774,7 +3788,7 @@ packages: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==, tarball: https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz} + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} @@ -4172,6 +4186,10 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thirty-two@1.0.2: + resolution: {integrity: sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==} + engines: {node: '>=0.2.6'} + throttleit@1.0.1: resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==} @@ -4308,32 +4326,32 @@ packages: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} turbo-darwin-64@2.2.3: - resolution: {integrity: sha512-Rcm10CuMKQGcdIBS3R/9PMeuYnv6beYIHqfZFeKWVYEWH69sauj4INs83zKMTUiZJ3/hWGZ4jet9AOwhsssLyg==, tarball: https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.2.3.tgz} + resolution: {integrity: sha512-Rcm10CuMKQGcdIBS3R/9PMeuYnv6beYIHqfZFeKWVYEWH69sauj4INs83zKMTUiZJ3/hWGZ4jet9AOwhsssLyg==} cpu: [x64] os: [darwin] turbo-darwin-arm64@2.2.3: - resolution: {integrity: sha512-+EIMHkuLFqUdJYsA3roj66t9+9IciCajgj+DVek+QezEdOJKcRxlvDOS2BUaeN8kEzVSsNiAGnoysFWYw4K0HA==, tarball: https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.2.3.tgz} + resolution: {integrity: sha512-+EIMHkuLFqUdJYsA3roj66t9+9IciCajgj+DVek+QezEdOJKcRxlvDOS2BUaeN8kEzVSsNiAGnoysFWYw4K0HA==} cpu: [arm64] os: [darwin] turbo-linux-64@2.2.3: - resolution: {integrity: sha512-UBhJCYnqtaeOBQLmLo8BAisWbc9v9daL9G8upLR+XGj6vuN/Nz6qUAhverN4Pyej1g4Nt1BhROnj6GLOPYyqxQ==, tarball: https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.2.3.tgz} + resolution: {integrity: sha512-UBhJCYnqtaeOBQLmLo8BAisWbc9v9daL9G8upLR+XGj6vuN/Nz6qUAhverN4Pyej1g4Nt1BhROnj6GLOPYyqxQ==} cpu: [x64] os: [linux] turbo-linux-arm64@2.2.3: - resolution: {integrity: sha512-hJYT9dN06XCQ3jBka/EWvvAETnHRs3xuO/rb5bESmDfG+d9yQjeTMlhRXKrr4eyIMt6cLDt1LBfyi+6CQ+VAwQ==, tarball: https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.2.3.tgz} + resolution: {integrity: sha512-hJYT9dN06XCQ3jBka/EWvvAETnHRs3xuO/rb5bESmDfG+d9yQjeTMlhRXKrr4eyIMt6cLDt1LBfyi+6CQ+VAwQ==} cpu: [arm64] os: [linux] turbo-windows-64@2.2.3: - resolution: {integrity: sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==, tarball: https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.2.3.tgz} + resolution: {integrity: sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==} cpu: [x64] os: [win32] turbo-windows-arm64@2.2.3: - resolution: {integrity: sha512-fnNrYBCqn6zgKPKLHu4sOkihBI/+0oYFr075duRxqUZ+1aLWTAGfHZLgjVeLh3zR37CVzuerGIPWAEkNhkWEIw==, tarball: https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.2.3.tgz} + resolution: {integrity: sha512-fnNrYBCqn6zgKPKLHu4sOkihBI/+0oYFr075duRxqUZ+1aLWTAGfHZLgjVeLh3zR37CVzuerGIPWAEkNhkWEIw==} cpu: [arm64] os: [win32] @@ -5201,8 +5219,6 @@ snapshots: '@faker-js/faker@9.2.0': {} - '@fastify/busboy@2.1.1': {} - '@floating-ui/core@1.6.8': dependencies: '@floating-ui/utils': 0.2.8 @@ -5402,6 +5418,17 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@otplib/core@12.0.1': {} + + '@otplib/plugin-crypto@12.0.1': + dependencies: + '@otplib/core': 12.0.1 + + '@otplib/plugin-thirty-two@12.0.1': + dependencies: + '@otplib/core': 12.0.1 + thirty-two: 1.0.2 + '@parcel/watcher-android-arm64@2.5.0': optional: true @@ -6709,7 +6736,7 @@ snapshots: debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 @@ -6722,7 +6749,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: @@ -6743,7 +6770,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -8700,6 +8727,8 @@ snapshots: dependencies: any-promise: 1.3.0 + thirty-two@1.0.2: {} + throttleit@1.0.1: {} through@2.3.8: {} From 5d6b6b538838ef435a32fb7e4c57934145f66594 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:38:13 +0100 Subject: [PATCH 4/5] chore: cleanup and some fixes --- acceptance/sink/go.mod | 3 +++ acceptance/tests/register.spec.ts | 10 +++++-- acceptance/tests/register.ts | 4 +++ acceptance/tests/user.ts | 26 ++++++++++++------- acceptance/tests/username-passkey.spec.ts | 1 + .../tests/username-password-changed.spec.ts | 1 + .../tests/username-password-otp_email.spec.ts | 1 + .../tests/username-password-otp_sms.spec.ts | 1 + .../tests/username-password-set.spec.ts | 1 + .../tests/username-password-totp.spec.ts | 1 + acceptance/tests/username-password.spec.ts | 1 + 11 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 acceptance/sink/go.mod diff --git a/acceptance/sink/go.mod b/acceptance/sink/go.mod new file mode 100644 index 0000000000..a33d6ae8bd --- /dev/null +++ b/acceptance/sink/go.mod @@ -0,0 +1,3 @@ +module github.com/zitadel/typescript/acceptance/sink + +go 1.22.6 diff --git a/acceptance/tests/register.spec.ts b/acceptance/tests/register.spec.ts index 98e5669e9e..a3ffc7a67e 100644 --- a/acceptance/tests/register.spec.ts +++ b/acceptance/tests/register.spec.ts @@ -15,9 +15,12 @@ test("register with password", async ({ page }) => { const firstname = faker.person.firstName(); const lastname = faker.person.lastName(); - await removeUserByUsername(username); await registerWithPassword(page, firstname, lastname, username, password, password); await loginScreenExpect(page, firstname + " " + lastname); + + // wait for projection of user + await page.waitForTimeout(2000); + await removeUserByUsername(username); }); test("register with passkey", async ({ page }) => { @@ -25,9 +28,12 @@ test("register with passkey", async ({ page }) => { const firstname = faker.person.firstName(); const lastname = faker.person.lastName(); - await removeUserByUsername(username); await registerWithPasskey(page, firstname, lastname, username); await loginScreenExpect(page, firstname + " " + lastname); + + // wait for projection of user + await page.waitForTimeout(2000); + await removeUserByUsername(username); }); test("register with username and password - only password enabled", async ({ page }) => { diff --git a/acceptance/tests/register.ts b/acceptance/tests/register.ts index f943e5bacc..e250d60b12 100644 --- a/acceptance/tests/register.ts +++ b/acceptance/tests/register.ts @@ -21,5 +21,9 @@ export async function registerWithPasskey(page: Page, firstname: string, lastnam await page.goto("/register"); await registerUserScreenPasskey(page, firstname, lastname, email); await page.getByTestId("submit-button").click(); + + // wait for projection of user + await page.waitForTimeout(2000); + return await passkeyRegister(page); } diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index d8a967aed6..3daefdff08 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -23,16 +23,10 @@ class User { const response = await addUser(this.props); this.setUserId(response.userId); - // wait for projection of user - await page.waitForTimeout(2000); } - async remove() { - const resp: any = await getUserByUsername(this.getUsername()); - if (!resp || !resp.result || !resp.result[0]) { - return; - } - await removeUser(resp.result[0].userId); + async cleanup() { + await removeUser(this.getUserId()); } public setUserId(userId: string) { @@ -68,7 +62,13 @@ class User { } } -export class PasswordUser extends User {} +export class PasswordUser extends User { + async ensure(page: Page) { + await super.ensure(page); + // wait for projection of user + await page.waitForTimeout(2000); + } +} export enum OtpType { sms = "sms", @@ -157,6 +157,14 @@ export class PasskeyUser extends User { await page.waitForTimeout(2000); } + async cleanup() { + const resp: any = await getUserByUsername(this.getUsername()); + if (!resp || !resp.result || !resp.result[0]) { + return; + } + await removeUser(resp.result[0].userId); + } + public getAuthenticatorId(): string { return this.authenticatorId; } diff --git a/acceptance/tests/username-passkey.spec.ts b/acceptance/tests/username-passkey.spec.ts index 7fbf290d99..e73de3547f 100644 --- a/acceptance/tests/username-passkey.spec.ts +++ b/acceptance/tests/username-passkey.spec.ts @@ -19,6 +19,7 @@ const test = base.extend<{ user: PasskeyUser }>({ }); await user.ensure(page); await use(user); + await user.cleanup(); }, }); diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index cf3af995f7..c185e51ec9 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -22,6 +22,7 @@ const test = base.extend<{ user: PasswordUser }>({ }); await user.ensure(page); await use(user); + await user.cleanup(); }, }); diff --git a/acceptance/tests/username-password-otp_email.spec.ts b/acceptance/tests/username-password-otp_email.spec.ts index b684fa80c6..daa2e0a429 100644 --- a/acceptance/tests/username-password-otp_email.spec.ts +++ b/acceptance/tests/username-password-otp_email.spec.ts @@ -24,6 +24,7 @@ const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({ await user.ensure(page); await use(user); + await user.cleanup(); }, }); diff --git a/acceptance/tests/username-password-otp_sms.spec.ts b/acceptance/tests/username-password-otp_sms.spec.ts index 86220fd2d6..8fb91a66c7 100644 --- a/acceptance/tests/username-password-otp_sms.spec.ts +++ b/acceptance/tests/username-password-otp_sms.spec.ts @@ -24,6 +24,7 @@ const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({ await user.ensure(page); await use(user); + await user.cleanup(); }, }); diff --git a/acceptance/tests/username-password-set.spec.ts b/acceptance/tests/username-password-set.spec.ts index c2a60bd410..30d442df50 100644 --- a/acceptance/tests/username-password-set.spec.ts +++ b/acceptance/tests/username-password-set.spec.ts @@ -23,6 +23,7 @@ const test = base.extend<{ user: PasswordUser }>({ }); await user.ensure(page); await use(user); + await user.cleanup(); }, }); diff --git a/acceptance/tests/username-password-totp.spec.ts b/acceptance/tests/username-password-totp.spec.ts index 33b1d9c09f..4b6e678931 100644 --- a/acceptance/tests/username-password-totp.spec.ts +++ b/acceptance/tests/username-password-totp.spec.ts @@ -23,6 +23,7 @@ const test = base.extend<{ user: PasswordUserWithTOTP; sink: any }>({ await user.ensure(page); await use(user); + await user.cleanup(); }, }); diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index 3f6bfd48f7..fcb6aad037 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -24,6 +24,7 @@ const test = base.extend<{ user: PasswordUser }>({ }); await user.ensure(page); await use(user); + await user.cleanup(); }, }); From 4740bf3a1a298bb03d84e034ee75cfb1aeb39ad6 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 4 Dec 2024 10:18:18 +0100 Subject: [PATCH 5/5] format --- acceptance/tests/register.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/tests/register.ts b/acceptance/tests/register.ts index e250d60b12..693bdfbc0d 100644 --- a/acceptance/tests/register.ts +++ b/acceptance/tests/register.ts @@ -24,6 +24,6 @@ export async function registerWithPasskey(page: Page, firstname: string, lastnam // wait for projection of user await page.waitForTimeout(2000); - + return await passkeyRegister(page); }