diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index f04ea0f6c..bf96e752c 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -7,7 +7,8 @@ "packages/http", "packages/viem", "packages/webauthn-stamper", - "packages/api-key-stamper" + "packages/api-key-stamper", + "packages/iframe-stamper" ], "sandboxes": [], "node": "18" diff --git a/examples/email-recovery/.env.local.example b/examples/email-recovery/.env.local.example new file mode 100644 index 000000000..010c8ff31 --- /dev/null +++ b/examples/email-recovery/.env.local.example @@ -0,0 +1,7 @@ +API_PUBLIC_KEY="" +API_PRIVATE_KEY="" +NEXT_PUBLIC_ORGANIZATION_ID="" +NEXT_PUBLIC_BASE_URL="https://api.turnkey.com" +# Can be changed to a localhost iframe if you're modifying the recovery flow +# For production, the URL should not be changed and point to the primary Turnkey domain. +NEXT_PUBLIC_RECOVERY_IFRAME_URL="https://recovery.turnkey.com" \ No newline at end of file diff --git a/examples/email-recovery/.eslintrc.json b/examples/email-recovery/.eslintrc.json new file mode 100644 index 000000000..bffb357a7 --- /dev/null +++ b/examples/email-recovery/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/examples/email-recovery/.gitignore b/examples/email-recovery/.gitignore new file mode 100644 index 000000000..8f322f0d8 --- /dev/null +++ b/examples/email-recovery/.gitignore @@ -0,0 +1,35 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/examples/email-recovery/README.md b/examples/email-recovery/README.md new file mode 100644 index 000000000..b3a8c0454 --- /dev/null +++ b/examples/email-recovery/README.md @@ -0,0 +1,55 @@ +# Example: `email-recovery` + +This example shows a complete email recovery flow. It contains a NextJS app with: + +- a frontend application +- a backend application + +The overall flow for email recovery is outlined below: +![Email recovery flow diagram](./email_recovery_steps.png) + +This example contains an example recovery page as well as a stub API endpoint for "your business" (where the email is resolved into an organization ID). The creation of the hidden iframe is abstracted by our `@turnkey/iframe-stamper` package. + +## Getting started + +### 1/ Cloning the example + +Make sure you have `node` installed locally; we recommend using Node v16+. + +```bash +$ git clone https://github.com/tkhq/sdk +$ cd sdk/ +$ corepack enable # Install `pnpm` +$ pnpm install -r # Install dependencies +$ pnpm run build-all # Compile source code +$ cd examples/email-recovery/ +``` + +### 2/ Setting up Turnkey + +The first step is to set up your Turnkey organization and account. By following the [Quickstart](https://docs.turnkey.com/getting-started/quickstart) guide, you should have: + +- A public/private API key pair for Turnkey +- An organization ID + +Once you've gathered these values, add them to a new `.env.local` file. Notice that your API private key should be securely managed and **_never_** be committed to git. + +```bash +$ cp .env.local.example .env.local +``` + +Now open `.env.local` and add the missing environment variables: + +- `API_PUBLIC_KEY` +- `API_PRIVATE_KEY` +- `NEXT_PUBLIC_ORGANIZATION_ID` +- `NEXT_PUBLIC_BASE_URL` (the `NEXT_PUBLIC` prefix makes the env variable accessible to the frontend app) +- `NEXT_PUBLIC_RECOVERY_IFRAME_URL` + +### 3/ Running the app + +```bash +$ pnpm run dev +``` + +This command will run a NextJS app on port 3000. If you navigate to http://localhost:3000 in your browser, you can follow the prompts to start an email recovery. diff --git a/examples/email-recovery/email_recovery_steps.png b/examples/email-recovery/email_recovery_steps.png new file mode 100644 index 000000000..8eea4a4d0 Binary files /dev/null and b/examples/email-recovery/email_recovery_steps.png differ diff --git a/examples/email-recovery/next.config.js b/examples/email-recovery/next.config.js new file mode 100644 index 000000000..658404ac6 --- /dev/null +++ b/examples/email-recovery/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +module.exports = nextConfig; diff --git a/examples/email-recovery/package.json b/examples/email-recovery/package.json new file mode 100644 index 000000000..23ee87c78 --- /dev/null +++ b/examples/email-recovery/package.json @@ -0,0 +1,32 @@ +{ + "name": "@turnkey/example-email-recovery", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@turnkey/http": "workspace:*", + "@turnkey/api-key-stamper": "workspace:*", + "@turnkey/iframe-stamper": "workspace:*", + "@types/node": "20.3.1", + "@types/react": "18.2.14", + "@types/react-dom": "18.2.6", + "axios": "^1.4.0", + "encoding": "^0.1.13", + "eslint": "8.43.0", + "eslint-config-next": "13.4.7", + "esm": "^3.2.25", + "install": "^0.13.0", + "next": "13.4.7", + "npm": "^9.7.2", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-hook-form": "^7.45.1", + "typescript": "5.1.3" + } +} diff --git a/examples/email-recovery/public/favicon.svg b/examples/email-recovery/public/favicon.svg new file mode 100644 index 000000000..c2d1cb966 --- /dev/null +++ b/examples/email-recovery/public/favicon.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + diff --git a/examples/email-recovery/public/fonts/inter/Inter-Bold.woff2 b/examples/email-recovery/public/fonts/inter/Inter-Bold.woff2 new file mode 100644 index 000000000..2846f29cc Binary files /dev/null and b/examples/email-recovery/public/fonts/inter/Inter-Bold.woff2 differ diff --git a/examples/email-recovery/public/fonts/inter/Inter-Regular.woff2 b/examples/email-recovery/public/fonts/inter/Inter-Regular.woff2 new file mode 100644 index 000000000..6c2b6893d Binary files /dev/null and b/examples/email-recovery/public/fonts/inter/Inter-Regular.woff2 differ diff --git a/examples/email-recovery/public/fonts/inter/Inter-SemiBold.woff2 b/examples/email-recovery/public/fonts/inter/Inter-SemiBold.woff2 new file mode 100644 index 000000000..611e90c95 Binary files /dev/null and b/examples/email-recovery/public/fonts/inter/Inter-SemiBold.woff2 differ diff --git a/examples/email-recovery/public/logo.svg b/examples/email-recovery/public/logo.svg new file mode 100644 index 000000000..f983fd29b --- /dev/null +++ b/examples/email-recovery/public/logo.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/examples/email-recovery/src/components/Recovery.tsx b/examples/email-recovery/src/components/Recovery.tsx new file mode 100644 index 000000000..77169986f --- /dev/null +++ b/examples/email-recovery/src/components/Recovery.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { IframeStamper } from "@turnkey/iframe-stamper"; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; + +interface RecoveryProps { + iframeUrl: string; + turnkeyBaseUrl: string; + setIframeStamper: Dispatch>; +} + +const TurnkeyIframeContainerId = "turnkey-iframe-container-id"; +const TurnkeyIframeElementId = "turnkey-iframe-element-id"; + +export function Recovery(props: RecoveryProps) { + const [iframeStamper, setIframeStamper] = useState( + null + ); + + useEffect(() => { + if (!iframeStamper) { + const iframeStamper = new IframeStamper({ + iframeUrl: props.iframeUrl, + iframeContainerId: TurnkeyIframeContainerId, + iframeElementId: TurnkeyIframeElementId, + }); + iframeStamper.init().then(() => { + setIframeStamper(iframeStamper); + props.setIframeStamper(iframeStamper); + }); + } + + return () => { + if (iframeStamper) { + iframeStamper.clear(); + setIframeStamper(null); + } + }; + }, [props, iframeStamper, setIframeStamper]); + + return
; +} diff --git a/examples/email-recovery/src/pages/_document.tsx b/examples/email-recovery/src/pages/_document.tsx new file mode 100644 index 000000000..52d1c89a1 --- /dev/null +++ b/examples/email-recovery/src/pages/_document.tsx @@ -0,0 +1,19 @@ +import Document, { Html, Head, Main, NextScript } from "next/document"; + +class Example extends Document { + render() { + return ( + + + + + +
+ + + + ); + } +} + +export default Example; diff --git a/examples/email-recovery/src/pages/api/initRecovery.ts b/examples/email-recovery/src/pages/api/initRecovery.ts new file mode 100644 index 000000000..210de27e1 --- /dev/null +++ b/examples/email-recovery/src/pages/api/initRecovery.ts @@ -0,0 +1,73 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { TurnkeyClient, createActivityPoller } from "@turnkey/http"; +import { ApiKeyStamper } from "@turnkey/api-key-stamper"; + +type InitRecoveryRequest = { + email: string; + targetPublicKey: string; +}; + +/** + * Returns the userId starting recovery (available in `INIT_USER_EMAIL_RECOVERY` activity result) + * as well as the organization ID. These two pieces of information are useful because they are used + * inside of the `RECOVER_USER` activity params. + */ +type InitRecoveryResponse = { + userId: string; + organizationId: string; +}; + +type ErrorMessage = { + message: string; +}; + +export default async function initRecovery( + req: NextApiRequest, + res: NextApiResponse +) { + try { + const request = req.body as InitRecoveryRequest; + const turnkeyClient = new TurnkeyClient( + { baseUrl: process.env.NEXT_PUBLIC_BASE_URL! }, + new ApiKeyStamper({ + apiPublicKey: process.env.API_PUBLIC_KEY!, + apiPrivateKey: process.env.API_PRIVATE_KEY!, + }) + ); + + const activityPoller = createActivityPoller({ + client: turnkeyClient, + requestFn: turnkeyClient.initUserEmailRecovery, + }); + + const completedActivity = await activityPoller({ + type: "ACTIVITY_TYPE_INIT_USER_EMAIL_RECOVERY", + timestampMs: String(Date.now()), + // This is simple in the case of a single organization. + // If you use sub-organizations for each user, this needs to be replaced by the user's specific sub-organization. + organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, + parameters: { + email: request.email, + targetPublicKey: request.targetPublicKey, + }, + }); + + const userId = completedActivity.result.initUserEmailRecoveryResult?.userId; + if (!userId) { + throw new Error("Expected a non-null user ID!"); + } + + res.status(200).json({ + userId: userId, + // This is simple in the case of a single organization + // If you use sub-organizations for each user, this needs to be replaced by the user's specific sub-organization. + organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, + }); + } catch (e) { + console.error(e); + + res.status(500).json({ + message: "Something went wrong.", + }); + } +} diff --git a/examples/email-recovery/src/pages/index.module.css b/examples/email-recovery/src/pages/index.module.css new file mode 100644 index 000000000..aed75c8b5 --- /dev/null +++ b/examples/email-recovery/src/pages/index.module.css @@ -0,0 +1,80 @@ +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("../../public/fonts/inter/Inter-Regular.woff2?v=3.19") + format("woff2"); +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url("../../public/fonts/inter/Inter-SemiBold.woff2?v=3.19") + format("woff2"); +} + +.main { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + padding: 6rem; + gap: 60px; +} + +.input { + display: block; + width: 240px; + margin: 0; + padding: 10px 16px; + border-radius: 8px; + border-width: 1px; + border-style: solid; + border-color: rgba(216, 219, 227, 1); + font-family: "Inter"; +} + +.label { + font-family: "Inter"; + display: block; + text-align: center; +} + +.prompt { + font-family: "Inter"; +} + +.base { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.button { + display: inline-flex; + align-items: center; + justify-content: center; + margin: 0; + padding: 10px 16px; + border-radius: 8px; + border-width: 1px; + border-style: solid; + cursor: pointer; + color: white; + background-color: rgba(43, 47, 51, 1); + border-color: rgba(63, 70, 75, 1); + font-family: "Inter"; +} + +.form { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 20px; + min-width: 400px; +} diff --git a/examples/email-recovery/src/pages/index.tsx b/examples/email-recovery/src/pages/index.tsx new file mode 100644 index 000000000..09bb1e3f3 --- /dev/null +++ b/examples/email-recovery/src/pages/index.tsx @@ -0,0 +1,246 @@ +import Image from "next/image"; +import styles from "./index.module.css"; +import { getWebAuthnAttestation, TurnkeyClient } from "@turnkey/http"; +import { IframeStamper } from "@turnkey/iframe-stamper"; +import { useForm } from "react-hook-form"; +import axios from "axios"; +import * as React from "react"; +import { useState } from "react"; +import { Recovery } from "@/components/Recovery"; + +/** + * Type definition for the server response coming back from `/api/initRecovery` + */ +type InitRecoveryResponse = { + userId: string; + organizationId: string; +}; + +/** + * Type definitions for the form data (client-side forms) + */ +type RecoverUserFormData = { + recoveryBundle: string; + authenticatorName: string; +}; +type InitRecoveryFormData = { + email: string; +}; + +// All algorithms can be found here: https://www.iana.org/assignments/cose/cose.xhtml#algorithms +// We only support ES256 and RS256, which are listed here +const es256 = -7; +const rs256 = -257; + +// This constant designates the type of credential we want to create. +// The enum only supports one value, "public-key" +// https://www.w3.org/TR/webauthn-2/#enumdef-publickeycredentialtype +const publicKey = "public-key"; + +const generateRandomBuffer = (): ArrayBuffer => { + const arr = new Uint8Array(32); + crypto.getRandomValues(arr); + return arr.buffer; +}; + +const base64UrlEncode = (challenge: ArrayBuffer): string => { + return Buffer.from(challenge) + .toString("base64") + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=/g, ""); +}; + +export default function RecoveryPage() { + const [initRecoveryResponse, setInitRecoveryResponse] = + useState(null); + const [iframeStamper, setIframeStamper] = useState( + null + ); + const { + register: initRecoveryFormRegister, + handleSubmit: initRecoveryFormSubmit, + } = useForm(); + const { + register: recoverUserFormRegister, + handleSubmit: recoverUserFormSubmit, + } = useForm(); + + const initRecovery = async (data: InitRecoveryFormData) => { + if (iframeStamper === null) { + throw new Error("cannot initialize recovery without an iframe"); + } + + const response = await axios.post("/api/initRecovery", { + email: data.email, + targetPublicKey: iframeStamper.publicKey(), + }); + setInitRecoveryResponse(response.data); + }; + + const recoverUser = async (data: RecoverUserFormData) => { + if (iframeStamper === null) { + throw new Error("iframeStamper is null"); + } + if (initRecoveryResponse === null) { + throw new Error("initRecoveryResponse is null"); + } + + let injected = await iframeStamper.injectRecoveryBundle( + data.recoveryBundle + ); + if (injected !== true) { + throw new Error("unexpected error while injecting recovery bundle"); + } + + const challenge = generateRandomBuffer(); + const authenticatorUserId = generateRandomBuffer(); + + // An example of possible options can be found here: + // https://www.w3.org/TR/webauthn-2/#sctn-sample-registration + const attestation = await getWebAuthnAttestation({ + publicKey: { + authenticatorSelection: { + residentKey: "preferred", + requireResidentKey: false, + userVerification: "preferred", + }, + rp: { + id: "localhost", + name: "Turnkey Federated Passkey Demo", + }, + challenge, + pubKeyCredParams: [ + { type: publicKey, alg: es256 }, + { type: publicKey, alg: rs256 }, + ], + user: { + id: authenticatorUserId, + name: data.authenticatorName, + displayName: data.authenticatorName, + }, + }, + }); + + const client = new TurnkeyClient( + { + baseUrl: process.env.NEXT_PUBLIC_BASE_URL!, + }, + iframeStamper + ); + + const response = await client.recoverUser({ + type: "ACTIVITY_TYPE_RECOVER_USER", + timestampMs: String(Date.now()), + organizationId: initRecoveryResponse.organizationId, + parameters: { + userId: initRecoveryResponse.userId, + authenticator: { + authenticatorName: data.authenticatorName, + challenge: base64UrlEncode(challenge), + attestation: attestation, + }, + }, + }); + + // There is an interesting edge case here: if we poll using the recovery credential, + // it will fail as soon as the activity is successful! + // I think there is a strategy we can implement potentially: + // - assert that the status of the activity is "PENDING" or "COMPLETE". Anything else should be an error. + // - on subsequent polls, assert that the status is "PENDING" or that an error "no user found for authenticator" is returned + // When the error is returned it means the recovery has taken place (the recovery credential has been deleted from org data!) + // Another solution is to poll this using a read-only API key, from the backend (proxying) + console.log(response); + + // Instead of simply alerting, redirect the user to your app's login page. + alert( + "SUCCESS! Authenticator added. Recovery flow complete. Try logging back in!" + ); + }; + + return ( +
+ + Turnkey Logo + + + + + {!iframeStamper &&

Loading...

} + + {iframeStamper && + iframeStamper.publicKey() && + initRecoveryResponse === null && ( +
+ + + + +
+ )} + + {iframeStamper && + iframeStamper.publicKey() && + initRecoveryResponse !== null && ( +
+ + + + +
+ )} +
+ ); +} diff --git a/examples/email-recovery/tsconfig.json b/examples/email-recovery/tsconfig.json new file mode 100644 index 000000000..0c7555fa7 --- /dev/null +++ b/examples/email-recovery/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/package.json b/package.json index ce9887fd8..2ad5ca220 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "pnpm": { "overrides": { "@confio/ics23@0.6.8>protobufjs": ">=7.2.4", - "protobufjs@>=6.10.0 <7.2.4": ">=7.2.4" + "protobufjs@>=6.10.0 <7.2.4": ">=7.2.4", + "@babel/traverse": ">=7.23.2" } } } diff --git a/packages/ethers/jest.config.js b/packages/ethers/jest.config.js index 87d1f6e27..fa3cd8a95 100644 --- a/packages/ethers/jest.config.js +++ b/packages/ethers/jest.config.js @@ -4,6 +4,7 @@ const config = { "\\.[jt]sx?$": "@turnkey/jest-config/transformer.js", }, testPathIgnorePatterns: ["/dist/", "/node_modules/"], + testTimeout: 30 * 1000, // For slow CI machines }; module.exports = config; diff --git a/packages/http/src/__generated__/services/coordinator/public/v1/public_api.client.ts b/packages/http/src/__generated__/services/coordinator/public/v1/public_api.client.ts index 7e478e925..0e91f20fc 100644 --- a/packages/http/src/__generated__/services/coordinator/public/v1/public_api.client.ts +++ b/packages/http/src/__generated__/services/coordinator/public/v1/public_api.client.ts @@ -116,10 +116,18 @@ import type { TExportPrivateKeyBody, TExportPrivateKeyResponse, } from "./public_api.fetcher"; +import type { + TExportWalletBody, + TExportWalletResponse, +} from "./public_api.fetcher"; import type { TInitUserEmailRecoveryBody, TInitUserEmailRecoveryResponse, } from "./public_api.fetcher"; +import type { + TRecoverUserBody, + TRecoverUserResponse, +} from "./public_api.fetcher"; import type { TRejectActivityBody, TRejectActivityResponse, @@ -1141,6 +1149,37 @@ export class TurnkeyClient { }; }; + /** + * Exports a Wallet + * + * Sign the provided `TExportWalletBody` with the client's `stamp` function, and submit the request (POST /public/v1/submit/export_wallet). + * + * See also {@link stampExportWallet}. + */ + exportWallet = async ( + input: TExportWalletBody + ): Promise => { + return this.request("/public/v1/submit/export_wallet", input); + }; + + /** + * Produce a `SignedRequest` from `TExportWalletBody` by using the client's `stamp` function. + * + * See also {@link ExportWallet}. + */ + stampExportWallet = async ( + input: TExportWalletBody + ): Promise => { + const fullUrl = this.config.baseUrl + "/public/v1/submit/export_wallet"; + const body = JSON.stringify(input); + const stamp = await this.stamper.stamp(body); + return { + body: body, + stamp: stamp, + url: fullUrl, + }; + }; + /** * Initializes a new recovery * @@ -1173,6 +1212,37 @@ export class TurnkeyClient { }; }; + /** + * Completes the process of recovering a user by adding an authenticator + * + * Sign the provided `TRecoverUserBody` with the client's `stamp` function, and submit the request (POST /public/v1/submit/recover_user). + * + * See also {@link stampRecoverUser}. + */ + recoverUser = async ( + input: TRecoverUserBody + ): Promise => { + return this.request("/public/v1/submit/recover_user", input); + }; + + /** + * Produce a `SignedRequest` from `TRecoverUserBody` by using the client's `stamp` function. + * + * See also {@link RecoverUser}. + */ + stampRecoverUser = async ( + input: TRecoverUserBody + ): Promise => { + const fullUrl = this.config.baseUrl + "/public/v1/submit/recover_user"; + const body = JSON.stringify(input); + const stamp = await this.stamper.stamp(body); + return { + body: body, + stamp: stamp, + url: fullUrl, + }; + }; + /** * Reject an Activity * diff --git a/packages/http/src/__generated__/services/coordinator/public/v1/public_api.fetcher.ts b/packages/http/src/__generated__/services/coordinator/public/v1/public_api.fetcher.ts index 2bebac98a..3eefa80bd 100644 --- a/packages/http/src/__generated__/services/coordinator/public/v1/public_api.fetcher.ts +++ b/packages/http/src/__generated__/services/coordinator/public/v1/public_api.fetcher.ts @@ -1459,6 +1459,52 @@ export const signExportPrivateKey = ( options, }); +/** + * `POST /public/v1/submit/export_wallet` + */ +export type TExportWalletResponse = + operations["PublicApiService_ExportWallet"]["responses"]["200"]["schema"]; + +/** + * `POST /public/v1/submit/export_wallet` + */ +export type TExportWalletInput = { body: TExportWalletBody }; + +/** + * `POST /public/v1/submit/export_wallet` + */ +export type TExportWalletBody = + operations["PublicApiService_ExportWallet"]["parameters"]["body"]["body"]; + +/** + * Export Wallet + * + * Exports a Wallet + * + * `POST /public/v1/submit/export_wallet` + */ +export const exportWallet = (input: TExportWalletInput) => + request({ + uri: "/public/v1/submit/export_wallet", + method: "POST", + body: input.body, + }); + +/** + * Request a WebAuthn assertion and return a signed `ExportWallet` request, ready to be POSTed to Turnkey. + * + * See {@link ExportWallet} + */ +export const signExportWallet = ( + input: TExportWalletInput, + options?: TurnkeyCredentialRequestOptions +) => + signedRequest({ + uri: "/public/v1/submit/export_wallet", + body: input.body, + options, + }); + /** * `POST /public/v1/submit/init_user_email_recovery` */ @@ -1511,6 +1557,52 @@ export const signInitUserEmailRecovery = ( options, }); +/** + * `POST /public/v1/submit/recover_user` + */ +export type TRecoverUserResponse = + operations["PublicApiService_RecoverUser"]["responses"]["200"]["schema"]; + +/** + * `POST /public/v1/submit/recover_user` + */ +export type TRecoverUserInput = { body: TRecoverUserBody }; + +/** + * `POST /public/v1/submit/recover_user` + */ +export type TRecoverUserBody = + operations["PublicApiService_RecoverUser"]["parameters"]["body"]["body"]; + +/** + * Recover a user + * + * Completes the process of recovering a user by adding an authenticator + * + * `POST /public/v1/submit/recover_user` + */ +export const recoverUser = (input: TRecoverUserInput) => + request({ + uri: "/public/v1/submit/recover_user", + method: "POST", + body: input.body, + }); + +/** + * Request a WebAuthn assertion and return a signed `RecoverUser` request, ready to be POSTed to Turnkey. + * + * See {@link RecoverUser} + */ +export const signRecoverUser = ( + input: TRecoverUserInput, + options?: TurnkeyCredentialRequestOptions +) => + signedRequest({ + uri: "/public/v1/submit/recover_user", + body: input.body, + options, + }); + /** * `POST /public/v1/submit/reject_activity` */ diff --git a/packages/http/src/__generated__/services/coordinator/public/v1/public_api.swagger.json b/packages/http/src/__generated__/services/coordinator/public/v1/public_api.swagger.json index 26b7bcbbc..e2edbfd4b 100644 --- a/packages/http/src/__generated__/services/coordinator/public/v1/public_api.swagger.json +++ b/packages/http/src/__generated__/services/coordinator/public/v1/public_api.swagger.json @@ -1020,6 +1020,38 @@ "tags": ["Private Keys"] } }, + "/public/v1/submit/export_wallet": { + "post": { + "summary": "Export Wallet", + "description": "Exports a Wallet", + "operationId": "PublicApiService_ExportWallet", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ActivityResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ExportWalletRequest" + } + } + ], + "tags": ["PublicApiService"] + } + }, "/public/v1/submit/init_user_email_recovery": { "post": { "summary": "Init Recovery", @@ -1052,6 +1084,38 @@ "tags": ["Organizations"] } }, + "/public/v1/submit/recover_user": { + "post": { + "summary": "Recover a user", + "description": "Completes the process of recovering a user by adding an authenticator", + "operationId": "PublicApiService_RecoverUser", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ActivityResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1RecoverUserRequest" + } + } + ], + "tags": ["Organizations"] + } + }, "/public/v1/submit/reject_activity": { "post": { "summary": "Reject Activity", @@ -1969,7 +2033,8 @@ "ACTIVITY_TYPE_REMOVE_ORGANIZATION_FEATURE", "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2", "ACTIVITY_TYPE_SIGN_TRANSACTION_V2", - "ACTIVITY_TYPE_EXPORT_PRIVATE_KEY" + "ACTIVITY_TYPE_EXPORT_PRIVATE_KEY", + "ACTIVITY_TYPE_EXPORT_WALLET" ] }, "v1ApiKey": { @@ -3473,6 +3538,59 @@ }, "required": ["privateKeyId", "exportBundle"] }, + "v1ExportWalletIntent": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "Unique identifier for a given Wallet." + }, + "targetPublicKey": { + "type": "string", + "description": "Client-side public key generated by the user, to which the export bundle will be encrypted." + }, + "language": { + "$ref": "#/definitions/v1MnemonicLanguage", + "description": "The language of the mnemonic to export. Defaults to English." + } + }, + "required": ["walletId", "targetPublicKey"] + }, + "v1ExportWalletRequest": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["ACTIVITY_TYPE_EXPORT_WALLET"] + }, + "timestampMs": { + "type": "string", + "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests." + }, + "organizationId": { + "type": "string", + "description": "Unique identifier for a given Organization." + }, + "parameters": { + "$ref": "#/definitions/v1ExportWalletIntent" + } + }, + "required": ["type", "timestampMs", "organizationId", "parameters"] + }, + "v1ExportWalletResult": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "Unique identifier for a given Wallet." + }, + "exportBundle": { + "type": "string", + "description": "Export bundle containing a wallet mnemonic + optional newline passphrase encrypted by the client's target public key." + } + }, + "required": ["walletId", "exportBundle"] + }, "v1Feature": { "type": "object", "properties": { @@ -3998,6 +4116,9 @@ }, "exportPrivateKeyIntent": { "$ref": "#/definitions/v1ExportPrivateKeyIntent" + }, + "exportWalletIntent": { + "$ref": "#/definitions/v1ExportWalletIntent" } }, "required": ["createOrganizationIntent"] @@ -4098,6 +4219,20 @@ "INVITATION_STATUS_REVOKED" ] }, + "v1MnemonicLanguage": { + "type": "string", + "enum": [ + "MNEMONIC_LANGUAGE_ENGLISH", + "MNEMONIC_LANGUAGE_SIMPLIFIED_CHINESE", + "MNEMONIC_LANGUAGE_TRADITIONAL_CHINESE", + "MNEMONIC_LANGUAGE_CZECH", + "MNEMONIC_LANGUAGE_FRENCH", + "MNEMONIC_LANGUAGE_ITALIAN", + "MNEMONIC_LANGUAGE_JAPANESE", + "MNEMONIC_LANGUAGE_KOREAN", + "MNEMONIC_LANGUAGE_SPANISH" + ] + }, "v1NOOPCodegenAnchorResponse": { "type": "object", "properties": { @@ -4173,6 +4308,13 @@ "type": "object", "$ref": "#/definitions/v1Feature" } + }, + "wallets": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1Wallet" + } } } }, @@ -4287,6 +4429,13 @@ }, "createdAt": { "$ref": "#/definitions/externaldatav1Timestamp" + }, + "updatedAt": { + "$ref": "#/definitions/externaldatav1Timestamp" + }, + "exported": { + "type": "boolean", + "description": "True when a given Private Key is exported, false otherwise." } }, "required": [ @@ -4296,7 +4445,9 @@ "curve", "addresses", "privateKeyTags", - "createdAt" + "createdAt", + "updatedAt", + "exported" ] }, "v1PrivateKeyParams": { @@ -4388,6 +4539,27 @@ }, "required": ["authenticator", "userId"] }, + "v1RecoverUserRequest": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["ACTIVITY_TYPE_RECOVER_USER"] + }, + "timestampMs": { + "type": "string", + "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests." + }, + "organizationId": { + "type": "string", + "description": "Unique identifier for a given Organization." + }, + "parameters": { + "$ref": "#/definitions/v1RecoverUserIntent" + } + }, + "required": ["type", "timestampMs", "organizationId", "parameters"] + }, "v1RecoverUserResult": { "type": "object", "properties": { @@ -4602,6 +4774,9 @@ }, "exportPrivateKeyResult": { "$ref": "#/definitions/v1ExportPrivateKeyResult" + }, + "exportWalletResult": { + "$ref": "#/definitions/v1ExportWalletResult" } } }, @@ -5509,6 +5684,36 @@ "createdAt" ] }, + "v1Wallet": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "Unique identifier for a given Wallet." + }, + "walletName": { + "type": "string", + "description": "Human-readable name for a Wallet." + }, + "createdAt": { + "$ref": "#/definitions/externaldatav1Timestamp" + }, + "updatedAt": { + "$ref": "#/definitions/externaldatav1Timestamp" + }, + "exported": { + "type": "boolean", + "description": "True when a given Wallet is exported, false otherwise." + } + }, + "required": [ + "walletId", + "walletName", + "createdAt", + "updatedAt", + "exported" + ] + }, "v1WalletAccountParams": { "type": "object", "properties": { diff --git a/packages/http/src/__generated__/services/coordinator/public/v1/public_api.types.ts b/packages/http/src/__generated__/services/coordinator/public/v1/public_api.types.ts index 4cd72b7ae..983c6bcd4 100644 --- a/packages/http/src/__generated__/services/coordinator/public/v1/public_api.types.ts +++ b/packages/http/src/__generated__/services/coordinator/public/v1/public_api.types.ts @@ -124,10 +124,18 @@ export type paths = { /** Exports a Private Key */ post: operations["PublicApiService_ExportPrivateKey"]; }; + "/public/v1/submit/export_wallet": { + /** Exports a Wallet */ + post: operations["PublicApiService_ExportWallet"]; + }; "/public/v1/submit/init_user_email_recovery": { /** Initializes a new recovery */ post: operations["PublicApiService_InitUserEmailRecovery"]; }; + "/public/v1/submit/recover_user": { + /** Completes the process of recovering a user by adding an authenticator */ + post: operations["PublicApiService_RecoverUser"]; + }; "/public/v1/submit/reject_activity": { /** Reject an Activity */ post: operations["PublicApiService_RejectActivity"]; @@ -446,7 +454,8 @@ export type definitions = { | "ACTIVITY_TYPE_REMOVE_ORGANIZATION_FEATURE" | "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2" | "ACTIVITY_TYPE_SIGN_TRANSACTION_V2" - | "ACTIVITY_TYPE_EXPORT_PRIVATE_KEY"; + | "ACTIVITY_TYPE_EXPORT_PRIVATE_KEY" + | "ACTIVITY_TYPE_EXPORT_WALLET"; v1ApiKey: { /** @description A User credential that can be used to authenticate to Turnkey. */ credential: definitions["externaldatav1Credential"]; @@ -1006,6 +1015,29 @@ export type definitions = { /** @description Export bundle containing a private key encrypted to the client's target public key. */ exportBundle: string; }; + v1ExportWalletIntent: { + /** @description Unique identifier for a given Wallet. */ + walletId: string; + /** @description Client-side public key generated by the user, to which the export bundle will be encrypted. */ + targetPublicKey: string; + /** @description The language of the mnemonic to export. Defaults to English. */ + language?: definitions["v1MnemonicLanguage"]; + }; + v1ExportWalletRequest: { + /** @enum {string} */ + type: "ACTIVITY_TYPE_EXPORT_WALLET"; + /** @description Timestamp (in milliseconds) of the request, used to verify liveness of user requests. */ + timestampMs: string; + /** @description Unique identifier for a given Organization. */ + organizationId: string; + parameters: definitions["v1ExportWalletIntent"]; + }; + v1ExportWalletResult: { + /** @description Unique identifier for a given Wallet. */ + walletId: string; + /** @description Export bundle containing a wallet mnemonic + optional newline passphrase encrypted by the client's target public key. */ + exportBundle: string; + }; v1Feature: { name?: definitions["v1FeatureName"]; value?: string; @@ -1201,6 +1233,7 @@ export type definitions = { signRawPayloadIntentV2?: definitions["v1SignRawPayloadIntentV2"]; signTransactionIntentV2?: definitions["v1SignTransactionIntentV2"]; exportPrivateKeyIntent?: definitions["v1ExportPrivateKeyIntent"]; + exportWalletIntent?: definitions["v1ExportWalletIntent"]; }; v1Invitation: { /** @description Unique identifier for a given Invitation object. */ @@ -1237,6 +1270,17 @@ export type definitions = { | "INVITATION_STATUS_CREATED" | "INVITATION_STATUS_ACCEPTED" | "INVITATION_STATUS_REVOKED"; + /** @enum {string} */ + v1MnemonicLanguage: + | "MNEMONIC_LANGUAGE_ENGLISH" + | "MNEMONIC_LANGUAGE_SIMPLIFIED_CHINESE" + | "MNEMONIC_LANGUAGE_TRADITIONAL_CHINESE" + | "MNEMONIC_LANGUAGE_CZECH" + | "MNEMONIC_LANGUAGE_FRENCH" + | "MNEMONIC_LANGUAGE_ITALIAN" + | "MNEMONIC_LANGUAGE_JAPANESE" + | "MNEMONIC_LANGUAGE_KOREAN" + | "MNEMONIC_LANGUAGE_SPANISH"; v1NOOPCodegenAnchorResponse: { stamp: definitions["v1WebAuthnStamp"]; }; @@ -1252,6 +1296,7 @@ export type definitions = { rootQuorum?: definitions["externaldatav1Quorum"]; allowedOrigins?: string[]; features?: definitions["v1Feature"][]; + wallets?: definitions["v1Wallet"][]; }; v1Pagination: { /** @description A limit of the number of object to be returned, between 1 and 100. Defaults to 10. */ @@ -1295,6 +1340,9 @@ export type definitions = { /** @description A list of Private Key Tag IDs. */ privateKeyTags: string[]; createdAt: definitions["externaldatav1Timestamp"]; + updatedAt: definitions["externaldatav1Timestamp"]; + /** @description True when a given Private Key is exported, false otherwise. */ + exported: boolean; }; v1PrivateKeyParams: { /** @description Human-readable name for a Private Key. */ @@ -1326,6 +1374,15 @@ export type definitions = { /** @description Unique identifier for the user performing recovery. */ userId: string; }; + v1RecoverUserRequest: { + /** @enum {string} */ + type: "ACTIVITY_TYPE_RECOVER_USER"; + /** @description Timestamp (in milliseconds) of the request, used to verify liveness of user requests. */ + timestampMs: string; + /** @description Unique identifier for a given Organization. */ + organizationId: string; + parameters: definitions["v1RecoverUserIntent"]; + }; v1RecoverUserResult: { /** @description ID of the authenticator created. */ authenticatorId: string[]; @@ -1402,6 +1459,7 @@ export type definitions = { setOrganizationFeatureResult?: definitions["v1SetOrganizationFeatureResult"]; removeOrganizationFeatureResult?: definitions["v1RemoveOrganizationFeatureResult"]; exportPrivateKeyResult?: definitions["v1ExportPrivateKeyResult"]; + exportWalletResult?: definitions["v1ExportWalletResult"]; }; v1RootUserParams: { /** @description Human-readable name for a User. */ @@ -1753,6 +1811,16 @@ export type definitions = { scheme: string; createdAt: definitions["externaldatav1Timestamp"]; }; + v1Wallet: { + /** @description Unique identifier for a given Wallet. */ + walletId: string; + /** @description Human-readable name for a Wallet. */ + walletName: string; + createdAt: definitions["externaldatav1Timestamp"]; + updatedAt: definitions["externaldatav1Timestamp"]; + /** @description True when a given Wallet is exported, false otherwise. */ + exported: boolean; + }; v1WalletAccountParams: { /** @description Cryptographic curve used to generate a wallet Account. */ curve: definitions["immutablecommonv1Curve"]; @@ -2316,6 +2384,24 @@ export type operations = { }; }; }; + /** Exports a Wallet */ + PublicApiService_ExportWallet: { + parameters: { + body: { + body: definitions["v1ExportWalletRequest"]; + }; + }; + responses: { + /** A successful response. */ + 200: { + schema: definitions["v1ActivityResponse"]; + }; + /** An unexpected error response. */ + default: { + schema: definitions["rpcStatus"]; + }; + }; + }; /** Initializes a new recovery */ PublicApiService_InitUserEmailRecovery: { parameters: { @@ -2334,6 +2420,24 @@ export type operations = { }; }; }; + /** Completes the process of recovering a user by adding an authenticator */ + PublicApiService_RecoverUser: { + parameters: { + body: { + body: definitions["v1RecoverUserRequest"]; + }; + }; + responses: { + /** A successful response. */ + 200: { + schema: definitions["v1ActivityResponse"]; + }; + /** An unexpected error response. */ + default: { + schema: definitions["rpcStatus"]; + }; + }; + }; /** Reject an Activity */ PublicApiService_RejectActivity: { parameters: { diff --git a/packages/iframe-stamper/CHANGELOG.md b/packages/iframe-stamper/CHANGELOG.md new file mode 100644 index 000000000..85a4e7460 --- /dev/null +++ b/packages/iframe-stamper/CHANGELOG.md @@ -0,0 +1,5 @@ +# @turnkey/webauthn-stamper + +## 0.1.0 + +Initial release diff --git a/packages/iframe-stamper/LICENSE b/packages/iframe-stamper/LICENSE new file mode 100644 index 000000000..de8bbeab2 --- /dev/null +++ b/packages/iframe-stamper/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 Turnkey + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/iframe-stamper/README.md b/packages/iframe-stamper/README.md new file mode 100644 index 000000000..ddce0363b --- /dev/null +++ b/packages/iframe-stamper/README.md @@ -0,0 +1,33 @@ +# @turnkey/iframe-stamper + +[![npm](https://img.shields.io/npm/v/@turnkey/iframe-stamper?color=%234C48FF)](https://www.npmjs.com/package/@turnkey/iframe-stamper) + +This package contains functions to stamp a Turnkey request through credentials contained in an iframe. It is meant to be used with [`@turnkey/http`](https://www.npmjs.com/package/@turnkey/http) to build flows. + +Usage: + +```ts +import { IframeStamper } from "@turnkey/iframe-stamper"; +import { TurnkeyClient } from "@turnkey/http"; + +const TurnkeyIframeContainerId = "turnkey-iframe-container"; +const TurnkeyIframeElementId = "turnkey-iframe"; + +const iframeStamper = new IframeStamper({ + iframeUrl: process.env.IFRAME_URL!, + iframeContainerId: TurnkeyIframeContainerId, + iframeElementId: TurnkeyIframeElementId, +}); + +// This inserts the iframe in the DOM and returns the public key +const publicKey = await iframeStamper.init(); + +// Injects a new credential in the iframe +const injected = await iframeStamper.injectRecoveryBundle(recoveryBundle); + +// New HTTP client able to sign with the credentials inside of the iframe +const httpClient = new TurnkeyClient( + { baseUrl: "https://api.turnkey.com" }, + iframeStamper +); +``` diff --git a/packages/iframe-stamper/jest.config.js b/packages/iframe-stamper/jest.config.js new file mode 100644 index 000000000..87d1f6e27 --- /dev/null +++ b/packages/iframe-stamper/jest.config.js @@ -0,0 +1,9 @@ +/** @type {import("@jest/types").Config.InitialOptions} */ +const config = { + transform: { + "\\.[jt]sx?$": "@turnkey/jest-config/transformer.js", + }, + testPathIgnorePatterns: ["/dist/", "/node_modules/"], +}; + +module.exports = config; diff --git a/packages/iframe-stamper/package.json b/packages/iframe-stamper/package.json new file mode 100644 index 000000000..ba9677e91 --- /dev/null +++ b/packages/iframe-stamper/package.json @@ -0,0 +1,43 @@ +{ + "name": "@turnkey/iframe-stamper", + "version": "0.1.0", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "license": "Apache-2.0", + "description": "Iframe-based stamper for @turnkey/http", + "keywords": [ + "Turnkey", + "http", + "stamper" + ], + "author": { + "name": "Turnkey", + "url": "https://turnkey.com/" + }, + "homepage": "https://github.com/tkhq/sdk", + "bugs": { + "url": "https://github.com/tkhq/sdk/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/tkhq/sdk.git", + "directory": "packages/iframe-stamper" + }, + "files": [ + "dist/", + "CHANGELOG.md" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "prepublishOnly": "pnpm -w run clean-all && pnpm -w run build-all", + "build": "tsc", + "clean": "rimraf ./dist ./.cache", + "test": "jest", + "typecheck": "tsc -p tsconfig.typecheck.json" + }, + "engines": { + "node": ">=16.0.0" + } +} diff --git a/packages/iframe-stamper/src/__tests__/iframe-test.ts b/packages/iframe-stamper/src/__tests__/iframe-test.ts new file mode 100644 index 000000000..feff3e406 --- /dev/null +++ b/packages/iframe-stamper/src/__tests__/iframe-test.ts @@ -0,0 +1,12 @@ +import { test, expect } from "@jest/globals"; +import { IframeStamper } from "../index"; + +test("throws when instantiated outside of a browser environment", async function () { + expect(() => { + new IframeStamper({ + iframeUrl: "https://recovery.tkhqlabs.xyz", + iframeContainerId: "my-container-id", + iframeElementId: "my-iframe-id", + }); + }).toThrow("Cannot initialize iframe in non-browser environment"); +}); diff --git a/packages/iframe-stamper/src/index.ts b/packages/iframe-stamper/src/index.ts new file mode 100644 index 000000000..a17110641 --- /dev/null +++ b/packages/iframe-stamper/src/index.ts @@ -0,0 +1,192 @@ +/// + +// Header name for an API key stamp +const stampHeaderName = "X-Stamp"; + +// Set of constants for event types expected to be sent and received between a parent page and its iframe. +export enum IframeEventType { + // Event sent by the iframe to its parent to indicate readiness. + // Value: the iframe public key + PublicKeyReady = "PUBLIC_KEY_READY", + // Event sent by the parent to inject a recovery bundle into the iframe. + // Value: the bundle to inject + InjectRecoveryBundle = "INJECT_RECOVERY_BUNDLE", + // Event sent by the iframe to its parent when `InjectBundle` is successful + // Value: true (boolean) + BundleInjected = "BUNDLE_INJECTED", + // Event sent by the parent page to request a signature + // Value: payload to sign + StampRequest = "STAMP_REQUEST", + // Event sent by the iframe to communicate the result of a stamp operation. + // Value: signed payload + Stamp = "STAMP", +} + +type TStamp = { + stampHeaderName: string; + stampHeaderValue: string; +}; + +export type TIframeStamperConfig = { + iframeUrl: string; + iframeElementId: string; + iframeContainerId: string; +}; + +/** + * Stamper to use with `@turnkey/http`'s `TurnkeyClient` + * Creating a stamper inserts an iframe in the current page. + */ +export class IframeStamper { + container: HTMLElement; + iframe: HTMLIFrameElement; + iframeOrigin: string; + iframePublicKey: string | null; + + /** + * Creates a new iframe stamper. This function _does not_ insert the iframe in the DOM. + * Call `.init()` to insert the iframe element in the DOM. + */ + constructor(config: TIframeStamperConfig) { + if (typeof window === "undefined") { + throw new Error("Cannot initialize iframe in non-browser environment"); + } + + if (document.getElementById(config.iframeElementId)) { + throw new Error( + `Iframe element with ID ${config.iframeElementId} already exists` + ); + } + + const container = document.getElementById(config.iframeContainerId); + if (!container) { + throw new Error( + `Cannot create iframe stamper: no container with ID ${config.iframeContainerId} exists in the current document` + ); + } + this.container = container; + + let iframe = window.document.createElement("iframe"); + iframe.id = config.iframeElementId; + iframe.src = config.iframeUrl; + this.iframe = iframe; + + const iframeUrl = new URL(config.iframeUrl); + this.iframeOrigin = iframeUrl.origin; + + // This is populated once the iframe is ready. Call `.init()` to kick off DOM insertion! + this.iframePublicKey = null; + } + + /** + * Inserts the iframe on the page and returns a promise resolving to the iframe's public key + */ + async init(): Promise { + this.container.appendChild(this.iframe); + return new Promise((resolve, _reject) => { + window.addEventListener( + "message", + (event) => { + if (event.origin !== this.iframeOrigin) { + // There might be other things going on in the window, for example: react dev tools, other extensions, etc. + // Instead of erroring out + return; + } + if (event.data?.type === IframeEventType.PublicKeyReady) { + this.iframePublicKey = event.data["value"]; + resolve(event.data["value"]); + } + }, + false + ); + }); + } + + /** + * Removes the iframe from the DOM + */ + clear() { + this.iframe.remove(); + } + + /** + * Returns the public key, or `null` if the underlying iframe isn't properly initialized. + */ + publicKey(): string | null { + return this.iframePublicKey; + } + + /** + * Function to inject a new credential into the iframe + * The bundle should be encrypted to the iframe's initial public key + * Encryption should be performed with HPKE (RFC 9180). + * This is used during recovery flows. + */ + async injectRecoveryBundle(bundle: string): Promise { + this.iframe.contentWindow?.postMessage( + { + type: IframeEventType.InjectRecoveryBundle, + value: bundle, + }, + "*" + ); + + return new Promise((resolve, _reject) => { + window.addEventListener( + "message", + (event) => { + if (event.origin !== this.iframeOrigin) { + // There might be other things going on in the window, for example: react dev tools, other extensions, etc. + // Instead of erroring out we simply return. Not our event! + return; + } + if (event.data?.type === IframeEventType.BundleInjected) { + resolve(event.data["value"]); + } + }, + false + ); + }); + } + + /** + * Function to sign a payload with the underlying iframe + */ + async stamp(payload: string): Promise { + if (this.iframePublicKey === null) { + throw new Error( + "null iframe public key. Have you called/awaited .init()?" + ); + } + + const iframeOrigin = this.iframeOrigin; + + this.iframe.contentWindow?.postMessage( + { + type: IframeEventType.StampRequest, + value: payload, + }, + "*" + ); + + return new Promise(function (resolve, _reject) { + window.addEventListener( + "message", + (event) => { + if (event.origin !== iframeOrigin) { + // There might be other things going on in the window, for example: react dev tools, other extensions, etc. + // Instead of erroring out we simply return. Not our event! + return; + } + if (event.data?.type === IframeEventType.Stamp) { + resolve({ + stampHeaderName: stampHeaderName, + stampHeaderValue: event.data["value"], + }); + } + }, + false + ); + }); + } +} diff --git a/packages/iframe-stamper/tsconfig.json b/packages/iframe-stamper/tsconfig.json new file mode 100644 index 000000000..6bc6235f8 --- /dev/null +++ b/packages/iframe-stamper/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "tsBuildInfoFile": "./.cache/.tsbuildinfo" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.json"], + "exclude": ["**/__tests__/**/*", "**/__fixtures__/**/*"] +} diff --git a/packages/iframe-stamper/tsconfig.typecheck.json b/packages/iframe-stamper/tsconfig.typecheck.json new file mode 100644 index 000000000..06c7c29b9 --- /dev/null +++ b/packages/iframe-stamper/tsconfig.typecheck.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "./.cache/.typecheck.tsbuildinfo" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.json"] +} diff --git a/packages/viem/jest.config.js b/packages/viem/jest.config.js index a86faa5a1..700668d29 100644 --- a/packages/viem/jest.config.js +++ b/packages/viem/jest.config.js @@ -5,6 +5,7 @@ const config = { }, testPathIgnorePatterns: ["/dist/", "/node_modules/"], setupFiles: ["dotenv/config"], + testTimeout: 30 * 1000, // For slow CI machines }; module.exports = config; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9a8ab2b6..2eda53cf1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,6 +3,7 @@ lockfileVersion: '6.0' overrides: '@confio/ics23@0.6.8>protobufjs': '>=7.2.4' protobufjs@>=6.10.0 <7.2.4: '>=7.2.4' + '@babel/traverse': '>=7.23.2' importers: @@ -63,6 +64,63 @@ importers: specifier: 0.8.13 version: 0.8.13 + examples/email-recovery: + dependencies: + '@turnkey/api-key-stamper': + specifier: workspace:* + version: link:../../packages/api-key-stamper + '@turnkey/http': + specifier: workspace:* + version: link:../../packages/http + '@turnkey/iframe-stamper': + specifier: workspace:* + version: link:../../packages/iframe-stamper + '@types/node': + specifier: 20.3.1 + version: 20.3.1 + '@types/react': + specifier: 18.2.14 + version: 18.2.14 + '@types/react-dom': + specifier: 18.2.6 + version: 18.2.6 + axios: + specifier: ^1.4.0 + version: 1.4.0 + encoding: + specifier: ^0.1.13 + version: 0.1.13 + eslint: + specifier: 8.43.0 + version: 8.43.0 + eslint-config-next: + specifier: 13.4.7 + version: 13.4.7(eslint@8.43.0)(typescript@5.1.3) + esm: + specifier: ^3.2.25 + version: 3.2.25 + install: + specifier: ^0.13.0 + version: 0.13.0 + next: + specifier: 13.4.7 + version: 13.4.7(@babel/core@7.22.20)(react-dom@18.2.0)(react@18.2.0) + npm: + specifier: ^9.7.2 + version: 9.7.2 + react: + specifier: 18.2.0 + version: 18.2.0 + react-dom: + specifier: 18.2.0 + version: 18.2.0(react@18.2.0) + react-hook-form: + specifier: ^7.45.1 + version: 7.45.1(react@18.2.0) + typescript: + specifier: 5.1.3 + version: 5.1.3 + examples/rebalancer: dependencies: '@turnkey/api-key-stamper': @@ -594,6 +652,8 @@ importers: specifier: ^3.1.5 version: 3.1.5 + packages/iframe-stamper: {} + packages/viem: dependencies: '@turnkey/api-key-stamper': @@ -662,7 +722,6 @@ packages: dependencies: '@babel/highlight': 7.22.20 chalk: 2.4.2 - dev: false /@babel/compat-data@7.20.14: resolution: {integrity: sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==} @@ -685,7 +744,7 @@ packages: '@babel/helpers': 7.20.13 '@babel/parser': 7.20.15 '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 + '@babel/traverse': 7.23.2 '@babel/types': 7.20.7 convert-source-map: 1.9.0 debug: 4.3.4(supports-color@8.1.1) @@ -701,14 +760,14 @@ packages: dependencies: '@ampproject/remapping': 2.2.1 '@babel/code-frame': 7.22.13 - '@babel/generator': 7.22.15 + '@babel/generator': 7.23.0 '@babel/helper-compilation-targets': 7.22.15 '@babel/helper-module-transforms': 7.22.20(@babel/core@7.22.20) '@babel/helpers': 7.22.15 - '@babel/parser': 7.22.16 + '@babel/parser': 7.23.0 '@babel/template': 7.22.15 - '@babel/traverse': 7.22.20 - '@babel/types': 7.22.19 + '@babel/traverse': 7.23.2 + '@babel/types': 7.23.0 convert-source-map: 1.9.0 debug: 4.3.4(supports-color@8.1.1) gensync: 1.0.0-beta.2 @@ -726,21 +785,20 @@ packages: '@jridgewell/gen-mapping': 0.3.2 jsesc: 2.5.2 - /@babel/generator@7.22.15: - resolution: {integrity: sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==} + /@babel/generator@7.23.0: + resolution: {integrity: sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.19 + '@babel/types': 7.23.0 '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.19 jsesc: 2.5.2 - dev: false /@babel/helper-annotate-as-pure@7.18.6: resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.23.0 dev: false /@babel/helper-builder-binary-assignment-operator-visitor@7.18.9: @@ -748,7 +806,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/helper-explode-assignable-expression': 7.18.6 - '@babel/types': 7.20.7 + '@babel/types': 7.23.0 dev: false /@babel/helper-compilation-targets@7.20.7(@babel/core@7.20.12): @@ -762,7 +820,7 @@ packages: '@babel/helper-validator-option': 7.18.6 browserslist: 4.21.5 lru-cache: 5.1.1 - semver: 6.3.0 + semver: 6.3.1 /@babel/helper-compilation-targets@7.22.15: resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==} @@ -783,13 +841,13 @@ packages: dependencies: '@babel/core': 7.20.12 '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 '@babel/helper-member-expression-to-functions': 7.20.7 '@babel/helper-optimise-call-expression': 7.18.6 '@babel/helper-replace-supers': 7.20.7 '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 - '@babel/helper-split-export-declaration': 7.18.6 + '@babel/helper-split-export-declaration': 7.22.6 transitivePeerDependencies: - supports-color dev: false @@ -816,7 +874,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) lodash.debounce: 4.0.8 resolve: 1.22.1 - semver: 6.3.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color dev: false @@ -828,61 +886,60 @@ packages: /@babel/helper-environment-visitor@7.22.20: resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} engines: {node: '>=6.9.0'} - dev: false /@babel/helper-explode-assignable-expression@7.18.6: resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.23.0 dev: false /@babel/helper-function-name@7.19.0: resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.20.7 - '@babel/types': 7.20.7 + '@babel/template': 7.22.15 + '@babel/types': 7.23.0 + dev: false - /@babel/helper-function-name@7.22.5: - resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==} + /@babel/helper-function-name@7.23.0: + resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.22.15 - '@babel/types': 7.22.19 - dev: false + '@babel/types': 7.23.0 /@babel/helper-hoist-variables@7.18.6: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.23.0 + dev: false /@babel/helper-hoist-variables@7.22.5: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.19 - dev: false + '@babel/types': 7.23.0 /@babel/helper-member-expression-to-functions@7.20.7: resolution: {integrity: sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.23.0 dev: false /@babel/helper-module-imports@7.18.6: resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.23.0 /@babel/helper-module-imports@7.22.15: resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.19 + '@babel/types': 7.23.0 dev: false /@babel/helper-module-transforms@7.20.11: @@ -895,7 +952,7 @@ packages: '@babel/helper-split-export-declaration': 7.18.6 '@babel/helper-validator-identifier': 7.19.1 '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 + '@babel/traverse': 7.23.2 '@babel/types': 7.20.7 transitivePeerDependencies: - supports-color @@ -918,7 +975,7 @@ packages: resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.23.0 dev: false /@babel/helper-plugin-utils@7.20.2: @@ -933,9 +990,9 @@ packages: dependencies: '@babel/core': 7.20.12 '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-wrap-function': 7.20.5 - '@babel/types': 7.20.7 + '@babel/types': 7.23.0 transitivePeerDependencies: - supports-color dev: false @@ -944,12 +1001,12 @@ packages: resolution: {integrity: sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-member-expression-to-functions': 7.20.7 '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 + '@babel/template': 7.22.15 + '@babel/traverse': 7.23.2 + '@babel/types': 7.23.0 transitivePeerDependencies: - supports-color dev: false @@ -958,34 +1015,33 @@ packages: resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.23.0 /@babel/helper-simple-access@7.22.5: resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.19 + '@babel/types': 7.23.0 dev: false /@babel/helper-skip-transparent-expression-wrappers@7.20.0: resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.23.0 dev: false /@babel/helper-split-export-declaration@7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.23.0 /@babel/helper-split-export-declaration@7.22.6: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.19 - dev: false + '@babel/types': 7.23.0 /@babel/helper-string-parser@7.19.4: resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} @@ -994,7 +1050,6 @@ packages: /@babel/helper-string-parser@7.22.5: resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} engines: {node: '>=6.9.0'} - dev: false /@babel/helper-validator-identifier@7.19.1: resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} @@ -1003,7 +1058,6 @@ packages: /@babel/helper-validator-identifier@7.22.20: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} - dev: false /@babel/helper-validator-option@7.18.6: resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} @@ -1018,10 +1072,10 @@ packages: resolution: {integrity: sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-function-name': 7.19.0 - '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 + '@babel/helper-function-name': 7.23.0 + '@babel/template': 7.22.15 + '@babel/traverse': 7.23.2 + '@babel/types': 7.23.0 transitivePeerDependencies: - supports-color dev: false @@ -1031,7 +1085,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 + '@babel/traverse': 7.23.2 '@babel/types': 7.20.7 transitivePeerDependencies: - supports-color @@ -1041,8 +1095,8 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.22.15 - '@babel/traverse': 7.22.20 - '@babel/types': 7.22.19 + '@babel/traverse': 7.23.2 + '@babel/types': 7.23.0 transitivePeerDependencies: - supports-color dev: false @@ -1051,7 +1105,7 @@ packages: resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.19.1 + '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 @@ -1062,7 +1116,6 @@ packages: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 - dev: false /@babel/parser@7.20.15: resolution: {integrity: sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==} @@ -1071,13 +1124,12 @@ packages: dependencies: '@babel/types': 7.20.7 - /@babel/parser@7.22.16: - resolution: {integrity: sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==} + /@babel/parser@7.23.0: + resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.22.19 - dev: false + '@babel/types': 7.23.0 /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.20.12): resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} @@ -2019,44 +2071,25 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.22.13 - '@babel/parser': 7.22.16 - '@babel/types': 7.22.19 - dev: false - - /@babel/traverse@7.20.13: - resolution: {integrity: sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.20.14 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.20.15 - '@babel/types': 7.20.7 - debug: 4.3.4(supports-color@8.1.1) - globals: 11.12.0 - transitivePeerDependencies: - - supports-color + '@babel/parser': 7.23.0 + '@babel/types': 7.23.0 - /@babel/traverse@7.22.20: - resolution: {integrity: sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==} + /@babel/traverse@7.23.2: + resolution: {integrity: sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==} engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.22.13 - '@babel/generator': 7.22.15 + '@babel/generator': 7.23.0 '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.22.5 + '@babel/helper-function-name': 7.23.0 '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.22.16 - '@babel/types': 7.22.19 + '@babel/parser': 7.23.0 + '@babel/types': 7.23.0 debug: 4.3.4(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: false /@babel/types@7.20.7: resolution: {integrity: sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==} @@ -2066,14 +2099,13 @@ packages: '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 - /@babel/types@7.22.19: - resolution: {integrity: sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==} + /@babel/types@7.23.0: + resolution: {integrity: sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-string-parser': 7.22.5 '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 - dev: false /@bcoe/v8-coverage@0.2.3: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -3392,7 +3424,7 @@ packages: resolution: {integrity: sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jridgewell/trace-mapping': 0.3.17 + '@jridgewell/trace-mapping': 0.3.19 callsites: 3.1.0 graceful-fs: 4.2.10 dev: true @@ -3455,15 +3487,15 @@ packages: engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/sourcemap-codec': 1.4.15 /@jridgewell/gen-mapping@0.3.2: resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - '@jridgewell/trace-mapping': 0.3.17 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.19 /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} @@ -3472,7 +3504,6 @@ packages: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/trace-mapping': 0.3.19 - dev: false /@jridgewell/resolve-uri@3.1.0: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} @@ -3481,7 +3512,6 @@ packages: /@jridgewell/resolve-uri@3.1.1: resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} - dev: false /@jridgewell/set-array@1.1.2: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} @@ -3492,7 +3522,6 @@ packages: /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: false /@jridgewell/trace-mapping@0.3.17: resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} @@ -3505,7 +3534,6 @@ packages: dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 - dev: false /@manypkg/find-root@1.1.0: resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -4458,18 +4486,18 @@ packages: /@types/babel__generator@7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.23.0 /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: - '@babel/parser': 7.20.15 - '@babel/types': 7.20.7 + '@babel/parser': 7.23.0 + '@babel/types': 7.23.0 /@types/babel__traverse@7.18.3: resolution: {integrity: sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.23.0 /@types/bn.js@4.11.6: resolution: {integrity: sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==} @@ -4541,10 +4569,6 @@ packages: /@types/node@16.18.12: resolution: {integrity: sha512-vzLe5NaNMjIE3mcddFVGlAXN1LEWueUsMsOJWaT6wWMJGyljHAWHznqfnKUQWGzu7TLPrGvWdNAsvQYW+C0xtw==} - /@types/node@18.13.0: - resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==} - dev: true - /@types/node@20.3.1: resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==} dev: false @@ -4565,7 +4589,7 @@ packages: /@types/prompts@2.4.2: resolution: {integrity: sha512-TwNx7qsjvRIUv/BCx583tqF5IINEVjCNqg9ofKHRlSoUHE62WBHrem4B1HGXcIrG511v29d1kJ9a/t2Esz7MIg==} dependencies: - '@types/node': 18.13.0 + '@types/node': 16.18.12 kleur: 3.0.3 dev: true @@ -5121,8 +5145,8 @@ packages: resolution: {integrity: sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/template': 7.20.7 - '@babel/types': 7.20.7 + '@babel/template': 7.22.15 + '@babel/types': 7.23.0 '@types/babel__core': 7.20.0 '@types/babel__traverse': 7.18.3 @@ -5134,7 +5158,7 @@ packages: '@babel/compat-data': 7.20.14 '@babel/core': 7.20.12 '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.20.12) - semver: 6.3.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color dev: false @@ -5364,7 +5388,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001453 + caniuse-lite: 1.0.30001535 electron-to-chromium: 1.4.298 node-releases: 2.0.10 update-browserslist-db: 1.0.10(browserslist@4.21.5) @@ -5456,12 +5480,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - /caniuse-lite@1.0.30001453: - resolution: {integrity: sha512-R9o/uySW38VViaTrOtwfbFEiBFUh7ST3uIG4OEymIG3/uKdHDO4xk/FaqfUw0d+irSUyFPy3dZszf9VvSTPnsA==} - /caniuse-lite@1.0.30001535: resolution: {integrity: sha512-48jLyUkiWFfhm/afF7cQPqPjaUmSraEhK4j+FCTJpgnGGEZHqyLe3hmWH7lIooZdSzXL0ReMvHz0vKDoTBsrwg==} - dev: false /case@1.6.3: resolution: {integrity: sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==} @@ -6259,7 +6279,7 @@ packages: minimatch: 3.1.2 object.values: 1.1.6 resolve: 1.22.1 - semver: 6.3.0 + semver: 6.3.1 tsconfig-paths: 3.14.2 transitivePeerDependencies: - eslint-import-resolver-typescript @@ -6289,7 +6309,7 @@ packages: minimatch: 3.1.2 object.entries: 1.1.6 object.fromentries: 2.0.6 - semver: 6.3.0 + semver: 6.3.1 dev: false /eslint-plugin-react-hooks@4.6.0(eslint@8.43.0): @@ -6321,7 +6341,7 @@ packages: object.values: 1.1.6 prop-types: 15.8.1 resolve: 2.0.0-next.4 - semver: 6.3.0 + semver: 6.3.1 string.prototype.matchall: 4.0.8 dev: false @@ -6631,16 +6651,6 @@ packages: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: false - /fast-glob@3.2.12: - resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} - engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - /fast-glob@3.3.0: resolution: {integrity: sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==} engines: {node: '>=8.6.0'} @@ -6650,7 +6660,6 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.5 - dev: false /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -6933,7 +6942,7 @@ packages: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.2.12 + fast-glob: 3.3.0 ignore: 5.2.4 merge2: 1.4.1 slash: 3.0.0 @@ -7557,10 +7566,10 @@ packages: engines: {node: '>=8'} dependencies: '@babel/core': 7.20.12 - '@babel/parser': 7.20.15 + '@babel/parser': 7.23.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 - semver: 6.3.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -7929,7 +7938,7 @@ packages: '@babel/generator': 7.20.14 '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.20.12) '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.20.12) - '@babel/traverse': 7.20.13 + '@babel/traverse': 7.23.2 '@babel/types': 7.20.7 '@jest/expect-utils': 29.4.3 '@jest/transform': 29.4.3 @@ -8281,7 +8290,7 @@ packages: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} dependencies: - semver: 6.3.0 + semver: 6.3.1 dev: true /makeerror@1.0.12: @@ -8507,7 +8516,7 @@ packages: '@next/env': 13.4.7 '@swc/helpers': 0.5.1 busboy: 1.6.0 - caniuse-lite: 1.0.30001453 + caniuse-lite: 1.0.30001535 postcss: 8.4.14 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -8862,7 +8871,7 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.18.6 + '@babel/code-frame': 7.22.13 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -9367,7 +9376,6 @@ packages: /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - dev: false /semver@7.3.8: resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} @@ -10042,7 +10050,7 @@ packages: resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} engines: {node: '>=10.12.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.17 + '@jridgewell/trace-mapping': 0.3.19 '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 1.9.0 dev: true diff --git a/tsconfig.mono.json b/tsconfig.mono.json index a232a320a..4fdd18114 100644 --- a/tsconfig.mono.json +++ b/tsconfig.mono.json @@ -5,6 +5,7 @@ { "path": "./packages/http" }, { "path": "./packages/webauthn-stamper" }, { "path": "./packages/api-key-stamper" }, + { "path": "./packages/iframe-stamper" }, { "path": "./packages/viem" } ], "files": []