diff --git a/.circleci/config.yml b/.circleci/config.yml index 21ad2a104..cb62d62d1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,20 +5,20 @@ jobs: build: docker: - image: cimg/node:lts-browsers - resource_class: xlarge + resource_class: 2xlarge steps: - checkout - restore_cache: - key: dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum "examples/kitchen-sink-example/package.json" }} + key: dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum "example-app/package.json" }} - run: npm ci - run: - name: npm run install:kitchen-sink + name: npm run install:example command: | if [ -z "$CIRCLE_PR_NUMBER" ]; then - npm run install:kitchen-sink + npm run install:example fi - save_cache: - key: dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum "examples/kitchen-sink-example/package.json" }} + key: dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum "example-app/package.json" }} paths: - ~/.npm - ~/.cache @@ -29,7 +29,7 @@ jobs: name: browserstack command: | if [ -z "$CIRCLE_PR_NUMBER" ]; then - npx start-server-and-test 'start:kitchen-sink-local' http://localhost:3000 'browserstack-cypress run --build-name $CIRCLE_BRANCH --no-wrap' + npx start-server-and-test 'start:example-local' http://localhost:3000 'browserstack-cypress run --build-name $CIRCLE_BRANCH --no-wrap' fi - store_test_results: path: test-results @@ -44,6 +44,7 @@ workflows: context: - browserstack-env - ship/node-publish: + publish-command: npm publish --tag beta requires: - build context: @@ -52,4 +53,4 @@ workflows: filters: branches: only: - - main + - beta diff --git a/.gitignore b/.gitignore index 798515a77..3831b61df 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,6 @@ dist/ # Mac OSX files .DS_Store + +# IDE +.idea diff --git a/CHANGELOG.md b/CHANGELOG.md index 07d6b90b9..52146f5d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,18 +9,47 @@ **Fixed** - Clean up erroneous cookies when chunk size decreases [\#1300](https://github.com/auth0/nextjs-auth0/pull/1300) ([adamjmcgrath](https://github.com/adamjmcgrath)) +## [v3.0.0-beta.3](https://github.com/auth0/nextjs-auth0/tree/v3.0.0-beta.3) (2023-06-28) +[Full Changelog](https://github.com/auth0/nextjs-auth0/compare/v3.0.0-beta.2...v3.0.0-beta.3) + +**Added** +- [SDK-4319] Add support for Edge runtime [\#1269](https://github.com/auth0/nextjs-auth0/pull/1269) ([adamjmcgrath](https://github.com/adamjmcgrath)) +- [SDK-4318] Enable responses from custom middleware [\#1265](https://github.com/auth0/nextjs-auth0/pull/1265) ([adamjmcgrath](https://github.com/adamjmcgrath)) + ## [v2.6.3](https://github.com/auth0/nextjs-auth0/tree/v2.6.3) (2023-06-26) [Full Changelog](https://github.com/auth0/nextjs-auth0/compare/v2.6.2...v2.6.3) **Fixed** - Fix for setting custom cookies in `withMiddlewareAuthRequired` [\#1263](https://github.com/auth0/nextjs-auth0/pull/1263) ([adamjmcgrath](https://github.com/adamjmcgrath)) + +## [v3.0.0-beta.2](https://github.com/auth0/nextjs-auth0/tree/v3.0.0-beta.2) (2023-06-16) +[Full Changelog](https://github.com/auth0/nextjs-auth0/compare/v3.0.0-beta.1...v3.0.0-beta.2) + +**Fixed** +- Fix issue where api wrapper was overwriting session update in api [\#1255](https://github.com/auth0/nextjs-auth0/pull/1255) ([adamjmcgrath](https://github.com/adamjmcgrath)) + +## [v3.0.0-beta.1](https://github.com/auth0/nextjs-auth0/tree/v3.0.0-beta.1) (2023-06-13) +[Full Changelog](https://github.com/auth0/nextjs-auth0/compare/v3.0.0-beta.0...v3.0.0-beta.1) + +**Fixed** +- Fix request check in node 16 [\#1250](https://github.com/auth0/nextjs-auth0/pull/1250) ([adamjmcgrath](https://github.com/adamjmcgrath)) + ## [v2.6.2](https://github.com/auth0/nextjs-auth0/tree/v2.6.2) (2023-06-09) [Full Changelog](https://github.com/auth0/nextjs-auth0/compare/v2.6.1...v2.6.2) **Fixed** - Fix for handling chunked cookies in edge runtime [\#1236](https://github.com/auth0/nextjs-auth0/pull/1236) ([adamjmcgrath](https://github.com/adamjmcgrath)) +## [v3.0.0-beta.0](https://github.com/auth0/nextjs-auth0/tree/v3.0.0-beta.0) (2023-06-08) +[Full Changelog](https://github.com/auth0/nextjs-auth0/compare/v2.6.1...v3.0.0-beta.0) + +**Added** +- Support for the App Router. + +**⚠️ BREAKING CHANGES** +- Support for EOL Node versions 12 and 14 has been removed. See the [V3_MIGRATION_GUIDE.md](./V3_MIGRATION_GUIDE.md) for more details. + ## [v2.6.1](https://github.com/auth0/nextjs-auth0/tree/v2.6.1) (2023-06-06) [Full Changelog](https://github.com/auth0/nextjs-auth0/compare/v2.6.0...v2.6.1) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40b23d4b2..c24337e9f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,27 +15,14 @@ Please read [Auth0's contribution guidelines](https://github.com/auth0/open-sour - `npm run build:test`: Do this once to build the test harness for the tests - `npm test`: Run the unit tests - `npm run test:watch`: Run the unit tests and watch for changes -- `npm run install:examples`: Install the examples -- Setup the examples https://github.com/auth0/nextjs-auth0/tree/main/examples -- `npm run start:basic`: Run the basic example -- `npm run start:kitchen-sink`: Run the kitchen sink example -- `npm run test:kitchen-sink`: Run the E2E tests (you will need to populate the `CYPRESS_USER_EMAIL` and `CYPRESS_USER_PASSWORD` env vars) -- `npm run test:kitchen-sink:watch`: Run the E2E tests and watch for changes +- `npm run install:example`: Install the examples +- Setup the examples https://github.com/auth0/nextjs-auth0/tree/main/example-app +- `npm run start:example`: Run the example +- `npm run test:example`: Run the E2E tests (you will need to populate the `CYPRESS_USER_EMAIL` and `CYPRESS_USER_PASSWORD` env vars) +- `npm run test:example:watch`: Run the E2E tests and watch for changes ## Running examples against a mock openid provider -Your env vars in `/examples/kitchen-sink-example/.env.local` should look like - -```bash -AUTH0_SECRET=#ANY LONG RANDOM VALUE -AUTH0_ISSUER_BASE_URL=http://localhost:3000/oidc -AUTH0_BASE_URL=http://localhost:3000 -AUTH0_CLIENT_ID=testing -AUTH0_CLIENT_SECRET=testing -``` - -Then run one of the commands: - -- `start:kitchen-sink-local`: "npm run dev:local --prefix=examples/kitchen-sink-example", -- `test:kitchen-sink-local`: Run the E2E tests against a mock openid provider -- `test:kitchen-sink-local:watch`: Run the E2E tests against a mock openid provider and watch for changes +- `start:example-local`: Run the example app with a mock openid provider +- `test:example-local`: Run the E2E tests with a mock openid provider +- `test:example-local:watch`: Run the E2E tests with a mock openid provider and watch for changes diff --git a/EXAMPLES.md b/EXAMPLES.md index 8eaa14d69..f211b8236 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -1,6 +1,5 @@ # Examples -- [Basic Setup](#basic-setup) - [Create your own instance of the SDK](#create-your-own-instance-of-the-sdk) - [Customize handlers behavior](#customize-handlers-behavior) - [Use custom auth urls](#use-custom-auth-urls) @@ -13,76 +12,9 @@ - [Use with Base Path and Internationalized Routing](#use-with-base-path-and-internationalized-routing) - [Use a custom session store](#use-a-custom-session-store) -All examples can be seen running in the [Kitchen Sink example app](./examples/kitchen-sink-example). +See also the [example app](./example-app). -## Basic Setup - -Configure the required options in an `.env.local` file in the root of your application: - -```sh -AUTH0_SECRET='LONG_RANDOM_VALUE' -AUTH0_BASE_URL='http://localhost:3000' -AUTH0_ISSUER_BASE_URL='https://your-tenant.auth0.com' -AUTH0_CLIENT_ID='CLIENT_ID' -AUTH0_CLIENT_SECRET='CLIENT_SECRET' -``` - -Create a [dynamic API route handler](https://nextjs.org/docs/api-routes/dynamic-api-routes) at `/pages/api/auth/[auth0].js`. - -```js -import { handleAuth } from '@auth0/nextjs-auth0'; - -export default handleAuth(); -``` - -This will create the following urls: `/api/auth/login`, `/api/auth/callback`, `/api/auth/logout` and `/api/auth/me`. - -Wrap your `pages/_app.jsx` component in the `UserProvider` component. - -```jsx -// pages/_app.jsx -import React from 'react'; -import { UserProvider } from '@auth0/nextjs-auth0/client'; - -export default function App({ Component, pageProps }) { - // You can optionally pass the `user` prop from pages that require server-side - // rendering to prepopulate the `useUser` hook. - const { user } = pageProps; - - return ( - - - - ); -} -``` - -Check the user's authentication state and log them in or out from the front end using the `useUser` hook. - -```jsx -// pages/index.jsx -import { useUser } from '@auth0/nextjs-auth0/client'; - -export default () => { - const { user, error, isLoading } = useUser(); - - if (isLoading) return
Loading...
; - if (error) return
{error.message}
; - - if (user) { - return ( -
- Welcome {user.name}! Logout -
- ); - } - return Login; -}; -``` - -Have a look at the `basic-example` app [./examples/basic-example](./examples/basic-example). - -## Create your own instance of the SDK +### Create your own instance of the SDK When you use the named exports, the SDK creates an instance of the SDK for you and configures it with the provided environment variables. @@ -155,7 +87,7 @@ export default auth0.handleAuth({ }); ``` -## Customize handlers behavior +### Customize handlers behavior Pass custom parameters to the auth handlers or add your own logging and error handling. @@ -191,7 +123,7 @@ export default handleAuth({ }); ``` -## Use custom auth urls +### Use custom auth urls Instead of (or in addition to) creating `/pages/api/auth/[auth0].js` to handle all requests, you can create them individually at different urls. @@ -217,7 +149,9 @@ export default () => Login; > Note: If you customize the login url you will need to set the environment variable `NEXT_PUBLIC_AUTH0_LOGIN` to this custom value for `withPageAuthRequired` to work correctly. And if you customize the profile url, you will need to set the `NEXT_PUBLIC_AUTH0_PROFILE` environment variable to this custom value for the `useUser` hook to work properly. -## Protecting a Server-Side Rendered (SSR) Page +### Protecting a Server-Side Rendered (SSR) Page + +#### Page Router Requests to `/pages/profile` without a valid session cookie will be redirected to the login page. @@ -234,9 +168,25 @@ export default function Profile({ user }) { export const getServerSideProps = withPageAuthRequired(); ``` -See a running example of an [SSR protected page](./examples/kitchen-sink-example/pages/profile-ssr.tsx) in the kitchen-sink example app or refer to the full list of configuration options for `withPageAuthRequired` [here](https://auth0.github.io/nextjs-auth0/modules/helpers_with_page_auth_required.html#withpageauthrequiredoptions). +See a running example of an [SSR protected page](./example-app/pages/page-router/profile-ssr.tsx) in the example app or refer to the full list of configuration options for `withPageAuthRequired` [here](https://auth0.github.io/nextjs-auth0/modules/helpers_with_page_auth_required.html#withpageauthrequiredoptions). -## Protecting a Client-Side Rendered (CSR) Page +#### App Router + +Requests to `/pages/profile` without a valid session cookie will be redirected to the login page. + +```jsx +// app/profile/page.js +import { withPageAuthRequired } from '@auth0/nextjs-auth0'; + +export default withPageAuthRequired(function Profile({ user }) { + return
Hello {user.name}
; +}, { returnTo: '/profile' }) +// You need to provide a `returnTo` since Server Components aren't aware of the page's URL +``` + +See a running example of a [protected server component page](./example-app/app/profile/page.tsx) in the example app or more info [in the docs](./src/helpers/with-page-auth-required.ts#129). + +### Protecting a Client-Side Rendered (CSR) Page Requests to `/pages/profile` without a valid session cookie will be redirected to the login page. @@ -249,11 +199,13 @@ export default withPageAuthRequired(function Profile({ user }) { }); ``` -See a running example of a [CSR protected page](./examples/kitchen-sink-example/pages/profile.tsx) in the kitchen-sink example app. +See a running example of a [CSR protected page](./example-app/pages/profile.tsx) in the example app. + +### Protect an API Route -## Protect an API Route +#### Page Router -Requests to `/pages/api/protected` without a valid session cookie will fail with `401`. +Requests to `/api/protected` without a valid session cookie will fail with `401`. ```js // pages/api/protected.js @@ -285,10 +237,49 @@ export default withPageAuthRequired(function Products() { }); ``` -See a running example in the kitchen-sink example app, the [protected API route](./examples/kitchen-sink-example/pages/api/shows.ts) and -the [frontend code to access the protected API](./examples/kitchen-sink-example/pages/shows.tsx). +See a running example in the example app, the [protected API route](./example-app/pages/api/page-router-profile.ts) and +the [frontend code to access the protected API](./example-app/pages/page-router/profile-api.tsx). + +#### App Router -## Protecting pages with Middleware +Requests to `/api/protected` without a valid session cookie will fail with `401`. + +```js +// app/api/protected/route.js +import { withApiAuthRequired, getSession } from '@auth0/nextjs-auth0'; + +export default withApiAuthRequired(async function myApiRoute(req) { + const res = new NextResponse(); + const { user } = await getSession(req, res); + return NextResponse.json({ protected: 'My Secret', id: user.sub }, res); +}); +``` + +Then you can access your API from the frontend with a valid session cookie. + +```jsx +// app/products/page.jsx +'use client' +import useSWR from 'swr'; +import { withPageAuthRequired } from '@auth0/nextjs-auth0/client'; + +const fetcher = async (uri) => { + const response = await fetch(uri); + return response.json(); +}; + +export default withPageAuthRequired(function Products() { + const { data, error } = useSWR('/api/protected', fetcher); + if (error) return
oops... {error.message}
; + if (data === undefined) return
Loading...
; + return
{data.protected}
; +}); +``` + +See a running example in the example app, the [protected API route](./example-app/app/api/profile/route.ts) and +the [frontend code to access the protected API](./example-app/app/profile-api/page.tsx). + +### Protecting pages with Middleware Protect your pages with Next.js Middleware. @@ -348,7 +339,7 @@ export default auth0.withMiddlewareAuthRequired(async function middleware(req) { }); ``` -## Access an External API from an API Route +### Access an External API from an API Route Get an access token by providing your API's audience and scopes. You can pass them directly to the `handlelogin` method, or use environment variables instead. @@ -390,8 +381,6 @@ export default withApiAuthRequired(async function products(req, res) { }); ``` -See a running example of the [API route acting as a proxy to an External API](./examples/kitchen-sink-example/pages/api/shows.ts) in the kitchen-sink example app. - ### Getting a Refresh Token - Include the `offline_access` scope your configuration (or `AUTH0_SCOPE`) @@ -419,7 +408,7 @@ Users can then sign up using the signup handler. Sign up ``` -## Use with Base Path and Internationalized Routing +### Use with Base Path and Internationalized Routing With Next.js you can deploy a Next.js application under a sub-path of a domain using [Base Path](https://nextjs.org/docs/api-reference/next.config.js/basepath) and serve internationalized (i18n) routes using [Internationalized Routing](https://nextjs.org/docs/advanced-features/i18n-routing). @@ -470,7 +459,7 @@ export const getServerSideProps = (ctx) => { }; ``` -## Use a custom session store +### Use a custom session store You need to create your own instance of the SDK in code, so you can pass an instance of your session store to the SDK's configuration. diff --git a/README.md b/README.md index d43185e15..fbe09a379 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The Auth0 Next.js SDK is a library for implementing user authentication in Next. - [Security](https://github.com/auth0/nextjs-auth0/blob/main/SECURITY.md) - Some important security notices that you should check. - [Architecture](https://github.com/auth0/nextjs-auth0/blob/main/ARCHITECTURE.md) - Architectural overview of the SDK. - [Testing](https://github.com/auth0/nextjs-auth0/blob/main/TESTING.md) - Some help with testing your nextjs-auth0 application. -- [Deploying](https://github.com/auth0/nextjs-auth0/blob/main/examples/README.md) - How we deploy our example app to Vercel. +- [Deploying](https://github.com/auth0/nextjs-auth0/blob/main/example-app/README.md) - How we deploy our example app to Vercel. - [Docs Site](https://auth0.com/docs) - explore our docs site and learn more about Auth0. ## Getting Started @@ -31,10 +31,7 @@ Using [npm](https://npmjs.org): npm install @auth0/nextjs-auth0 ``` -This library supports the following tooling versions: - -- Node.js: 12 LTS and newer LTS releases are supported. -- Next.js: `>=10` +This library requires Node.js 16 LTS and newer LTS versions. ### Auth0 Configuration @@ -83,12 +80,22 @@ You can see a full list of Auth0 configuration options in the ["Configuration pr > For more details about loading environment variables in Next.js, visit the ["Environment Variables"](https://nextjs.org/docs/basic-features/environment-variables) document. -#### Add the Dynamic API Route +Add `handleAuth()` to your app, which creates the following route handlers under the hood that perform different parts of the authentication flow: -Go to your Next.js application and create a [catch-all, dynamic API route handler](https://nextjs.org/docs/api-routes/dynamic-api-routes#optional-catch-all-api-routes) under the `/pages/api` directory: +- `/api/auth/login`: Your Next.js application redirects users to your identity provider for them to log in (you can optionally pass a `returnTo` parameter to return to a custom relative URL after login, for example `/api/auth/login?returnTo=/profile`). +- `/api/auth/callback`: Your identity provider redirects users to this route after they successfully log in. +- `/api/auth/logout`: Your Next.js application logs out the user. +- `/api/auth/me`: You can fetch user profile information in JSON format. -- Create an `auth` directory under the `/pages/api/` directory. +> Note: `handleAuth` requires Node.js and so will not work on Cloudflare Workers or Vercel Edge Runtime. + +#### Page Router + +##### Add the Dynamic API Route +Create a [catch-all, dynamic API route handler](https://nextjs.org/docs/api-routes/dynamic-api-routes#optional-catch-all-api-routes) under the `/pages/api` directory: + +- Create an `auth` directory under the `/pages/api/` directory. - Create a `[auth0].js` file under the newly created `auth` directory. The path to your dynamic API route file would be `/pages/api/auth/[auth0].js`. Populate that file as follows: @@ -99,16 +106,7 @@ import { handleAuth } from '@auth0/nextjs-auth0'; export default handleAuth(); ``` -Executing `handleAuth()` creates the following route handlers under the hood that perform different parts of the authentication flow: - -- `/api/auth/login`: Your Next.js application redirects users to your identity provider for them to log in (you can optionally pass a `returnTo` parameter to return to a custom relative URL after login, for example `/api/auth/login?returnTo=/profile`). -- `/api/auth/callback`: Your identity provider redirects users to this route after they successfully log in. -- `/api/auth/logout`: Your Next.js application logs out the user. -- `/api/auth/me`: You can fetch user profile information in JSON format. - -> Note: `handleAuth` requires Node.js and so will not work on Cloudflare Workers or Vercel Edge Runtime. - -#### Add the UserProvider to Custom App +##### Add the UserProvider to Custom App Wrap your `pages/_app.js` component with the `UserProvider` component: @@ -126,7 +124,7 @@ export default function App({ Component, pageProps }) { } ``` -#### Consume Authentication +##### Consume Authentication You can now determine if a user is authenticated by checking that the `user` object returned by the `useUser()` hook is defined. You can also log in or log out your users from the frontend layer of your Next.js application by redirecting them to the appropriate automatically-generated route: @@ -154,16 +152,100 @@ export default function Index() { > Next linting rules might suggest using the `Link` component instead of an anchor tag. The `Link` component is meant to perform [client-side transitions between pages](https://nextjs.org/docs/api-reference/next/link). As the links point to an API route and not to a page, you should keep them as anchor tags. -There are two additional ways to check for an authenticated user; one for Next.js pages using [withPageAuthRequired](https://auth0.github.io/nextjs-auth0/modules/helpers_with_page_auth_required.html#withpageauthrequired) and one for Next.js API routes using [withApiAuthRequired](https://auth0.github.io/nextjs-auth0/modules/helpers_with_api_auth_required.html#withapiauthrequired). +#### App Router + +> Important: You should understand the [limitations of the App Directory](#important-limitations-of-the-app-directory) before proceeding. + +##### Add the Dynamic API Route + +Create a [catch-all, dynamic API route handler](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) under the `/app/api` directory (strictly speaking you do not need to put API routes under `/api` but we maintain the convention for simplicity): + +- Create an `api` directory under the `/app/` directory. +- Create an `auth` directory under the newly created `/app/api/` directory. +- Create a `[auth0]` directory under the newly created `auth` directory. +- Create a `route.js` file under the newly created `[auth0]` directory. + +The path to your dynamic API route file will be `/app/api/auth/[auth0]/route.js`. Populate that file as follows: + +```js +import { handleAuth } from '@auth0/nextjs-auth0'; + +export const GET = handleAuth(); +``` + +##### Add the `UserProvider` to your layout + +Wrap your `app/layout.js` component with the `UserProvider` component: + +```jsx +// app/layout.js +import React from 'react'; +import { UserProvider } from '@auth0/nextjs-auth0/client'; + +export default function App({ children }) { + return ( + + {children} + + ); +} +``` + +##### Consume Authentication + +You can now determine if a user is authenticated by checking that the `user` object returned by the `useUser()` hook is defined. You can also log in or log out your users from the frontend layer of your Next.js application by redirecting them to the appropriate automatically-generated route: + +```jsx +// pages/index.js +'use client'; +import { useUser } from '@auth0/nextjs-auth0/client'; + +export default function Index() { + const { user, error, isLoading } = useUser(); + + if (isLoading) return
Loading...
; + if (error) return
{error.message}
; + + if (user) { + return ( +
+ Welcome {user.name}! Logout +
+ ); + } + + return Login; +} +``` + +> Next linting rules might suggest using the `Link` component instead of an anchor tag. The `Link` component is meant to perform [client-side transitions between pages](https://nextjs.org/docs/api-reference/next/link). As the links point to an API route and not to a page, you should keep them as anchor tags. + +##### Important: Limitations of the App Directory + +At the time of writing, Server Components in the App Directory (including Pages and Layouts) _cannot_ write to a cookie. + +If you rely on Server Components to read and update your session from a Server Component you should be aware of the following: + +- If you have a rolling session (the default for this SDK), it will not be updated when the user visits your site. So their session expiration may revert to its absolute duration (7 days by default). +- If you refresh the access token, the new access token will not be persisted in the session. So subsequent attempts to get an access token will always result in refreshing the expired access token in the session. +- If you make any other updates to the session, they will not be persisted between requests. + +> The cookie is updated from [middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware) and [route handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers). For other comprehensive examples, see the [EXAMPLES.md](https://github.com/auth0/nextjs-auth0/blob/main/EXAMPLES.md) document. ## API Reference -### Server (for Node.js) +### Server + +#### For Node `import * from @auth0/nextjs-auth0` +#### For Edge runtime + +`import * from @auth0/nextjs-auth0/edge` + - [Configuration Options and Environment variables](https://auth0.github.io/nextjs-auth0/modules/config.html) - [initAuth0](https://auth0.github.io/nextjs-auth0/modules/index.html#initauth0) - [handleAuth](https://auth0.github.io/nextjs-auth0/modules/handlers_auth.html) @@ -176,15 +258,7 @@ For other comprehensive examples, see the [EXAMPLES.md](https://github.com/auth0 - [getSession](https://auth0.github.io/nextjs-auth0/modules/session_get_session.html) - [updateSession](https://auth0.github.io/nextjs-auth0/modules/session_update_session.html) - [getAccessToken](https://auth0.github.io/nextjs-auth0/modules/session_get_access_token.html) - -### Edge (for Middleware and the Edge runtime) - -`import * from @auth0/nextjs-auth0/edge` - -- [Configuration Options and Environment variables](https://auth0.github.io/nextjs-auth0/modules/config.html) -- [initAuth0](https://auth0.github.io/nextjs-auth0/modules/edge.html#initauth0-1) -- [withMiddlewareAuthRequired](https://auth0.github.io/nextjs-auth0/modules/helpers_with_middleware_auth_required.html) -- [getSession](https://auth0.github.io/nextjs-auth0/modules/edge.html#getsession-1) +- [withMiddlewareAuthRequired](https://auth0.github.io/nextjs-auth0/modules/helpers_with_middleware_auth_required.html) (Edge only) ### Client (for the Browser) @@ -299,7 +373,7 @@ For end to end tests, have a look at how we use a [mock OIDC Provider](./scripts # Deploying -For deploying, have a look at [how we deploy our example app to Vercel](./examples/README.md). +For deploying, have a look at [how we deploy our example app to Vercel](./example-app/README.md). ## Contributing diff --git a/V3_MIGRATION_GUIDE.md b/V3_MIGRATION_GUIDE.md new file mode 100644 index 000000000..be7c0e520 --- /dev/null +++ b/V3_MIGRATION_GUIDE.md @@ -0,0 +1,57 @@ +# V3 Migration Guide + +Guide to migrating from `2.x` to `3.x` + +## Node 16 or newer is required + +Node 16 LTS and newer LTS releases are supported. + +## TypeScript changes + +All the server functions of this SDK now support the App Router in addition to the Page Router. + +As a result of this, the type signatures of these functions have been overloaded to include the App Router signatures. + +So in some places, TypeScript may require help inferring the types of request and response. e.g. + +### Before + +```ts +import { withApiAuthRequired } from '@auth0/nextjs-auth0' + +export default withApiAuthRequired(async function handler(req, res) { + res.status(200).json({}); +}); +``` + +### After + +```ts +import { NextApiRequest, NextApiResponse } from 'next'; +import { withApiAuthRequired } from '@auth0/nextjs-auth0' + +export default withApiAuthRequired(async function handler(req: NextApiRequest, res: NextApiResponse) { + res.status(200).json({}); +}); +``` + + +## The `/401` handler has been removed + +As of Next.js 13.1, you can now return responses from Middleware so the Unauthorized handler has been removed in favour of an Unauthorized response. + +If you want to protect an API with `withMiddlewareAuthRequired` you will need a minimum of Next.js 13.1, or add the 401 back yourself. e.g. + +```ts +import { handleAuth } from '@auth0/nextjs-auth0'; + +export default handleAuth({ + '401'(_req, res) { + res.status(401).json({ + error: 'not_authenticated', + description: 'The user does not have an active session or is not authenticated' + }); + } +}); + +``` diff --git a/__mocks__/lodash/isPlainObject.js b/__mocks__/lodash/isPlainObject.js new file mode 100644 index 000000000..ac4fb3ffb --- /dev/null +++ b/__mocks__/lodash/isPlainObject.js @@ -0,0 +1,12 @@ +'use strict'; + +// From https://github.com/sindresorhus/is-plain-obj/blob/v2.1.0/index.js +// As this one is more permissive and works on Edge runtime +module.exports = (value) => { + if (Object.prototype.toString.call(value) !== '[object Object]') { + return false; + } + + const prototype = Object.getPrototypeOf(value); + return prototype === null || prototype === Object.prototype; +}; diff --git a/cypress/e2e/smoke.cy.ts b/cypress/e2e/smoke.cy.ts index 427140ca6..7eb5d5492 100644 --- a/cypress/e2e/smoke.cy.ts +++ b/cypress/e2e/smoke.cy.ts @@ -22,46 +22,140 @@ const loginToNodeOidc = () => { const login = useAuth0 ? loginToAuth0 : loginToNodeOidc; describe('smoke tests', () => { - it('should do basic login and show user', () => { - cy.visit('/'); - cy.get('[data-testid=login]').click(); - login(); - cy.url().should('eq', `${Cypress.config().baseUrl}/`); - cy.get('[data-testid=profile]').contains(EMAIL); - cy.get('[data-testid=logout]').should('exist'); - }); + describe('page router', () => { + it('should do basic login and show user', () => { + cy.visit('/page-router'); + cy.get('[data-testid=login]').click(); + login(); + cy.url().should('eq', `${Cypress.config().baseUrl}/page-router`); + cy.get('[data-testid=logout]').should('exist'); + }); - it('should protect a client-side rendered page', () => { - cy.visit('/profile'); - login(); - cy.url().should('eq', `${Cypress.config().baseUrl}/profile`); - cy.get('[data-testid=profile]').contains(EMAIL); - }); + it('should protect a client-side rendered page', () => { + cy.visit('/page-router/profile-csr'); + login(); + cy.url().should('eq', `${Cypress.config().baseUrl}/page-router/profile-csr`); + cy.get('[data-testid=profile]').contains(EMAIL); + }); + + it('should protect a server-side rendered page', () => { + cy.visit('/page-router/profile-ssr'); + login(); + + cy.url().should('eq', `${Cypress.config().baseUrl}/page-router/profile-ssr`); + cy.get('[data-testid=profile]').contains(EMAIL); + }); + + it('should protect a page with middleware', () => { + cy.visit('/page-router/profile-middleware'); + login(); + cy.url().should('eq', `${Cypress.config().baseUrl}/page-router/profile-middleware`); + cy.get('[data-testid=profile]').contains(EMAIL); + }); + + it('should logout and return to the index page', () => { + cy.visit('/page-router/profile-csr'); + login(); + cy.url().should('eq', `${Cypress.config().baseUrl}/page-router/profile-csr`); + cy.get('[data-testid=logout]').click(); + if (!useAuth0) { + cy.get('[name=logout]').click(); + } + cy.url().should('eq', `${Cypress.config().baseUrl}/page-router`); + cy.get('[data-testid=login]').should('exist'); + }); + + it('should protect an api', () => { + cy.request({ url: '/api/page-router-profile', failOnStatusCode: false }).as('unauthorized'); + + cy.get('@unauthorized').should((response: any) => { + expect(response.status).to.eq(401); + expect(response.body.error).to.eq('not_authenticated'); + }); + }); - it('should protect a server-side rendered page', () => { - cy.visit('/profile-ssr'); - login(); + it('should access an api', () => { + cy.visit('/page-router/profile-api'); + login(); - cy.url().should('eq', `${Cypress.config().baseUrl}/profile-ssr`); - cy.get('[data-testid=profile]').contains(EMAIL); + cy.url().should('eq', `${Cypress.config().baseUrl}/page-router/profile-api`); + cy.get('[data-testid=profile-api]').contains(EMAIL); + }); }); + describe('app router', () => { + it('should render an app route', () => { + cy.visit('/profile'); + login(); + cy.url().should('eq', `${Cypress.config().baseUrl}/profile`); + cy.get('[data-testid=server-component]').contains(EMAIL); + cy.get('[data-testid=client-component]').contains(EMAIL); + }); - it('should protect a page with middleware', () => { - cy.visit('/profile-mw'); - login(); - cy.url().should('eq', `${Cypress.config().baseUrl}/profile-mw`); - cy.get('[data-testid=profile]').contains(EMAIL); + it('should protect an api', () => { + cy.request({ url: '/api/profile', failOnStatusCode: false }).as('unauthorized'); + + cy.get('@unauthorized').should((response: any) => { + expect(response.status).to.eq(401); + expect(response.body.error).to.eq('not_authenticated'); + }); + }); + + it('should access an api', () => { + cy.visit('/profile-api'); + login(); + + cy.url().should('eq', `${Cypress.config().baseUrl}/profile-api`); + cy.get('[data-testid=profile-api]').contains(EMAIL); + }); + + it('should logout and return to the index page', () => { + cy.visit('/profile'); + login(); + cy.url().should('eq', `${Cypress.config().baseUrl}/profile`); + cy.get('[data-testid=logout]').click(); + if (!useAuth0) { + cy.get('[name=logout]').click(); + } + cy.url().should('eq', `${Cypress.config().baseUrl}/`); + cy.get('[data-testid=login]').should('exist'); + }); }); + describe('app router (edge)', () => { + it('should render an app route', () => { + cy.visit('/edge-profile'); + login(); + cy.url().should('eq', `${Cypress.config().baseUrl}/edge-profile`); + cy.get('[data-testid=profile]').contains(EMAIL); + cy.get('[data-testid=logout-edge]').click(); + }); + + it('should protect an api', () => { + cy.request({ url: '/api/edge-profile', failOnStatusCode: false }).as('unauthorized-edge'); + + cy.get('@unauthorized-edge').should((response: any) => { + expect(response.status).to.eq(401); + expect(response.body.error).to.eq('not_authenticated'); + }); + }); + + it('should access an api', () => { + cy.visit('/edge-profile-api'); + login(); + + cy.url().should('eq', `${Cypress.config().baseUrl}/edge-profile-api`); + cy.get('[data-testid=profile-api]').contains(EMAIL); + }); - it('should logout and return to the index page', () => { - cy.visit('/profile'); - login(); - cy.url().should('eq', `${Cypress.config().baseUrl}/profile`); - cy.get('[data-testid=logout]').click(); - if (!useAuth0) { - cy.get('[name=logout]').click(); - } - cy.url().should('eq', `${Cypress.config().baseUrl}/`); - cy.get('[data-testid=login]').should('exist'); + it('should logout and return to the index page', () => { + cy.visit('/edge-profile'); + login(); + cy.url().should('eq', `${Cypress.config().baseUrl}/edge-profile`); + cy.get('[data-testid=logout-edge]').click(); + if (!useAuth0) { + cy.get('[name=logout]').click(); + } + cy.url().should('eq', `${Cypress.config().baseUrl}/`); + cy.get('[data-testid=login-edge]').should('exist'); + }); }); }); diff --git a/examples/kitchen-sink-example/.env.local.template b/example-app/.env.local.template similarity index 100% rename from examples/kitchen-sink-example/.env.local.template rename to example-app/.env.local.template diff --git a/examples/kitchen-sink-example/.env.production b/example-app/.env.production similarity index 100% rename from examples/kitchen-sink-example/.env.production rename to example-app/.env.production diff --git a/example-app/.eslintrc.json b/example-app/.eslintrc.json new file mode 100644 index 000000000..75d1cf061 --- /dev/null +++ b/example-app/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "extends": "next/core-web-vitals", + "rules": { + "@next/next/no-html-link-for-pages": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" + } +} diff --git a/example-app/.gitignore b/example-app/.gitignore new file mode 100644 index 000000000..8f322f0d8 --- /dev/null +++ b/example-app/.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/basic-example/.npmrc b/example-app/.npmrc similarity index 100% rename from examples/basic-example/.npmrc rename to example-app/.npmrc diff --git a/examples/README.md b/example-app/README.md similarity index 64% rename from examples/README.md rename to example-app/README.md index 50dfee0e4..13b7e1020 100644 --- a/examples/README.md +++ b/example-app/README.md @@ -1,8 +1,18 @@ -# Next.js Auth0 Examples - -In this folder we'll be showing off different examples on how to use the [@auth0/nextjs-auth0](https://www.npmjs.com/package/@auth0/nextjs-auth0) package in your Next.js applications. - -## Configuration +# Next.js Auth0 Example App + +In this folder we'll be showing off how to use the [@auth0/nextjs-auth0](https://www.npmjs.com/package/@auth0/nextjs-auth0) package in your Next.js applications. + +- [Local Development](#local-development) + * [Configuring Auth0](#configuring-auth0) + * [Environment Variables](#environment-variables) +- [Hosting on Vercel](#hosting-on-vercel) + * [Configuring Auth0](#configuring-auth0-1) + + [Wildcards](#wildcards) + * [Configuring Vercel](#configuring-vercel) + * [Environment Variables](#environment-variables-1) + + [Assigning the AUTH0_BASE_URL](#assigning-the-auth0-base-url) + - [Preview deployments](#preview-deployments) + - [Production deployments (or other environments with fixed urls)](#production-deployments--or-other-environments-with-fixed-urls-) ### Local Development @@ -20,18 +30,18 @@ Go to the [Auth0 dashboard](https://manage.auth0.com/) and create a new applicat For local development you'll just want to create a `.env.local` file with the necessary settings: ``` -AUTH0_SECRET=viloxyf_z2GW6K4CT-KQD_MoLEA2wqv5jWuq4Jd0P7ymgG5GJGMpvMneXZzhK3sL (at least 32 characters, used to encrypt the cookie) -AUTH0_ISSUER_BASE_URL=https://YOUR_AUTH0_DOMAIN +AUTH0_SECRET={A SECRET UNIQUE STRING} (at least 32 characters, used to encrypt the cookie) +AUTH0_ISSUER_BASE_URL=https://{YOUR_AUTH0_DOMAIN} AUTH0_BASE_URL=http://localhost:3000/ -AUTH0_CLIENT_ID=YOUR_AUTH0_CLIENT_ID -AUTH0_CLIENT_SECRET=YOUR_AUTH0_CLIENT_SECRET +AUTH0_CLIENT_ID={YOUR_AUTH0_CLIENT_ID} +AUTH0_CLIENT_SECRET={YOUR_AUTH0_CLIENT_SECRET} AUTH0_SCOPE=openid profile read:shows -AUTH0_AUDIENCE=YOUR_AUTH0_API_IDENTIFIER +AUTH0_AUDIENCE={YOUR_AUTH0_API_IDENTIFIER} ``` ### Hosting on Vercel -The kitchen-sink example application is hosted on Vercel, including preview deployments to make Pull Request reviewing a bit easier in terms of verifying the functionalities in the example application. +The example application is hosted on Vercel, including preview deployments to make Pull Request reviewing a bit easier in terms of verifying the functionalities in the example application. #### Configuring Auth0 @@ -54,41 +64,26 @@ By default, Vercel uses the `vercel.app` domain for all of your environments. Us #### Configuring Vercel -If you do not have a vercel account yet, move over to https://vercel.com/ to sign up for one. +If you do not have a Vercel account, you can sign up for one at https://vercel.com/. Once logged in to your account, you can create a new project and import a Git repository. Vercel should automatically select the `Next.js` Framework Preset. -Because our deployment is a bit different from a standard Next.js repository, we will need to override the `Build and Output settings`: +Because this app's deployment is a bit different from a standard Next.js repository, we override the `Build and Output settings` command: - Build Command: `npm run build:vercel` -- Output Directory: `examples/kitchen-sink-example/.next` - -The reason why we need to overrride these settings is because the Next.js app we want to build does not sit in the root of the repository. The example application is also dependent on the Next.js SDK, so we will need to ensure that Vercel executes the following commands when running `npm run build:vercel`: - -- Build the SDK: `npm run build` -- Install the dependencies for the sample application: `npm i --prefix=examples/kitchen-sink-example` -- Build the sample application: `npm run build --prefix=examples/kitchen-sink-example` - -As Vercel wants one single build command, we make use of the `build:vercel` npm script to run all of the above: - -``` -"build:vercel": "npm run install:examples && npm run build && npm run build:examples", -``` - -**Note**: Vercel runs `npm install` in the root of the repository by default, so we do not need to worry about that. +- Output Directory: `example-app/.next` #### Environment Variables Configure the following environment variables when importing your project or in "Settings > Environment Variables": -| Name | Value | -| --------------------- | --------------------------------------------------------------------------------------------------------------------- | -| AUTH0_SECRET | viloxyf_z2GW6K4CT-KQD_MoLEA2wqv5jWuq4Jd0P7ymgG5GJGMpvMneXZzhK3sL (at least 32 characters, used to encrypt the cookie) | -| AUTH0_ISSUER_BASE_URL | https://YOUR_AUTH0_DOMAIN | -| AUTH0_CLIENT_ID | YOUR_AUTH0_CLIENT_ID | -| AUTH0_CLIENT_SECRET | YOUR_AUTH0_CLIENT_SECRET | -| AUTH0_AUDIENCE | YOUR_AUTH0_API_IDENTIFIER | -| AUTH0_SCOPE | openid profile read:shows | +| Name | Value | +| --------------------- |------------------------------------------------------------------------------| +| AUTH0_SECRET | {A SECRET UNIQUE STRING} (at least 32 characters, used to encrypt the cookie) | +| AUTH0_ISSUER_BASE_URL | https://{YOUR_AUTH0_DOMAIN} | +| AUTH0_CLIENT_ID | {YOUR_AUTH0_CLIENT_ID} | +| AUTH0_CLIENT_SECRET | {YOUR_AUTH0_CLIENT_SECRET} | +| AUTH0_AUDIENCE | {YOUR_AUTH0_API_IDENTIFIER} | ##### Assigning the AUTH0_BASE_URL @@ -112,7 +107,7 @@ NEXT_PUBLIC_AUTH0_BASE_URL=$VERCEL_URL > Note: Long URLs (> 63 characters) will get truncated by Vercel. See: https://vercel.com/docs/concepts/deployments/generated-urls#truncation -Unlike other `.env` files, You will need to check in `.env.production` so it should **not** contain any secrets. See how we define `.env.production` in the [kitchen-sink example app](./kitchen-sink-example/.env.production). +Unlike other `.env` files, You will need to check in `.env.production` so it should **not** contain any secrets. See how we define `.env.production` in [.env.production](./.env.production). ###### Production deployments (or other environments with fixed urls) diff --git a/example-app/app/api/auth/[auth0]/route.ts b/example-app/app/api/auth/[auth0]/route.ts new file mode 100644 index 000000000..eafedf0d0 --- /dev/null +++ b/example-app/app/api/auth/[auth0]/route.ts @@ -0,0 +1,7 @@ +import { handleAuth } from '@auth0/nextjs-auth0'; + +export const GET = handleAuth({ + onError(req: Request, error: Error) { + console.error(error); + } +}); diff --git a/example-app/app/api/edge-auth/[auth0]/route.ts b/example-app/app/api/edge-auth/[auth0]/route.ts new file mode 100644 index 000000000..3b9dab536 --- /dev/null +++ b/example-app/app/api/edge-auth/[auth0]/route.ts @@ -0,0 +1,17 @@ +import { handleAuth, handleLogin, handleCallback } from '@auth0/nextjs-auth0/edge'; + +const redirectUri = `${process.env.AUTH0_BASE_URL}/api/edge-auth/callback`; + +export const GET = handleAuth({ + login: handleLogin({ + authorizationParams: { redirect_uri: redirectUri } + }), + callback: handleCallback({ redirectUri }), + onError(req: Request, error: Error) { + console.error(error); + } +}); + +export const runtime = 'edge'; +//https://github.com/vercel/next.js/issues/51642 +export const fetchCache = 'force-no-store'; diff --git a/example-app/app/api/edge-profile/route.ts b/example-app/app/api/edge-profile/route.ts new file mode 100644 index 000000000..65b0ee598 --- /dev/null +++ b/example-app/app/api/edge-profile/route.ts @@ -0,0 +1,12 @@ +import { getSession, withApiAuthRequired } from '@auth0/nextjs-auth0/edge'; +import { NextResponse } from 'next/server'; + +const GET = withApiAuthRequired(async () => { + const session = await getSession(); + + return NextResponse.json(session?.user); +}); + +export { GET }; + +export const runtime = 'edge'; diff --git a/example-app/app/api/profile/route.ts b/example-app/app/api/profile/route.ts new file mode 100644 index 000000000..1a349b4f5 --- /dev/null +++ b/example-app/app/api/profile/route.ts @@ -0,0 +1,10 @@ +import { getSession, withApiAuthRequired } from '@auth0/nextjs-auth0'; +import { NextResponse } from 'next/server'; + +const GET = withApiAuthRequired(async () => { + const session = await getSession(); + + return NextResponse.json(session?.user); +}); + +export { GET }; diff --git a/example-app/app/client-component.tsx b/example-app/app/client-component.tsx new file mode 100644 index 000000000..80f5e6b3c --- /dev/null +++ b/example-app/app/client-component.tsx @@ -0,0 +1,11 @@ +'use client'; + +import { useUser } from '@auth0/nextjs-auth0/client'; + +export default function ClientComponent() { + const { user } = useUser(); + if (user) { + return
{JSON.stringify(user, null, 2)}
; + } + return <>; +} diff --git a/example-app/app/edge-profile-api/page.tsx b/example-app/app/edge-profile-api/page.tsx new file mode 100644 index 000000000..c0c5d9596 --- /dev/null +++ b/example-app/app/edge-profile-api/page.tsx @@ -0,0 +1,23 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { withPageAuthRequired } from '@auth0/nextjs-auth0/client'; + +export default withPageAuthRequired(function ProfileApi() { + const [user, setUser] = useState(); + + useEffect(() => { + (async () => { + const res = await fetch(`${window.location.origin}/api/edge-profile`); + setUser(await res.json()); + })(); + }, []); + + return ( +
+

Profile (fetched from API)

+

User

+
{JSON.stringify(user, null, 2)}
+
+ ); +}); diff --git a/example-app/app/edge-profile/page.tsx b/example-app/app/edge-profile/page.tsx new file mode 100644 index 000000000..e3962fe1b --- /dev/null +++ b/example-app/app/edge-profile/page.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { getSession, withPageAuthRequired } from '@auth0/nextjs-auth0/edge'; + +export default withPageAuthRequired( + async function Page() { + const session = await getSession(); + + return ( +
+

Profile

+

Page:

+

Access Token

+
{JSON.stringify({ accessToken: session?.accessToken }, null, 2)}
+

User

+
{JSON.stringify(session?.user, null, 2)}
+
+ ); + }, + { returnTo: '/edge-profile' } +); + +export const runtime = 'edge'; diff --git a/example-app/app/favicon.ico b/example-app/app/favicon.ico new file mode 100644 index 000000000..718d6fea4 Binary files /dev/null and b/example-app/app/favicon.ico differ diff --git a/example-app/app/globals.css b/example-app/app/globals.css new file mode 100644 index 000000000..9920ca83c --- /dev/null +++ b/example-app/app/globals.css @@ -0,0 +1,54 @@ +body { + margin: 0; + color: #333; + font-family: sans-serif; +} +.header { + padding: 0.2rem; + color: #fff; + background-color: #000; +} +.header.secondary { + background-color: #333; +} +nav { + max-width: 62rem; + margin: 1.5rem auto; +} +ul { + display: flex; + list-style: none; + margin-left: 0; + padding-left: 0; +} +li { + margin-right: 1rem; +} +.header.secondary li:nth-last-child(3) { + margin-right: auto; +} +.header a { + color: #fff; + text-decoration: none; +} +.header.home a[href='/'], +.header.page-router a[href$='/page-router'], +.header.profile a[href$='/profile'], +.header.edge-profile a[href$='/edge-profile'], +.header.profile-middleware a[href$='/profile-middleware'], +.header.profile-api a[href$='/profile-api'], +.header.edge-profile-api a[href$='/edge-profile-api'], +a.active { + color: #888; +} +button { + font-size: 1rem; + color: #fff; + cursor: pointer; + border: none; + background: none; +} +.container { + max-width: 62rem; + margin: 1.5rem auto; +} diff --git a/examples/kitchen-sink-example/app/layout.tsx b/example-app/app/layout.tsx similarity index 59% rename from examples/kitchen-sink-example/app/layout.tsx rename to example-app/app/layout.tsx index c7ade2392..05a99a41c 100644 --- a/examples/kitchen-sink-example/app/layout.tsx +++ b/example-app/app/layout.tsx @@ -1,14 +1,20 @@ -import React from 'react'; -import './global.css'; -import Header from '../components/header'; +import './globals.css'; import { UserProvider } from '@auth0/nextjs-auth0/client'; +import Nav from '@/app/nav'; + +export const metadata = { + title: 'Create Next App', + description: 'Generated by create next app' +}; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( -
+
+
{children}
diff --git a/example-app/app/nav.tsx b/example-app/app/nav.tsx new file mode 100644 index 000000000..c7806e83e --- /dev/null +++ b/example-app/app/nav.tsx @@ -0,0 +1,96 @@ +'use client'; + +import React from 'react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { useUser } from '@auth0/nextjs-auth0/client'; + +export default function Nav() { + const { user } = useUser(); + const pathname = usePathname(); + const pageName = pathname?.split('/').pop(); + + return ( + <> +
+ +
+
+ +
+ + ); +} diff --git a/example-app/app/page.tsx b/example-app/app/page.tsx new file mode 100644 index 000000000..1d18233ae --- /dev/null +++ b/example-app/app/page.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +export default function Home() { + return ( +
+

App router directory

+
+ ); +} diff --git a/example-app/app/profile-api/page.tsx b/example-app/app/profile-api/page.tsx new file mode 100644 index 000000000..c0c5d9596 --- /dev/null +++ b/example-app/app/profile-api/page.tsx @@ -0,0 +1,23 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { withPageAuthRequired } from '@auth0/nextjs-auth0/client'; + +export default withPageAuthRequired(function ProfileApi() { + const [user, setUser] = useState(); + + useEffect(() => { + (async () => { + const res = await fetch(`${window.location.origin}/api/edge-profile`); + setUser(await res.json()); + })(); + }, []); + + return ( +
+

Profile (fetched from API)

+

User

+
{JSON.stringify(user, null, 2)}
+
+ ); +}); diff --git a/example-app/app/profile-middleware/page.tsx b/example-app/app/profile-middleware/page.tsx new file mode 100644 index 000000000..1d939d6db --- /dev/null +++ b/example-app/app/profile-middleware/page.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { getSession } from '@auth0/nextjs-auth0'; +import ServerComponent from '@/app/server-component'; +import ClientComponent from '@/app/client-component'; + +export default async function Page() { + const session = await getSession(); + + return ( +
+

Profile (protected by Middleware)

+

Page:

+

Access Token

+
{JSON.stringify({ accessToken: session?.accessToken }, null, 2)}
+

User

+
{JSON.stringify(session?.user, null, 2)}
+

Server Component:

+ {/*@ts-expect-error Async Server Component*/} + +

Client Component:

+ +
+ ); +} diff --git a/example-app/app/profile/page.tsx b/example-app/app/profile/page.tsx new file mode 100644 index 000000000..bbec98035 --- /dev/null +++ b/example-app/app/profile/page.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { getSession, withPageAuthRequired } from '@auth0/nextjs-auth0'; +import ServerComponent from '@/app/server-component'; +import ClientComponent from '@/app/client-component'; + +export default withPageAuthRequired( + async function Page() { + const session = await getSession(); + + return ( +
+

Profile

+

Page:

+

Access Token

+
{JSON.stringify({ accessToken: session?.accessToken }, null, 2)}
+

User

+
{JSON.stringify(session?.user, null, 2)}
+

Server Component:

+ {/*@ts-expect-error Async Server Component*/} + +

Client Component:

+ +
+ ); + }, + { returnTo: '/profile' } +); diff --git a/example-app/app/server-component.tsx b/example-app/app/server-component.tsx new file mode 100644 index 000000000..520d27669 --- /dev/null +++ b/example-app/app/server-component.tsx @@ -0,0 +1,17 @@ +import { getSession, getAccessToken } from '@auth0/nextjs-auth0'; + +export default async function ServerComponent() { + const session = await getSession(); + const accessToken = await getAccessToken(); + if (session) { + return ( + <> +

Access Token

+
{JSON.stringify(accessToken, null, 2)}
+

User

+
{JSON.stringify(session.user, null, 2)}
+ + ); + } + return <>; +} diff --git a/example-app/components/header.tsx b/example-app/components/header.tsx new file mode 100644 index 000000000..988a5cac2 --- /dev/null +++ b/example-app/components/header.tsx @@ -0,0 +1,126 @@ +import React from 'react'; +import Link from 'next/link'; +import { useUser } from '@auth0/nextjs-auth0/client'; +import { useRouter } from 'next/router'; + +const Header = (): React.ReactElement => { + const { user } = useUser(); + const { pathname } = useRouter(); + const pageName = pathname.split('/').pop(); + + return ( + <> +
+ +
+
+ +
+ + + ); +}; + +export default Header; diff --git a/examples/kitchen-sink-example/components/layout.tsx b/example-app/components/layout.tsx similarity index 88% rename from examples/kitchen-sink-example/components/layout.tsx rename to example-app/components/layout.tsx index 85df63b5e..2bff41be3 100644 --- a/examples/kitchen-sink-example/components/layout.tsx +++ b/example-app/components/layout.tsx @@ -17,7 +17,7 @@ const Layout = ({ children }: React.PropsWithChildren): React.ReactElem @@ -25,7 +25,7 @@ const Layout = ({ children }: React.PropsWithChildren): React.ReactElem body { margin: 0; color: #333; - font-family: -apple-system, 'Segoe UI'; + font-family: sans-serif; } `} diff --git a/example-app/lib/auth0.ts b/example-app/lib/auth0.ts new file mode 100644 index 000000000..dc32a5435 --- /dev/null +++ b/example-app/lib/auth0.ts @@ -0,0 +1,10 @@ +import { initAuth0, Auth0Server } from '@auth0/nextjs-auth0'; + +export const pageRouterAuth: Auth0Server = initAuth0({ + auth0Logout: !(process.env.AUTH0_ISSUER_BASE_URL as string).startsWith('http://localhost'), + routes: { + login: '/api/page-router-auth/login', + callback: '/api/page-router-auth/callback', + postLogoutRedirect: '/page-router' + } +}); diff --git a/examples/kitchen-sink-example/middleware.ts b/example-app/middleware.ts similarity index 67% rename from examples/kitchen-sink-example/middleware.ts rename to example-app/middleware.ts index fb2930b0b..7c1200309 100644 --- a/examples/kitchen-sink-example/middleware.ts +++ b/example-app/middleware.ts @@ -3,5 +3,5 @@ import { withMiddlewareAuthRequired } from '@auth0/nextjs-auth0/edge'; export default withMiddlewareAuthRequired(); export const config = { - matcher: ['/profile-mw', '/api/hello-world-mw'] + matcher: ['/page-router/profile-middleware', '/profile-middleware'] }; diff --git a/example-app/next.config.js b/example-app/next.config.js new file mode 100644 index 000000000..d619ab085 --- /dev/null +++ b/example-app/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + productionBrowserSourceMaps: true +}; + +module.exports = nextConfig; diff --git a/example-app/package.json b/example-app/package.json new file mode 100644 index 000000000..034ea891e --- /dev/null +++ b/example-app/package.json @@ -0,0 +1,25 @@ +{ + "name": "example-app", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "dev:local": "node server.js", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@auth0/nextjs-auth0": "file:../", + "@types/node": "file:../node_modules/@types/node", + "@types/react": "file:../node_modules/@types/react", + "@types/react-dom": "file:../node_modules/@types/react-dom", + "eslint": "8.40.0", + "eslint-config-next": "13.4.1", + "express": "^4.18.2", + "next": "file:../node_modules/next", + "react": "file:../node_modules/react", + "react-dom": "file:../node_modules/react-dom", + "typescript": "5.0.4" + } +} diff --git a/examples/kitchen-sink-example/pages/_app.tsx b/example-app/pages/_app.tsx similarity index 75% rename from examples/kitchen-sink-example/pages/_app.tsx rename to example-app/pages/_app.tsx index b42124c0d..4d8b46633 100644 --- a/examples/kitchen-sink-example/pages/_app.tsx +++ b/example-app/pages/_app.tsx @@ -6,7 +6,7 @@ export default function App({ Component, pageProps }: AppProps): React.ReactElem const { user } = pageProps; return ( - + ); diff --git a/example-app/pages/api/page-router-auth/[...auth0].ts b/example-app/pages/api/page-router-auth/[...auth0].ts new file mode 100644 index 000000000..1c15c7a8a --- /dev/null +++ b/example-app/pages/api/page-router-auth/[...auth0].ts @@ -0,0 +1,11 @@ +import { pageRouterAuth } from '@/lib/auth0'; + +const redirectUri = `${process.env.AUTH0_BASE_URL}/api/page-router-auth/callback`; + +export default pageRouterAuth.handleAuth({ + login: pageRouterAuth.handleLogin({ + authorizationParams: { redirect_uri: redirectUri } + }), + callback: pageRouterAuth.handleCallback({ redirectUri }), + logout: pageRouterAuth.handleLogout({ returnTo: `${process.env.AUTH0_BASE_URL}/page-router` }) +}); diff --git a/example-app/pages/api/page-router-profile.ts b/example-app/pages/api/page-router-profile.ts new file mode 100644 index 000000000..461f69f6d --- /dev/null +++ b/example-app/pages/api/page-router-profile.ts @@ -0,0 +1,8 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import { pageRouterAuth } from '../../lib/auth0'; + +export default pageRouterAuth.withApiAuthRequired(async function profile(req: NextApiRequest, res: NextApiResponse) { + const session = await pageRouterAuth.getSession(req, res); + + res.status(200).json(session?.user); +}); diff --git a/example-app/pages/page-router/index.tsx b/example-app/pages/page-router/index.tsx new file mode 100644 index 000000000..d9570837e --- /dev/null +++ b/example-app/pages/page-router/index.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +import Layout from '@/components/layout'; + +export default function Index() { + return ( + +

Page router directory

+
+ ); +} diff --git a/example-app/pages/page-router/profile-api.tsx b/example-app/pages/page-router/profile-api.tsx new file mode 100644 index 000000000..b92d6defe --- /dev/null +++ b/example-app/pages/page-router/profile-api.tsx @@ -0,0 +1,22 @@ +import React, { useEffect, useState } from 'react'; +import { withPageAuthRequired } from '@auth0/nextjs-auth0/client'; + +import Layout from '@/components/layout'; + +export default withPageAuthRequired(function ProfileApi() { + const [user, setUser] = useState(); + + useEffect(() => { + (async () => { + const res = await fetch(`${window.location.origin}/api/page-router-profile`); + setUser(await res.json()); + })(); + }, []); + + return ( + +

Profile (fetched from API)

+
{JSON.stringify(user, null, 2)}
+
+ ); +}); diff --git a/example-app/pages/page-router/profile-csr.tsx b/example-app/pages/page-router/profile-csr.tsx new file mode 100644 index 000000000..d6522b41c --- /dev/null +++ b/example-app/pages/page-router/profile-csr.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { useUser, withPageAuthRequired } from '@auth0/nextjs-auth0/client'; + +import Layout from '@/components/layout'; + +export default withPageAuthRequired(function Profile() { + const { user, isLoading } = useUser(); + if (isLoading) { + return

Loading...

; + } + return ( + +

Profile (client rendered)

+
{JSON.stringify(user, null, 2)}
+
+ ); +}); diff --git a/examples/kitchen-sink-example/pages/profile-mw.tsx b/example-app/pages/page-router/profile-middleware.tsx similarity index 75% rename from examples/kitchen-sink-example/pages/profile-mw.tsx rename to example-app/pages/page-router/profile-middleware.tsx index 9f17f5461..c45aa121e 100644 --- a/examples/kitchen-sink-example/pages/profile-mw.tsx +++ b/example-app/pages/page-router/profile-middleware.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useUser } from '@auth0/nextjs-auth0/client'; -import Layout from '../components/layout'; +import Layout from '@/components/layout'; export default function Profile() { const { user, isLoading } = useUser(); @@ -10,8 +10,7 @@ export default function Profile() { } return ( -

Profile

-

Profile

+

Profile (client rendered / protected by Middleware)

{JSON.stringify(user, null, 2)}
); diff --git a/example-app/pages/page-router/profile-ssr.tsx b/example-app/pages/page-router/profile-ssr.tsx new file mode 100644 index 000000000..47d387fe9 --- /dev/null +++ b/example-app/pages/page-router/profile-ssr.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { InferGetServerSidePropsType } from 'next'; +import { pageRouterAuth } from '../../lib/auth0'; +import Layout from '@/components/layout'; + +export default function Profile({ user }: InferGetServerSidePropsType): React.ReactElement { + return ( + +

Profile (server rendered)

+
{JSON.stringify(user, null, 2)}
+
+ ); +} + +export const getServerSideProps = pageRouterAuth.withPageAuthRequired(); diff --git a/examples/kitchen-sink-example/server.js b/example-app/server.js similarity index 63% rename from examples/kitchen-sink-example/server.js rename to example-app/server.js index 16646565d..eb2036f36 100644 --- a/examples/kitchen-sink-example/server.js +++ b/example-app/server.js @@ -1,11 +1,16 @@ const express = require('express'); const next = require('next'); -const oidc = require('../../scripts/oidc-provider'); +const oidc = require('../scripts/oidc-provider'); -const port = process.env.PORT || 3000; +const port = +(process.env.PORT || 3000); const app = next({ dev: true, hostname: 'localhost', port }); const handle = app.getRequestHandler(); +process.env.AUTH0_ISSUER_BASE_URL = `http://localhost:${port}/oidc/`; +process.env.AUTH0_CLIENT_ID = 'testing'; +process.env.AUTH0_CLIENT_SECRET = 'testing'; +process.env.AUTH0_SCOPE = 'openid profile email offline_access'; + app .prepare() .then(() => { diff --git a/examples/kitchen-sink-example/tsconfig.json b/example-app/tsconfig.json similarity index 65% rename from examples/kitchen-sink-example/tsconfig.json rename to example-app/tsconfig.json index 50eb114d0..e06a4454a 100644 --- a/examples/kitchen-sink-example/tsconfig.json +++ b/example-app/tsconfig.json @@ -1,36 +1,28 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, - "strict": false, + "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, - "jsx": "preserve", "isolatedModules": true, + "jsx": "preserve", "incremental": true, "plugins": [ { "name": "next" } - ] + ], + "paths": { + "@/*": ["./*"] + } }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts" - ], - "exclude": [ - "node_modules" - ] + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/examples/.eslintrc b/examples/.eslintrc deleted file mode 100644 index fb4071a5e..000000000 --- a/examples/.eslintrc +++ /dev/null @@ -1,30 +0,0 @@ -{ - "extends": ["plugin:prettier/recommended", "plugin:react/recommended"], - "plugins": ["prettier", "react"], - "root": true, - "env": { - "node": true, - "jest": true, - "es6": true, - "browser": true - }, - "parserOptions": { - "ecmaVersion": 2019, - "sourceType": "module" - }, - "rules": { - "prettier/prettier": "error", - "indent": ["error", 2], - "no-console": [ - 1, - { - "allow": ["error", "info", "warn"] - } - ], - "react/prop-types": 0, - "max-len": ["error", 120], - "comma-dangle": ["error", "never"], - "no-trailing-spaces": "error", - "semi": ["off"] - } -} diff --git a/examples/basic-example/.env.local.template b/examples/basic-example/.env.local.template deleted file mode 100644 index 7ac7eb8fd..000000000 --- a/examples/basic-example/.env.local.template +++ /dev/null @@ -1,5 +0,0 @@ -AUTH0_SECRET= -AUTH0_ISSUER_BASE_URL= -AUTH0_BASE_URL= -AUTH0_CLIENT_ID= -AUTH0_CLIENT_SECRET= diff --git a/examples/basic-example/.gitignore b/examples/basic-example/.gitignore deleted file mode 100644 index 5d054be8d..000000000 --- a/examples/basic-example/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules -dist -.env -.next -.DS_Store -package-lock.json diff --git a/examples/basic-example/components/header.jsx b/examples/basic-example/components/header.jsx deleted file mode 100644 index 086c04444..000000000 --- a/examples/basic-example/components/header.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import Link from 'next/link'; -import { useUser } from '@auth0/nextjs-auth0/client'; - -const Header = () => { - const { user } = useUser(); - - return ( -
- - - -
- ); -}; - -export default Header; diff --git a/examples/basic-example/components/layout.jsx b/examples/basic-example/components/layout.jsx deleted file mode 100644 index 81ae4f953..000000000 --- a/examples/basic-example/components/layout.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import Head from 'next/head'; - -import Header from './header'; - -const Layout = ({ children }) => ( - <> - - Next.js with Auth0 - - -
- -
-
{children}
-
- - - - -); - -export default Layout; diff --git a/examples/basic-example/next-env.d.ts b/examples/basic-example/next-env.d.ts deleted file mode 100644 index 4f11a03dc..000000000 --- a/examples/basic-example/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/basic-example/next.config.js b/examples/basic-example/next.config.js deleted file mode 100644 index 4f336cb74..000000000 --- a/examples/basic-example/next.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - poweredByHeader: false -}; diff --git a/examples/basic-example/now.json b/examples/basic-example/now.json deleted file mode 100644 index 46ed05ac1..000000000 --- a/examples/basic-example/now.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "build": { - "env": { - "AUTH0_DOMAIN": "YOUR_AUTH0_DOMAIN", - "AUTH0_CLIENT_ID": "YOUR_AUTH0_CLIENT_ID", - "AUTH0_CLIENT_SECRET": "@auth0_client_secret", - "REDIRECT_URI": "https://my-website.now.sh/api/auth/callback", - "POST_LOGOUT_REDIRECT_URI": "https://my-website.now.sh/", - "SESSION_COOKIE_SECRET": "@session_cookie_secret" - } - } -} diff --git a/examples/basic-example/package.json b/examples/basic-example/package.json deleted file mode 100644 index 68daa8c9f..000000000 --- a/examples/basic-example/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "nextjs-auth0-example", - "version": "1.0.0", - "description": "Example of how to sign in to your Next.js application using Auth0", - "scripts": { - "dev": "next", - "build": "next build", - "start": "next start" - }, - "author": "", - "license": "MIT", - "dependencies": { - "@auth0/nextjs-auth0": "file:../../", - "next": "file:../../node_modules/next", - "react": "file:../../node_modules/react", - "react-dom": "file:../../node_modules/react-dom" - } -} diff --git a/examples/basic-example/pages/_app.jsx b/examples/basic-example/pages/_app.jsx deleted file mode 100644 index a78730d70..000000000 --- a/examples/basic-example/pages/_app.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { UserProvider } from '@auth0/nextjs-auth0/client'; - -export default function App({ Component, pageProps }) { - // If you've used `withAuth`, pageProps.user can pre-populate the hook - // if you haven't used `withAuth`, pageProps.user is undefined so the hook - // fetches the user from the API routes - const { user } = pageProps; - - return ( - - - - ); -} diff --git a/examples/basic-example/pages/api/auth/[auth0].ts b/examples/basic-example/pages/api/auth/[auth0].ts deleted file mode 100644 index 304b4012a..000000000 --- a/examples/basic-example/pages/api/auth/[auth0].ts +++ /dev/null @@ -1,3 +0,0 @@ -import { handleAuth } from '@auth0/nextjs-auth0'; - -export default handleAuth(); diff --git a/examples/basic-example/pages/index.jsx b/examples/basic-example/pages/index.jsx deleted file mode 100644 index 9f80ea0d4..000000000 --- a/examples/basic-example/pages/index.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import { useUser } from '@auth0/nextjs-auth0/client'; - -import Layout from '../components/layout'; - -export default function Home() { - const { user, error, isLoading } = useUser(); - - return ( - -

Next.js and Auth0 Example

- - {isLoading &&

Loading login info...

} - - {error && ( - <> -

Error

-
{error.message}
- - )} - - {user && ( - <> -

Rendered user info on the client

-
{JSON.stringify(user, null, 2)}
- - )} - - {!isLoading && !error && !user && ( - <> -

- To test the login click in Login -

-

- Once you have logged in you should be able to click in Protected Page and Logout -

- - )} -
- ); -} diff --git a/examples/basic-example/pages/protected-page.jsx b/examples/basic-example/pages/protected-page.jsx deleted file mode 100644 index 89020d3a5..000000000 --- a/examples/basic-example/pages/protected-page.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { withPageAuthRequired } from '@auth0/nextjs-auth0'; -import { useUser } from '@auth0/nextjs-auth0/client'; - -import Layout from '../components/layout'; - -export default function ProtectedPage() { - const { user, error, isLoading } = useUser(); - - return ( - -

Protected Page

- - {isLoading &&

Loading profile...

} - - {error && ( - <> -

Error

-
{error.message}
- - )} - - {user && ( - <> -

Profile

-
{JSON.stringify(user, null, 2)}
- - )} -
- ); -} - -export const getServerSideProps = withPageAuthRequired(); diff --git a/examples/basic-example/tsconfig.json b/examples/basic-example/tsconfig.json deleted file mode 100644 index 35d51eac9..000000000 --- a/examples/basic-example/tsconfig.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "strict": false, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve" - }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx" - ], - "exclude": [ - "node_modules" - ] -} diff --git a/examples/kitchen-sink-example/.eslintrc b/examples/kitchen-sink-example/.eslintrc deleted file mode 100644 index ece3c8be8..000000000 --- a/examples/kitchen-sink-example/.eslintrc +++ /dev/null @@ -1,50 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "plugin:@next/next/recommended", - "plugin:prettier/recommended" - ], - "settings": { - "import/resolver": { - "node": { - "extensions": [".js", ".jsx", ".ts", ".tsx"] - } - }, - "react": { - "version": "detect" - } - }, - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module", - "ecmaFeatures": { - "jsx": true, - "experimentalObjectRestSpread": true - } - }, - "env": { - "es6": true, - "browser": true, - "node": true - }, - "rules": { - "react/jsx-uses-react": 1, - "react/jsx-no-undef": 2, - "react/jsx-wrap-multilines": 2, - "react/no-string-refs": 0, - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": [ - "error" - ] - }, - "plugins": [ - "import", - "@typescript-eslint", - "react", - "prettier" - ] -} diff --git a/examples/kitchen-sink-example/.gitignore b/examples/kitchen-sink-example/.gitignore deleted file mode 100644 index 5d054be8d..000000000 --- a/examples/kitchen-sink-example/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules -dist -.env -.next -.DS_Store -package-lock.json diff --git a/examples/kitchen-sink-example/.npmrc b/examples/kitchen-sink-example/.npmrc deleted file mode 100644 index 43c97e719..000000000 --- a/examples/kitchen-sink-example/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=false diff --git a/examples/kitchen-sink-example/.prettierrc b/examples/kitchen-sink-example/.prettierrc deleted file mode 100644 index 8ef5b8a3b..000000000 --- a/examples/kitchen-sink-example/.prettierrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "requirePragma": false, - "printWidth": 120, - "tabWidth": 2, - "useTabs": false, - "semi": true, - "singleQuote": true, - "trailingComma": "none", - "bracketSpacing": true, - "jsxBracketSameLine": false -} diff --git a/examples/kitchen-sink-example/.vscode/launch.json b/examples/kitchen-sink-example/.vscode/launch.json deleted file mode 100644 index 72cb4c691..000000000 --- a/examples/kitchen-sink-example/.vscode/launch.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "attach", - "name": "Launch Program", - "skipFiles": ["/**"], - "port": 9229 - } - ] -} diff --git a/examples/kitchen-sink-example/app/global.css b/examples/kitchen-sink-example/app/global.css deleted file mode 100644 index 9d8c42298..000000000 --- a/examples/kitchen-sink-example/app/global.css +++ /dev/null @@ -1,9 +0,0 @@ -body { - margin: 0; - color: #333; - font-family: -apple-system, 'Segoe UI', sans-serif; -} -.container { - max-width: 42rem; - margin: 1.5rem auto; -} diff --git a/examples/kitchen-sink-example/app/head.tsx b/examples/kitchen-sink-example/app/head.tsx deleted file mode 100644 index df1652511..000000000 --- a/examples/kitchen-sink-example/app/head.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; - -export default function Head() { - return ( - <> - Next.js with Auth0 - - ); -} diff --git a/examples/kitchen-sink-example/app/profile-experimental-rsc/page.tsx b/examples/kitchen-sink-example/app/profile-experimental-rsc/page.tsx deleted file mode 100644 index 7fec11ed4..000000000 --- a/examples/kitchen-sink-example/app/profile-experimental-rsc/page.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { headers } from 'next/headers'; -import { getSession as auth0GetSession } from '@auth0/nextjs-auth0'; -import { IncomingMessage, ServerResponse } from 'http'; -import { Socket } from 'net'; - -// Note: This is an experiment to test that the SDK works in the experimental app directory. -// You should not rely on this code (or the app directory) in production. -const reqRes = () => { - const req = new IncomingMessage(new Socket()); - headers().forEach((v, k) => { - req.headers[k] = v; - }); - const res = new ServerResponse(req); - return { req, res }; -}; - -export function getSession() { - const { req, res } = reqRes(); - return auth0GetSession(req, res); -} - -export default async function ExperimentalRscPage() { - const session = await getSession(); - return ( -
-

Profile

-

Profile

-
{JSON.stringify(session || {}, null, 2)}
-
- ); -} diff --git a/examples/kitchen-sink-example/components/header.tsx b/examples/kitchen-sink-example/components/header.tsx deleted file mode 100644 index e18286d7a..000000000 --- a/examples/kitchen-sink-example/components/header.tsx +++ /dev/null @@ -1,104 +0,0 @@ -'use client'; -import React from 'react'; -import Link from 'next/link'; -import { useUser } from '@auth0/nextjs-auth0/client'; - -const Header = (): React.ReactElement => { - const { user } = useUser(); - - return ( - - ); -}; - -export default Header; diff --git a/examples/kitchen-sink-example/lib/use-api.ts b/examples/kitchen-sink-example/lib/use-api.ts deleted file mode 100644 index a3350c9e2..000000000 --- a/examples/kitchen-sink-example/lib/use-api.ts +++ /dev/null @@ -1,60 +0,0 @@ -import * as React from 'react'; - -function initialState(args: { error?: any; isLoading?: boolean; response?: any }) { - return { - response: null, - error: null, - isLoading: true, - ...args - }; -} - -const useApi = ( - url: RequestInfo, - options = {} -): { - error: unknown; - isLoading: boolean; - response: any; -} => { - const [state, setState] = React.useState(() => initialState({})); - - React.useEffect(() => { - const fetchData = async () => { - try { - const res = await fetch(url, { - ...options - }); - - if (res.status >= 400) { - setState( - initialState({ - error: await res.json(), - isLoading: false - }) - ); - } else { - setState( - initialState({ - response: await res.json(), - isLoading: false - }) - ); - } - } catch (error) { - setState( - initialState({ - error: { - error: error.message - }, - isLoading: false - }) - ); - } - }; - fetchData(); - }, []); - return state; -}; - -export default useApi; diff --git a/examples/kitchen-sink-example/next-env.d.ts b/examples/kitchen-sink-example/next-env.d.ts deleted file mode 100644 index 4f11a03dc..000000000 --- a/examples/kitchen-sink-example/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/kitchen-sink-example/next.config.js b/examples/kitchen-sink-example/next.config.js deleted file mode 100644 index cae4f577f..000000000 --- a/examples/kitchen-sink-example/next.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - poweredByHeader: false, - experimental: { - appDir: true - } -}; diff --git a/examples/kitchen-sink-example/package.json b/examples/kitchen-sink-example/package.json deleted file mode 100644 index e65db4a12..000000000 --- a/examples/kitchen-sink-example/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "nextjs-auth0-kitchen-sink-example", - "version": "0.0.1", - "description": "Typescript example for nextjs-auth0", - "main": "index.js", - "scripts": { - "dev:local": "node server.js", - "dev": "next", - "build": "next build", - "start": "next start", - "format": "prettier --write \"{components,pages}/**/*.{ts,tsx}\"", - "lint": "eslint \"{components,pages}/**/*.{ts,tsx}\"", - "lint:fix": "eslint \"{components,pages}/**/*.{ts,tsx}\" --fix" - }, - "keywords": [ - "auth0", - "nextjs", - "typescript", - "react", - "eslint" - ], - "devDependencies": { - "@next/eslint-plugin-next": "^13.0.3", - "@types/node": "^14.14.21", - "@types/react": "^18.0.25", - "@types/styled-jsx": "^3.4.4", - "@typescript-eslint/eslint-plugin": "^4.13.0", - "@typescript-eslint/parser": "^4.13.0", - "eslint": "^7.9.0", - "eslint-config-prettier": "^8.1.0", - "eslint-plugin-prettier": "^3.1.4", - "express": "^4.17.1", - "lint-staged": "^10.3.0", - "prettier": "^2.1.1", - "typescript": "^4.1.3" - }, - "dependencies": { - "@auth0/nextjs-auth0": "file:../../", - "@serverless-jwt/next": "^0.2.1", - "next": "file:../../node_modules/next", - "react": "file:../../node_modules/react", - "react-dom": "file:../../node_modules/react-dom" - }, - "author": "Auth0", - "license": "MIT" -} diff --git a/examples/kitchen-sink-example/pages/_document.tsx b/examples/kitchen-sink-example/pages/_document.tsx deleted file mode 100644 index 13efaab3b..000000000 --- a/examples/kitchen-sink-example/pages/_document.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from 'react'; -import Document, { Html, Head, Main, NextScript } from 'next/document'; - -export default class CustomDocument extends Document { - render(): React.ReactElement { - return ( - - - -
- - - - ); - } -} diff --git a/examples/kitchen-sink-example/pages/about.tsx b/examples/kitchen-sink-example/pages/about.tsx deleted file mode 100644 index 2951cf944..000000000 --- a/examples/kitchen-sink-example/pages/about.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; - -import Layout from '../components/layout'; - -export default function About(): React.ReactElement { - return ( - -

About

-

- This is the about page, navigating between this page and Home is always pretty fast. However, when you - navigate to the Profile page it takes more time because it uses SSR to fetch the user first; -

-
- ); -} diff --git a/examples/kitchen-sink-example/pages/api/auth/[auth0].ts b/examples/kitchen-sink-example/pages/api/auth/[auth0].ts deleted file mode 100644 index 1511a73e0..000000000 --- a/examples/kitchen-sink-example/pages/api/auth/[auth0].ts +++ /dev/null @@ -1,8 +0,0 @@ -import { handleAuth } from '@auth0/nextjs-auth0'; - -export default handleAuth({ - onError(req, res, error) { - console.error(error); - res.status(error.status || 500).end('Check the console for the error'); - } -}); diff --git a/examples/kitchen-sink-example/pages/api/hello-world-mw.ts b/examples/kitchen-sink-example/pages/api/hello-world-mw.ts deleted file mode 100644 index 77c77f470..000000000 --- a/examples/kitchen-sink-example/pages/api/hello-world-mw.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default async function helloWorld(req, res) { - res.status(200).json({ text: 'Hello World!' }); -} diff --git a/examples/kitchen-sink-example/pages/api/my/shows.ts b/examples/kitchen-sink-example/pages/api/my/shows.ts deleted file mode 100644 index c7e6e23da..000000000 --- a/examples/kitchen-sink-example/pages/api/my/shows.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NextJwtVerifier } from '@serverless-jwt/next'; -import { NextAuthenticatedApiRequest } from '@serverless-jwt/next/dist/types'; -import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'; - -const verifyJwt = NextJwtVerifier({ - issuer: process.env.AUTH0_ISSUER_BASE_URL, - audience: process.env.AUTH0_AUDIENCE -}); - -const requireScope = (scope: string, apiRoute: NextApiHandler) => - verifyJwt(async (req: NextAuthenticatedApiRequest, res) => { - const { claims } = req.identityContext; - if (!claims || !claims.scope || (claims.scope as string).indexOf(scope) === -1) { - return res.status(403).json({ - error: 'access_denied', - error_description: `Token does not contain the required '${scope}' scope` - }); - } - return apiRoute(req, res) as void; - }); - -const apiRoute = async (req: NextApiRequest, res: NextApiResponse) => { - try { - const response = await fetch('https://api.tvmaze.com/search/shows?q=identity'); - const shows = await response.json(); - - res.status(200).json({ shows }); - } catch (error) { - console.error(error); - res.status(error.status || 500).json({ - code: error.code, - error: error.message - }); - } -}; - -export default requireScope('read:shows', apiRoute); diff --git a/examples/kitchen-sink-example/pages/api/shows.ts b/examples/kitchen-sink-example/pages/api/shows.ts deleted file mode 100644 index 4dbbd5d9d..000000000 --- a/examples/kitchen-sink-example/pages/api/shows.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { withApiAuthRequired, getAccessToken } from '@auth0/nextjs-auth0'; - -export default withApiAuthRequired(async function shows(req, res) { - try { - const { accessToken } = await getAccessToken(req, res, { - scopes: ['read:shows'] - }); - - const baseUrlOrDomain = process.env.AUTH0_BASE_URL || process.env.NEXT_PUBLIC_AUTH0_BASE_URL; - const baseURL = baseUrlOrDomain?.indexOf('http') === 0 ? baseUrlOrDomain : `https://${baseUrlOrDomain}`; - - // This is a contrived example, normally your external API would exist on another domain. - const response = await fetch(baseURL + '/api/my/shows', { - headers: { - Authorization: `Bearer ${accessToken}` - } - }); - - const shows = await response.json(); - res.status(response.status || 200).json(shows); - } catch (error) { - console.error(error); - res.status(error.status || 500).json({ - code: error.code, - error: error.message - }); - } -}); diff --git a/examples/kitchen-sink-example/pages/index.tsx b/examples/kitchen-sink-example/pages/index.tsx deleted file mode 100644 index e7287dd4a..000000000 --- a/examples/kitchen-sink-example/pages/index.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import { useUser } from '@auth0/nextjs-auth0/client'; - -import Layout from '../components/layout'; - -export default function Home(): React.ReactElement { - const { user, error, isLoading } = useUser(); - - return ( - -

Next.js and Auth0 Example

- - {isLoading &&

Loading login info...

} - - {error && ( - <> -

Error

-
{error.message}
- - )} - - {user && ( - <> -

Rendered user info on the client

-
{JSON.stringify(user, null, 2)}
- - )} - - {!isLoading && !error && !user && ( - <> -

- To test the login click in Login -

-

- Once you have logged in you should be able to click in Protected Page and Logout -

- - )} -
- ); -} diff --git a/examples/kitchen-sink-example/pages/profile-ssr.tsx b/examples/kitchen-sink-example/pages/profile-ssr.tsx deleted file mode 100644 index 839ce240c..000000000 --- a/examples/kitchen-sink-example/pages/profile-ssr.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { withPageAuthRequired } from '@auth0/nextjs-auth0'; -import { UserProfile } from '@auth0/nextjs-auth0/client'; - -import Layout from '../components/layout'; - -type ProfileProps = { user: UserProfile }; - -export default function Profile({ user }: ProfileProps): React.ReactElement { - return ( - -

Profile

-

Profile (server rendered)

-
{JSON.stringify(user, null, 2)}
-
- ); -} - -export const getServerSideProps = withPageAuthRequired(); diff --git a/examples/kitchen-sink-example/pages/profile.tsx b/examples/kitchen-sink-example/pages/profile.tsx deleted file mode 100644 index e40ef1535..000000000 --- a/examples/kitchen-sink-example/pages/profile.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { withPageAuthRequired } from '@auth0/nextjs-auth0/client'; - -import Layout from '../components/layout'; - -export default withPageAuthRequired(function Profile({ user }) { - return ( - -

Profile

-

Profile

-
{JSON.stringify(user, null, 2)}
-
- ); -}); diff --git a/examples/kitchen-sink-example/pages/shows.tsx b/examples/kitchen-sink-example/pages/shows.tsx deleted file mode 100644 index ed36edcee..000000000 --- a/examples/kitchen-sink-example/pages/shows.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; - -import useApi from '../lib/use-api'; -import Layout from '../components/layout'; -import { withPageAuthRequired } from '@auth0/nextjs-auth0/client'; - -type TVShow = { show: { name: string } }; - -export default withPageAuthRequired(function TvShows(): React.ReactElement { - const { response, error, isLoading } = useApi('/api/shows'); - - return ( - -

TV Shows

- - {isLoading &&

Loading TV shows...

} - - {response && ( - <> -

My favourite TV shows:

-
-            {JSON.stringify(
-              response.shows.map((s: TVShow) => s.show.name),
-              null,
-              2
-            )}
-          
- - )} - - {error && ( - <> -

Error loading TV shows

-
{JSON.stringify(error, null, 2)}
- - )} -
- ); -}); diff --git a/jest-base.config.js b/jest-base.config.js new file mode 100644 index 000000000..65bd5b054 --- /dev/null +++ b/jest-base.config.js @@ -0,0 +1,9 @@ +/** @type {import('jest').Config} */ +module.exports = { + rootDir: '.', + moduleFileExtensions: ['ts', 'tsx', 'js'], + preset: 'ts-jest/presets/js-with-ts', + globalSetup: './tests/global-setup.ts', + setupFilesAfterEnv: ['./tests/setup.ts'], + transformIgnorePatterns: ['/node_modules/(?!oauth4webapi)'] +}; diff --git a/jest-edge.config.js b/jest-edge.config.js new file mode 100644 index 000000000..19bfac4db --- /dev/null +++ b/jest-edge.config.js @@ -0,0 +1,18 @@ +const base = require('./jest-base.config'); + +/** @type {import('jest').Config} */ +module.exports = { + ...base, + displayName: 'edge', + testEnvironment: '@edge-runtime/jest-environment', + testMatch: [ + '**/tests/handlers/login.test.ts', + '**/tests/handlers/logout.test.ts', + '**/tests/handlers/callback.test.ts', + '**/tests/handlers/profile.test.ts', + '**/tests/http/auth0-next-request.test.ts', + '**/tests/http/auth0-next-response.test.ts', + '**/tests/helpers/with-middleware-auth-required.test.ts', + '**/tests/session/get-access-token.test.ts' + ] +}; diff --git a/jest-node.config.js b/jest-node.config.js new file mode 100644 index 000000000..ac525efe2 --- /dev/null +++ b/jest-node.config.js @@ -0,0 +1,8 @@ +const base = require('./jest-base.config'); + +/** @type {import('jest').Config} */ +module.exports = { + ...base, + displayName: 'node', + testEnvironment: 'jest-environment-node-single-context' +}; diff --git a/package-lock.json b/package-lock.json index 3d931e6ae..ea34d0542 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,26 +1,26 @@ { "name": "@auth0/nextjs-auth0", - "version": "2.7.0", + "version": "3.0.0-beta.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@auth0/nextjs-auth0", - "version": "2.7.0", + "version": "3.0.0-beta.3", "license": "MIT", "dependencies": { "@panva/hkdf": "^1.0.2", "cookie": "^0.5.0", "debug": "^4.3.4", - "http-errors": "^1.8.1", "joi": "^17.6.0", "jose": "^4.9.2", + "oauth4webapi": "^2.3.0", "openid-client": "^5.2.1", "tslib": "^2.4.0", "url-join": "^4.0.1" }, "devDependencies": { - "@edge-runtime/jest-environment": "^1.1.0-beta.31", + "@edge-runtime/jest-environment": "^2.1.0", "@testing-library/jest-dom": "^5.11.9", "@testing-library/react": "^13.4.0", "@testing-library/react-hooks": "^5.0.3", @@ -28,10 +28,10 @@ "@types/clone": "^2.1.0", "@types/cookie": "^0.5.1", "@types/debug": "^4.1.5", - "@types/http-errors": "^1.8.0", - "@types/jest": "^27.0.1", + "@types/jest": "^29.5.2", "@types/jsonwebtoken": "^8.5.0", "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", "@types/on-headers": "^1.0.0", "@types/react": "^18.0.24", "@types/react-dom": "^17.0.9", @@ -51,10 +51,12 @@ "eslint-plugin-prettier": "^3.3.1", "eslint-plugin-react": "^7.22.0", "eslint-plugin-react-hooks": "^4.2.0", - "jest": "^27.2.0", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", "jest-environment-node-single-context": "^27.3.0", "next": "^13.4.10", "nock": "^13.0.5", + "node-fetch": "^2.6.11", "oidc-provider": "^7.6.0", "on-headers": "^1.0.2", "prettier": "^2.2.1", @@ -65,12 +67,12 @@ "start-server-and-test": "^1.11.7", "timekeeper": "^2.2.0", "tough-cookie": "^4.0.0", - "ts-jest": "^27.0.5", + "ts-jest": "^29.1.0", "typedoc": "^0.24.1", "typescript": "^4.1.3" }, "engines": { - "node": ">=12.19.0" + "node": ">=16" }, "peerDependencies": { "next": ">=10" @@ -82,6 +84,19 @@ "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", "dev": true }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", @@ -92,35 +107,35 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.15.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.5.tgz", - "integrity": "sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.5", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", + "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.9", + "@babel/helper-module-transforms": "^7.22.9", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.7", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.8", + "@babel/types": "^7.22.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" + "json5": "^2.2.2", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -131,17 +146,23 @@ } }, "node_modules/@babel/core/node_modules/@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "node_modules/@babel/core/node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -154,48 +175,32 @@ "node": ">=6" } }, - "node_modules/@babel/core/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@babel/generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", - "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", + "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", "dev": true, "dependencies": { - "@babel/types": "^7.15.4", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz", + "integrity": "sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -204,186 +209,167 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "dependencies": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" + "yallist": "^3.0.2" } }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", + "node_modules/@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", "dev": true, "dependencies": { - "@babel/types": "^7.15.4" + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", "dev": true, "dependencies": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.4.tgz", - "integrity": "sha512-9fHHSGE9zTC++KuXLZcB5FKgvlV83Ox+NLUmQTawovwlJ85+QMhk1CnVk406CQVj97LaWod6KVjl2Sfgw9Aktw==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", + "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.14.9", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.5" }, "engines": { "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", + "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", "dev": true, "dependencies": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -454,9 +440,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.6.tgz", - "integrity": "sha512-S/TSCcsRuCkmpUuoWijua0Snt+f3ewU/8spLo+4AXJCZfT0bVCzLD5MuOKdrx0mlAptbKzn5AdgEIIKXxXkz9Q==", + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -525,6 +511,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", @@ -613,12 +614,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -640,44 +641,45 @@ } }, "node_modules/@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template/node_modules/@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", + "version": "7.22.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", + "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.7", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.7", + "@babel/types": "^7.22.5", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -686,12 +688,12 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -707,12 +709,13 @@ } }, "node_modules/@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -826,34 +829,19 @@ } }, "node_modules/@edge-runtime/jest-environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@edge-runtime/jest-environment/-/jest-environment-1.1.0.tgz", - "integrity": "sha512-YwiR+zxAt3BksS1DrOuVdqnDw+18EZxm3vpGo8vuxqKXOg9UNdmj7c3nfkdTdhQ3gr3+sSpcS/4+vLoqW1QSKA==", - "dev": true, - "dependencies": { - "@edge-runtime/vm": "1.1.0", - "@jest/environment": "28.1.3", - "@jest/fake-timers": "28.1.3", - "@jest/types": "28.1.3", - "jest-mock": "28.1.3", - "jest-util": "28.1.3" - } - }, - "node_modules/@edge-runtime/jest-environment/node_modules/@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@edge-runtime/jest-environment/-/jest-environment-2.2.5.tgz", + "integrity": "sha512-I9JYvOrWhsso/C/OmSxaawEEw7mUpjNVIwNJncBODVhLoFkn6gkHGZVTPR0e4g4yH9yq4gwXnSdC4BS/iwTSqw==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@edge-runtime/vm": "3.0.4", + "@jest/environment": "29.5.0", + "@jest/fake-timers": "29.5.0", + "jest-mock": "29.5.0", + "jest-util": "29.5.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">=14" } }, "node_modules/@edge-runtime/jest-environment/node_modules/chalk": { @@ -873,12 +861,12 @@ } }, "node_modules/@edge-runtime/jest-environment/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", + "@jest/types": "^29.5.0", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -886,22 +874,28 @@ "picomatch": "^2.2.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@edge-runtime/primitives": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@edge-runtime/primitives/-/primitives-1.1.0.tgz", - "integrity": "sha512-MpL5fKlOs9mz5DMRuFchLe3Il84t7XpfbPq4qtaEK37uNMCRx1MzA3d7A4aTsR/guXSZvV/AtEbKVqBWjuSThA==", - "dev": true + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@edge-runtime/primitives/-/primitives-3.0.4.tgz", + "integrity": "sha512-ZisWDavCpjroNbXZ1FZ2qHqlNfTXjIcQx+7aRdC+n6DVsTShUW7S+urGj2Wags5CH0oICWRoksYX5GngW7SZzw==", + "dev": true, + "engines": { + "node": ">=14" + } }, "node_modules/@edge-runtime/vm": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@edge-runtime/vm/-/vm-1.1.0.tgz", - "integrity": "sha512-a3PSCdznoop5+ifkNDaSINB9V+Anwh+wpoaASIWhq9PLQuBF9D6Yxe/mLRZkuZRkOJ2ZmaTzMGDI5ROUChTL7g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@edge-runtime/vm/-/vm-3.0.4.tgz", + "integrity": "sha512-/QvVIn2+nO48dO3cl5tD3cjoF/xJ7WfTKBIs/oKgfmmWbEQ4EixKlOTJEpcBdOni0E8FCHIc7yxoJGq1qcLdCg==", "dev": true, "dependencies": { - "@edge-runtime/primitives": "1.1.0" + "@edge-runtime/primitives": "3.0.4" + }, + "engines": { + "node": ">=14" } }, "node_modules/@eslint/eslintrc": { @@ -1025,15 +1019,6 @@ "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1062,57 +1047,20 @@ } }, "node_modules/@jest/console": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", - "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.1.tgz", + "integrity": "sha512-Aj772AYgwTSr5w8qnyoJ0eDYvN6bMsH3ORH1ivMotrInHLKdUz6BDlaEXHdM6kODaBIkNIyQGzsMvRdOv7VG7Q==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", "slash": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@jest/console/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/console/node_modules/chalk": { @@ -1131,63 +1079,60 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/console/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "node_modules/@jest/console/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/core": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", - "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.1.tgz", + "integrity": "sha512-CcowHypRSm5oYQ1obz1wfvkjZZ2qoQlrKKvlfPwh5jUXVU12TWr2qMeH8chLMuTFzHh5a1g2yaqlqDICbr+ukQ==", "dev": true, "dependencies": { - "@jest/console": "^27.5.1", - "@jest/reporters": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/console": "^29.6.1", + "@jest/reporters": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "emittery": "^0.8.1", + "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^27.5.1", - "jest-config": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-resolve-dependencies": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "jest-watcher": "^27.5.1", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.6.1", + "jest-haste-map": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-resolve-dependencies": "^29.6.1", + "jest-runner": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", + "jest-watcher": "^29.6.1", "micromatch": "^4.0.4", - "rimraf": "^3.0.0", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1198,43 +1143,6 @@ } } }, - "node_modules/@jest/core/node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@jest/core/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/core/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1251,106 +1159,110 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/core/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "node_modules/@jest/core/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "node_modules/@jest/core/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "jest-mock": "^29.5.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/environment/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@jest/expect": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-N5xlPrAYaRNyFgVf2s9Uyyvr795jnB6rObuPx4QFvNJz8aAjpZUDfO4bh5G/xuplMID8PrnuF1+SfSyDxhsgYg==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "expect": "^29.6.1", + "jest-snapshot": "^29.6.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "node_modules/@jest/expect-utils": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.1.tgz", + "integrity": "sha512-o319vIf5pEMx0LmzSxxkYYxo4wrRLKHq9dP1yJU7FoPTB0LfAKSz8SWD6D/6U3v/O52t9cF5t+MeJiRsfk7zMw==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" + "jest-get-type": "^29.4.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "node_modules/@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers/node_modules/chalk": { @@ -1370,12 +1282,12 @@ } }, "node_modules/@jest/fake-timers/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -1383,99 +1295,54 @@ "picomatch": "^2.2.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", - "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.1.tgz", + "integrity": "sha512-2VjpaGy78JY9n9370H8zGRCFbYVWwjY6RdDMhoJHa1sYfwe6XM/azGN0SjY8kk7BOZApIejQ1BFPyH7FPG0w3A==", "dev": true, "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/types": "^27.5.1", - "expect": "^27.5.1" + "@jest/environment": "^29.6.1", + "@jest/expect": "^29.6.1", + "@jest/types": "^29.6.1", + "jest-mock": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/globals/node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "node_modules/@jest/globals/node_modules/@jest/environment": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-mock": "^29.6.1" }, "engines": { - "node": ">=6.9.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/globals/node_modules/@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "node_modules/@jest/globals/node_modules/@jest/fake-timers": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "dependencies": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@jest/globals/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals/node_modules/chalk": { @@ -1494,73 +1361,70 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/globals/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "node_modules/@jest/globals/node_modules/jest-mock": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-util": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/globals/node_modules/jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "node_modules/@jest/globals/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*" + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", - "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.1.tgz", + "integrity": "sha512-9zuaI9QKr9JnoZtFQlw4GREQbxgmNYXU6QuWtmuODvk5nvPUeBYapVR/VYMyi2WSx3jXTLJTJji8rN6+Cm4+FA==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/console": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", - "glob": "^7.1.2", + "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^5.1.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", + "jest-worker": "^29.6.1", "slash": "^3.0.0", - "source-map": "^0.6.0", "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1571,31 +1435,6 @@ } } }, - "node_modules/@jest/reporters/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/reporters/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1612,73 +1451,106 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@jest/reporters/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.24.1" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/source-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", - "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz", + "integrity": "sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==", "dev": true, "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", - "graceful-fs": "^4.2.9", - "source-map": "^0.6.0" + "graceful-fs": "^4.2.9" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/test-result": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", - "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.1.tgz", + "integrity": "sha512-Ynr13ZRcpX6INak0TPUukU8GWRfm/vAytE3JbJNGAvINySWYdfE7dGZMbk36oVuK4CigpbhMn8eg1dixZ7ZJOw==", "dev": true, "dependencies": { - "@jest/console": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/console": "^29.6.1", + "@jest/types": "^29.6.1", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/test-result/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/@jest/test-sequencer": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.1.tgz", + "integrity": "sha512-oBkC36PCDf/wb6dWeQIhaviU0l5u6VCsXa119yqdUosYAt7/FbQU2M2UoziO3igj/HBDEgp57ONQ3fm0v9uyyg==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "@jest/test-result": "^29.6.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.1", + "slash": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/test-result/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "node_modules/@jest/transform": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.1.tgz", + "integrity": "sha512-URnTneIU3ZjRSaf906cvf6Hpox3hIeJXRnz3VDSw5/X93gR8ycdfSIEy19FlVx8NFmpN7fe3Gb1xF+NjXaQLWg==", "dev": true, "dependencies": { - "@types/yargs-parser": "*" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.1", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/test-result/node_modules/chalk": { + "node_modules/@jest/transform/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -1694,73 +1566,54 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/test-sequencer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", - "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", + "node_modules/@jest/transform/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@jest/test-result": "^27.5.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-runtime": "^27.5.1" + "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", - "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", + "node_modules/@jest/transform/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.5.1", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-util": "^27.5.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/@jest/transform/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/@jest/types": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", "dev": true, "dependencies": { + "@jest/schemas": "^29.6.0", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^16.0.0", + "@types/yargs": "^17.0.8", "chalk": "^4.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/chalk": { + "node_modules/@jest/types/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -1776,6 +1629,60 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, "node_modules/@koa/cors": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-3.1.0.tgz", @@ -2020,9 +1927,9 @@ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" }, "node_modules/@sinclair/typebox": { - "version": "0.24.38", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.38.tgz", - "integrity": "sha512-IbYB6vdhLFmzGEyXXEdFAJKyq7S4/RsivkgxNzs/LzwYuUJHmeNQ0cHkjG/Yqm6VgUzzZDLMZAf0XgeeaZAocA==", + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, "node_modules/@sindresorhus/is": { @@ -2047,12 +1954,21 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "dependencies": { - "@sinonjs/commons": "^1.7.0" + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" } }, "node_modules/@swc/helpers": { @@ -2188,12 +2104,12 @@ } }, "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true, "engines": { - "node": ">= 6" + "node": ">= 10" } }, "node_modules/@types/aria-query": { @@ -2203,13 +2119,13 @@ "dev": true }, "node_modules/@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", "dev": true, "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" @@ -2235,12 +2151,12 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz", - "integrity": "sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", + "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", "dev": true, "dependencies": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.20.7" } }, "node_modules/@types/body-parser": { @@ -2302,9 +2218,9 @@ } }, "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", "dev": true, "dependencies": { "@types/node": "*" @@ -2316,12 +2232,6 @@ "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", "dev": true }, - "node_modules/@types/http-errors": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz", - "integrity": "sha512-2aoSC4UUbHDj2uCsCxcG/vRMXey/m17bC7UwitVm5hn22nI8O8Y9iDpA76Orc+DWkQ4zZrOKEshCqR/jSuXAHA==", - "dev": true - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -2347,19 +2257,62 @@ } }, "node_modules/@types/jest": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.1.tgz", - "integrity": "sha512-HTLpVXHrY69556ozYkcq47TtQJXpcWAWfkoqz+ZGz2JnmZhzlRjprCIyFnetSy8gpDWwTTGBcRVv1J1I1vBrHw==", + "version": "29.5.3", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.3.tgz", + "integrity": "sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==", "dev": true, "dependencies": { - "jest-diff": "^27.0.0", - "pretty-format": "^27.0.0" + "expect": "^29.0.0", + "pretty-format": "^29.0.0" } }, - "node_modules/@types/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==", + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@types/jest/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==", "dev": true }, "node_modules/@types/json-schema": { @@ -2404,6 +2357,30 @@ "integrity": "sha512-GXZxEtOxYGFchyUzxvKI14iff9KZ2DI+A6a37o6EQevtg6uO9t+aUZKcaC1Te5Ng1OnLM7K9NVVj+FbecD9cJg==", "dev": true }, + "node_modules/@types/node-fetch": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", + "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@types/on-headers": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/on-headers/-/on-headers-1.0.0.tgz", @@ -2414,9 +2391,9 @@ } }, "node_modules/@types/prettier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", - "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", "dev": true }, "node_modules/@types/prop-types": { @@ -2575,9 +2552,9 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.20", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.20.tgz", - "integrity": "sha512-eknWrTHofQuPk2iuqDm1waA7V6xPlbgBoaaXEgYkClhLOnB0TtbW+srJaOToAgawPxPlHQzwypFA2bhZaUGP5A==", + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -2863,13 +2840,25 @@ } }, "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", "dev": true, "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, "node_modules/acorn-jsx": { @@ -2882,9 +2871,9 @@ } }, "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true, "engines": { "node": ">=0.4.0" @@ -3386,52 +3375,26 @@ "dev": true }, "node_modules/babel-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", - "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.1.tgz", + "integrity": "sha512-qu+3bdPEQC6KZSPz+4Fyjbga5OODNcp49j6GKzG1EKbkfyJBxEYGVUmVGpwCSeGouG52R4EgYMLb6p9YeEEQ4A==", "dev": true, "dependencies": { - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/transform": "^29.6.1", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.5.1", + "babel-preset-jest": "^29.5.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, - "node_modules/babel-jest/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/babel-jest/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/babel-jest/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3465,18 +3428,18 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", - "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", "dev": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", + "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/babel-preset-current-node-syntax": { @@ -3503,16 +3466,16 @@ } }, "node_modules/babel-preset-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", - "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^27.5.1", + "babel-plugin-jest-hoist": "^29.5.0", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" @@ -3759,12 +3722,6 @@ "node": ">=8" } }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -3772,26 +3729,35 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" }, "bin": { "browserslist": "cli.js" }, "engines": { "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" } }, "node_modules/browserstack-cypress-cli": { @@ -3990,15 +3956,6 @@ "node": ">=6" } }, - "node_modules/browserstack-cypress-cli/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/browserstack-cypress-cli/node_modules/slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", @@ -4313,9 +4270,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001429", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz", - "integrity": "sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg==", + "version": "1.0.30001517", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz", + "integrity": "sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==", "dev": true, "funding": [ { @@ -4325,6 +4282,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -4399,9 +4360,9 @@ "dev": true }, "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, "node_modules/clean-stack": { @@ -4517,9 +4478,9 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, "node_modules/color-convert": { @@ -4683,13 +4644,10 @@ } }, "node_modules/convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true }, "node_modules/cookie": { "version": "0.5.0", @@ -4800,9 +4758,9 @@ "dev": true }, "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", "dev": true }, "node_modules/cssstyle": { @@ -4978,17 +4936,17 @@ } }, "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", "dev": true, "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/dayjs": { @@ -5023,9 +4981,9 @@ } }, "node_modules/decimal.js": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", - "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, "node_modules/decompress": { @@ -5268,9 +5226,9 @@ "dev": true }, "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "engines": { "node": ">=0.10.0" @@ -5329,6 +5287,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, "engines": { "node": ">= 0.6" } @@ -5362,12 +5321,12 @@ } }, "node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", "dev": true, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/dir-glob": { @@ -5410,24 +5369,15 @@ "dev": true }, "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", "dev": true, "dependencies": { - "webidl-conversions": "^5.0.0" + "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/dot-prop": { @@ -5495,18 +5445,18 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.3.836", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.836.tgz", - "integrity": "sha512-Ney3pHOJBWkG/AqYjrW0hr2AUCsao+2uvq9HUlRP8OlpSdk/zOHOUJP7eu0icDvePC9DlgffuelP4TnOJmMRUg==", + "version": "1.4.470", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.470.tgz", + "integrity": "sha512-zZM48Lmy2FKWgqyvsX9XK+J6FfP7aCDUFLmgooLJzA7v1agCs/sxSoBpTIwDLhmbhpx9yJIxj2INig/ncjJRqg==", "dev": true }, "node_modules/emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sindresorhus/emittery?sponsor=1" @@ -5561,6 +5511,18 @@ "node": ">=8.6" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -5693,15 +5655,14 @@ } }, "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dev": true, "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" + "esutils": "^2.0.2" }, "bin": { "escodegen": "bin/escodegen.js", @@ -5723,57 +5684,6 @@ "node": ">=4.0" } }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/eslint": { "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", @@ -6395,55 +6305,20 @@ } }, "node_modules/expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "dev": true, - "dependencies": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/expect/node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/expect/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-XEdDLonERCU1n9uR56/Stx9OqojaLAQtZf9PrCHH9Hl8YXiEIka3H4NXJ3NOIBmQJTg7+j7buh34PMHfJujc8g==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/expect-utils": "^29.6.1", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/expect/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/expect/node_modules/chalk": { @@ -6462,24 +6337,21 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/expect/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "node_modules/expect/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/extend": { @@ -6576,9 +6448,9 @@ } }, "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, "dependencies": { "bser": "2.1.1" @@ -7293,15 +7165,15 @@ } }, "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", "dev": true, "dependencies": { - "whatwg-encoding": "^1.0.5" + "whatwg-encoding": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/html-escaper": { @@ -7333,6 +7205,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", @@ -7345,12 +7218,12 @@ } }, "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, "dependencies": { - "@tootallnate/once": "1", + "@tootallnate/once": "2", "agent-base": "6", "debug": "4" }, @@ -7524,7 +7397,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/ini": { "version": "2.0.0", @@ -8059,9 +7933,9 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "dependencies": { "@babel/core": "^7.12.3", @@ -8150,20 +8024,21 @@ } }, "node_modules/jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", - "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.1.tgz", + "integrity": "sha512-Nirw5B4nn69rVUZtemCQhwxOBhm0nsp3hmtF4rzCeWD7BkjAXRIji7xWQfnTNbz9g0aVsBX6aZK3n+23LM6uDw==", "dev": true, "dependencies": { - "@jest/core": "^27.5.1", + "@jest/core": "^29.6.1", + "@jest/types": "^29.6.1", "import-local": "^3.0.2", - "jest-cli": "^27.5.1" + "jest-cli": "^29.6.1" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -8175,64 +8050,22 @@ } }, "node_modules/jest-changed-files": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", - "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", "execa": "^5.0.0", - "throat": "^6.0.1" + "p-limit": "^3.1.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-changed-files/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-changed-files/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/jest-changed-files/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.3", @@ -8274,111 +8107,66 @@ } }, "node_modules/jest-circus": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", - "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.1.tgz", + "integrity": "sha512-tPbYLEiBU4MYAL2XoZme/bgfUeotpDBd81lgHLCbDZZFaGmECk0b+/xejPFtmiBP87GgP/y4jplcRpbH+fgCzQ==", "dev": true, "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/environment": "^29.6.1", + "@jest/expect": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^0.7.0", - "expect": "^27.5.1", "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", + "jest-each": "^29.6.1", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", + "p-limit": "^3.1.0", + "pretty-format": "^29.6.1", + "pure-rand": "^6.0.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.18.6" + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=6.9.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-circus/node_modules/@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "dependencies": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-mock": "^27.5.1" + "jest-mock": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-circus/node_modules/@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/jest-circus/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-circus/node_modules/chalk": { @@ -8397,63 +8185,93 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-circus/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "node_modules/jest-circus/node_modules/jest-mock": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-util": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-circus/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/jest-cli": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", - "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.1.tgz", + "integrity": "sha512-607dSgTA4ODIN6go9w6xY3EYkyPFGicx51a69H7yfvt7lN53xNswEVLovq+E77VsTRi5fWprLH0yl4DJgE8Ing==", "dev": true, "dependencies": { - "@jest/core": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/core": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", + "jest-config": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "prompts": "^2.0.1", - "yargs": "^16.2.0" + "yargs": "^17.3.1" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -8464,113 +8282,164 @@ } } }, - "node_modules/jest-cli/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-cli/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "node_modules/jest-cli/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "dependencies": { - "@types/yargs-parser": "*" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-cli/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "engines": { "node": ">=10" + } + }, + "node_modules/jest-cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-cli/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" } }, "node_modules/jest-config": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", - "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.1.tgz", + "integrity": "sha512-XdjYV2fy2xYixUiV2Wc54t3Z4oxYPAELUzWnV6+mcbq0rh742X2p52pii5A3oeRzYjLnQxCsZmp0qpI6klE2cQ==", "dev": true, "dependencies": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.5.1", - "@jest/types": "^27.5.1", - "babel-jest": "^27.5.1", + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.6.1", + "@jest/types": "^29.6.1", + "babel-jest": "^29.6.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", - "glob": "^7.1.1", + "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-jasmine2": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", + "jest-circus": "^29.6.1", + "jest-environment-node": "^29.6.1", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-runner": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^27.5.1", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { + "@types/node": "*", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, "ts-node": { "optional": true } } }, - "node_modules/jest-config/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/jest-config/node_modules/@jest/environment": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "jest-mock": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "node_modules/jest-config/node_modules/@jest/fake-timers": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "dependencies": { - "@types/yargs-parser": "*" + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-config/node_modules/chalk": { @@ -8589,91 +8458,102 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "node_modules/jest-config/node_modules/jest-environment-node": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.1.tgz", + "integrity": "sha512-ZNIfAiE+foBog24W+2caIldl4Irh8Lx1PUhg/GZ0odM1d/h2qORAsejiFc7zb+SEmYPn1yDZzEDSU5PmDkmVLQ==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-config/node_modules/jest-mock": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-util": "^29.6.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-docblock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", - "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", + "node_modules/jest-config/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "detect-newline": "^3.0.0" + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", - "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", + "node_modules/jest-config/node_modules/pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/jest-config/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-each/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "node_modules/jest-config/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-diff": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.1.tgz", + "integrity": "sha512-FsNCvinvl8oVxpNLttNQX7FAq7vR+gMDGj90tiP7siWw1UdakWUGqrylpsYrpvj908IYckm5Y0Q7azNAozU1Kg==", "dev": true, "dependencies": { - "@types/yargs-parser": "*" + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/chalk": { + "node_modules/jest-diff/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -8689,100 +8569,188 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-environment-jsdom": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", - "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1", - "jsdom": "^16.6.0" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-jsdom/node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "detect-newline": "^3.0.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-jsdom/node_modules/@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "node_modules/jest-each": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.1.tgz", + "integrity": "sha512-n5eoj5eiTHpKQCAVcNTT7DRqeUmJ01hsAL0Q1SMiBHcBcvTKDELixQOGMCpqhbIuTcfC4kMfSnpmDqRgRJcLNQ==", "dev": true, "dependencies": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" + "@jest/types": "^29.6.1", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.6.1", + "pretty-format": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-jsdom/node_modules/@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-jsdom/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/jest-each/node_modules/pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-environment-jsdom": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.6.1.tgz", + "integrity": "sha512-PoY+yLaHzVRhVEjcVKSfJ7wXmJW4UqPYNhR05h7u/TK0ouf6DmRNZFBL/Z00zgQMyWGMBXn69/FmOvhEJu8cIw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/jsdom": "^20.0.0", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1", + "jsdom": "^20.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, - "node_modules/jest-environment-jsdom/node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "node_modules/jest-environment-jsdom/node_modules/@jest/environment": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "dependencies": { - "@sinonjs/commons": "^1.7.0" + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-mock": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-jsdom/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "node_modules/jest-environment-jsdom/node_modules/@jest/fake-timers": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "dependencies": { - "@types/yargs-parser": "*" + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-jsdom/node_modules/chalk": { @@ -8801,37 +8769,35 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-environment-jsdom/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "node_modules/jest-environment-jsdom/node_modules/jest-mock": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-util": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-jsdom/node_modules/jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "node_modules/jest-environment-jsdom/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*" + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { @@ -8991,65 +8957,39 @@ } }, "node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", "dev": true, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.1.tgz", + "integrity": "sha512-0m7f9PZXxOCk1gRACiVgX85knUKPKLPg4oRCjLoqIm9brTHXaorMA0JpmtmVkQiT8nmXyIVoZd/nnH1cfC33ig==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", + "@jest/types": "^29.6.1", + "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.1", + "jest-worker": "^29.6.1", "micromatch": "^4.0.4", - "walker": "^1.0.7" + "walker": "^1.0.8" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, - "node_modules/jest-haste-map/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-haste-map/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -9066,113 +9006,84 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-jasmine2": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", - "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", + "node_modules/jest-haste-map/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "throat": "^6.0.1" + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-jasmine2/node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "node_modules/jest-leak-detector": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.1.tgz", + "integrity": "sha512-OrxMNyZirpOEwkF3UHnIkAiZbtkBWiye+hhBweCHkVbCgyEy71Mwbb5zgeTNYWJBi1qgDVfPC1IwO9dVEeTLwQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.1" }, "engines": { - "node": ">=6.9.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-jasmine2/node_modules/@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" + "node": ">=10" }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-jasmine2/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-jasmine2/node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } + "node_modules/jest-leak-detector/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true }, - "node_modules/jest-jasmine2/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "node_modules/jest-matcher-utils": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz", + "integrity": "sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA==", "dev": true, "dependencies": { - "@types/yargs-parser": "*" + "chalk": "^4.0.0", + "jest-diff": "^29.6.1", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-jasmine2/node_modules/chalk": { + "node_modules/jest-matcher-utils/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -9188,132 +9099,70 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-jasmine2/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", - "dev": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-leak-detector": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", - "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", - "dev": true, - "dependencies": { - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-matcher-utils/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.1.tgz", + "integrity": "sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", + "@jest/types": "^29.6.1", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util/node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/jest-message-util/node_modules/@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/jest-message-util/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -9331,18 +9180,17 @@ } }, "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.6.0", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { @@ -9364,33 +9212,17 @@ "dev": true }, "node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/types": "^29.5.0", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "jest-util": "^29.5.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-mock/node_modules/chalk": { @@ -9409,10 +9241,27 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-mock/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "engines": { "node": ">=6" @@ -9427,116 +9276,145 @@ } }, "node_modules/jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", "dev": true, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", - "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.1.tgz", + "integrity": "sha512-AeRkyS8g37UyJiP9w3mmI/VXU/q8l/IH52vj/cDAyScDcemRbSBhfX/NMYIGilQgSVwsjxrCHf3XJu4f+lxCMg==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", + "jest-haste-map": "^29.6.1", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", + "resolve.exports": "^2.0.0", "slash": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", - "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.1.tgz", + "integrity": "sha512-BbFvxLXtcldaFOhNMXmHRWx1nXQO5LoXiKSGQcA1LxxirYceZT6ch8KTE1bK3X31TNG/JbkI7OkS/ABexVahiw==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-snapshot": "^27.5.1" + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-resolve-dependencies/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-resolve-dependencies/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "node_modules/jest-resolve/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@types/yargs-parser": "*" + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-resolve-dependencies/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-runner": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.1.tgz", + "integrity": "sha512-tw0wb2Q9yhjAQ2w8rHRDxteryyIck7gIzQE4Reu3JuOBpGp96xWgF0nY8MDdejzrLCZKDcp8JlZrBN/EtkQvPQ==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jest/console": "^29.6.1", + "@jest/environment": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.6.1", + "jest-haste-map": "^29.6.1", + "jest-leak-detector": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-resolve": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-util": "^29.6.1", + "jest-watcher": "^29.6.1", + "jest-worker": "^29.6.1", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-resolve/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/jest-runner/node_modules/@jest/environment": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "jest-mock": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-resolve/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "node_modules/jest-runner/node_modules/@jest/fake-timers": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "dependencies": { - "@types/yargs-parser": "*" + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-resolve/node_modules/chalk": { + "node_modules/jest-runner/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -9552,117 +9430,120 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-runner": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", - "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", + "node_modules/jest-runner/node_modules/jest-environment-node": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.1.tgz", + "integrity": "sha512-ZNIfAiE+foBog24W+2caIldl4Irh8Lx1PUhg/GZ0odM1d/h2qORAsejiFc7zb+SEmYPn1yDZzEDSU5PmDkmVLQ==", "dev": true, "dependencies": { - "@jest/console": "^27.5.1", - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-leak-detector": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "node_modules/jest-runner/node_modules/jest-mock": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-util": "^29.6.1" }, "engines": { - "node": ">=6.9.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "node_modules/jest-runner/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-mock": "^27.5.1" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", + "node_modules/jest-runtime": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.1.tgz", + "integrity": "sha512-D6/AYOA+Lhs5e5il8+5pSLemjtJezUr+8zx+Sn8xlmOux3XOqx4d8l/2udBea8CRPqqrzhsKUsN/gBDE/IcaPQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/globals": "^29.6.1", + "@jest/source-map": "^29.6.0", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/jest-runtime/node_modules/@jest/environment": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "jest-mock": "^29.6.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner/node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "node_modules/jest-runtime/node_modules/@jest/fake-timers": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "dependencies": { - "@types/yargs-parser": "*" + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/chalk": { + "node_modules/jest-runtime/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -9678,117 +9559,176 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-runner/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "node_modules/jest-runtime/node_modules/jest-mock": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-util": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "node_modules/jest-runtime/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.1.tgz", + "integrity": "sha512-G4UQE1QQ6OaCgfY+A0uR1W2AY0tGXUPQpoUClhWHq1Xdnx1H6JOrC2nH5lqnOEqaDgbHFgIwZ7bNq24HpB180A==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*" + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.6.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.6.1", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", + "natural-compare": "^1.4.0", + "pretty-format": "^29.6.1", + "semver": "^7.5.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", - "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/globals": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" + "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "node_modules/jest-snapshot/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" } }, - "node_modules/jest-runtime/node_modules/@jest/fake-timers": { + "node_modules/jest-util": { "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", "dev": true, "dependencies": { "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-runtime/node_modules/@jest/types": { + "node_modules/jest-util/node_modules/@jest/types": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", @@ -9804,16 +9744,7 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-runtime/node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/jest-runtime/node_modules/@types/yargs": { + "node_modules/jest-util/node_modules/@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", @@ -9822,7 +9753,7 @@ "@types/yargs-parser": "*" } }, - "node_modules/jest-runtime/node_modules/chalk": { + "node_modules/jest-util/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -9838,33 +9769,27 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-runtime/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/jest-validate": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.1.tgz", + "integrity": "sha512-r3Ds69/0KCN4vx4sYAbGL1EVpZ7MSS0vLmd3gV78O+NAx3PDQQukRU5hNHPXlyqCgFY8XUk7EuTMLugh0KzahA==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "@jest/types": "^29.6.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.6.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "engines": { "node": ">=10" @@ -9873,141 +9798,74 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-runtime/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/jest-runtime/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-runtime/node_modules/jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-serializer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", + "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", - "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^27.5.1", - "semver": "^7.3.2" + "node": ">=10" }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-snapshot/node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } + "node_modules/jest-validate/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true }, - "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/jest-watcher": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.1.tgz", + "integrity": "sha512-d4wpjWTS7HEZPaaj8m36QiaP856JthRZkrgcIY/7ISoUWPIillrXM23WPboZVLbiwZBt4/qn2Jke84Sla6JhFA==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.6.1", + "string-length": "^4.0.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/chalk": { + "node_modules/jest-watcher/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -10023,48 +9881,13 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-snapshot/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "node_modules/jest-watcher/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -10072,35 +9895,25 @@ "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/jest-worker": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.1.tgz", + "integrity": "sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "jest-util": "^29.6.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-util/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/chalk": { + "node_modules/jest-worker/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -10116,147 +9929,33 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-validate": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", - "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.5.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "leven": "^3.1.0", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-worker/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/jest-watcher": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", - "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", + "node_modules/jest-worker/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^27.5.1", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">= 10.13.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-worker/node_modules/supports-color": { @@ -10341,41 +10040,40 @@ "dev": true }, "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", "dev": true, "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" }, "peerDependencies": { "canvas": "^2.5.0" @@ -10387,9 +10085,9 @@ } }, "node_modules/jsdom/node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -10399,9 +10097,9 @@ } }, "node_modules/jsdom/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, "dependencies": { "asynckit": "^0.4.0", @@ -11346,6 +11044,48 @@ "node": ">= 10.13" } }, + "node_modules/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -11367,9 +11107,9 @@ } }, "node_modules/node-releases": { - "version": "1.1.75", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", - "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, "node_modules/normalize-path": { @@ -11406,9 +11146,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", "dev": true }, "node_modules/oauth-sign": { @@ -11420,6 +11160,14 @@ "node": "*" } }, + "node_modules/oauth4webapi": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.3.0.tgz", + "integrity": "sha512-JGkb5doGrwzVDuHwgrR4nHJayzN4h59VCed6EW8Tql6iHDfZIabCJvg6wtbn5q6pyB2hZruI3b77Nudvq7NmvA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -11770,6 +11518,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/package-json": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", @@ -11967,10 +11724,16 @@ } }, "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } }, "node_modules/parseurl": { "version": "1.3.3", @@ -12104,9 +11867,9 @@ } }, "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true, "engines": { "node": ">= 6" @@ -12176,15 +11939,6 @@ "node": ">=8" } }, - "node_modules/pkg-dir/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/pkg-dir/node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -12425,6 +12179,22 @@ "node": ">=8" } }, + "node_modules/pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, "node_modules/qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", @@ -12434,6 +12204,12 @@ "node": ">=0.6" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -12843,6 +12619,12 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -12897,9 +12679,9 @@ } }, "node_modules/resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, "engines": { "node": ">=10" @@ -13011,15 +12793,15 @@ "dev": true }, "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "dependencies": { "xmlchars": "^2.2.0" }, "engines": { - "node": ">=10" + "node": ">=v12.22.7" } }, "node_modules/scheduler": { @@ -13089,7 +12871,8 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true }, "node_modules/shebang-command": { "version": "2.0.0", @@ -13192,9 +12975,9 @@ } }, "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "dependencies": { "buffer-from": "^1.0.0", @@ -13346,6 +13129,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, "engines": { "node": ">= 0.6" } @@ -13555,19 +13339,6 @@ "node": ">=8" } }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -13720,22 +13491,6 @@ "rimraf": "bin.js" } }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -13756,12 +13511,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "node_modules/throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, "node_modules/throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", @@ -13817,7 +13566,7 @@ "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true, "engines": { "node": ">=4" @@ -13848,78 +13597,80 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, "engines": { "node": ">=0.6" } }, "node_modules/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dev": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", - "universalify": "^0.1.2" + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "engines": { "node": ">=6" } }, "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true, "engines": { "node": ">= 4.0.0" } }, "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", "dev": true, "dependencies": { "punycode": "^2.1.1" }, "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/ts-jest": { - "version": "27.1.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.5.tgz", - "integrity": "sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA==", + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", "dev": true, "dependencies": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", - "jest-util": "^27.0.0", - "json5": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "20.x" + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" }, "bin": { "ts-jest": "cli.js" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", - "@types/jest": "^27.0.0", - "babel-jest": ">=27.0.0 <28", - "jest": "^27.0.0", - "typescript": ">=3.8 <5.0" + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" }, "peerDependenciesMeta": { "@babel/core": { "optional": true }, - "@types/jest": { + "@jest/types": { "optional": true }, "babel-jest": { @@ -13930,6 +13681,39 @@ } } }, + "node_modules/ts-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ts-jest/node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/ts-jest/node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -13943,9 +13727,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -13957,6 +13741,15 @@ "node": ">=10" } }, + "node_modules/ts-jest/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -14199,6 +13992,36 @@ "node": ">=8" } }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/update-notifier": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", @@ -14290,6 +14113,16 @@ "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -14324,27 +14157,24 @@ "dev": true }, "node_modules/v8-to-istanbul": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", "dev": true, "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" + "convert-source-map": "^1.6.0" }, "engines": { "node": ">=10.12.0" } }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true }, "node_modules/vary": { "version": "1.1.2", @@ -14381,25 +14211,16 @@ "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", "dev": true }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", "dev": true, "dependencies": { - "xml-name-validator": "^3.0.0" + "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" } }, "node_modules/wait-on": { @@ -14444,41 +14265,58 @@ } }, "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true, "engines": { - "node": ">=10.4" + "node": ">=12" } }, "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "dependencies": { - "iconv-lite": "0.4.24" + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } }, "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "dev": true, "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/which": { @@ -14707,16 +14545,16 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -14737,10 +14575,13 @@ } }, "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } }, "node_modules/xmlchars": { "version": "2.2.0", @@ -14919,6 +14760,16 @@ "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", "dev": true }, + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", @@ -14929,226 +14780,206 @@ } }, "@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", "dev": true }, "@babel/core": { - "version": "7.15.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.5.tgz", - "integrity": "sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.5", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", + "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.9", + "@babel/helper-module-transforms": "^7.22.9", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.7", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.8", + "@babel/types": "^7.22.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" + "json5": "^2.2.2", + "semver": "^6.3.1" }, "dependencies": { "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.22.5" } }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, "@babel/generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", - "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", + "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", "dev": true, "requires": { - "@babel/types": "^7.15.4", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" } }, "@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz", + "integrity": "sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==", "dev": true, "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } } }, + "@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "dev": true + }, "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" } }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.22.5" } }, "@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.22.5" } }, "@babel/helper-module-transforms": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.4.tgz", - "integrity": "sha512-9fHHSGE9zTC++KuXLZcB5FKgvlV83Ox+NLUmQTawovwlJ85+QMhk1CnVk406CQVj97LaWod6KVjl2Sfgw9Aktw==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.14.9", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", + "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.5" } }, "@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true }, - "@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, "@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.22.5" } }, "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.22.5" } }, + "@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true + }, "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", "dev": true }, "@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", + "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", "dev": true, "requires": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5" } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -15206,9 +15037,9 @@ } }, "@babel/parser": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.6.tgz", - "integrity": "sha512-S/TSCcsRuCkmpUuoWijua0Snt+f3ewU/8spLo+4AXJCZfT0bVCzLD5MuOKdrx0mlAptbKzn5AdgEIIKXxXkz9Q==", + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -15256,6 +15087,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, "@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", @@ -15320,12 +15160,12 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/runtime": { @@ -15338,51 +15178,52 @@ } }, "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", "dev": true, "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" }, "dependencies": { "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.22.5" } } } }, "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", + "version": "7.22.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", + "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.7", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.7", + "@babel/types": "^7.22.5", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.22.5" } }, "globals": { @@ -15394,12 +15235,13 @@ } }, "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", "to-fast-properties": "^2.0.0" } }, @@ -15499,33 +15341,18 @@ } }, "@edge-runtime/jest-environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@edge-runtime/jest-environment/-/jest-environment-1.1.0.tgz", - "integrity": "sha512-YwiR+zxAt3BksS1DrOuVdqnDw+18EZxm3vpGo8vuxqKXOg9UNdmj7c3nfkdTdhQ3gr3+sSpcS/4+vLoqW1QSKA==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@edge-runtime/jest-environment/-/jest-environment-2.2.5.tgz", + "integrity": "sha512-I9JYvOrWhsso/C/OmSxaawEEw7mUpjNVIwNJncBODVhLoFkn6gkHGZVTPR0e4g4yH9yq4gwXnSdC4BS/iwTSqw==", "dev": true, "requires": { - "@edge-runtime/vm": "1.1.0", - "@jest/environment": "28.1.3", - "@jest/fake-timers": "28.1.3", - "@jest/types": "28.1.3", - "jest-mock": "28.1.3", - "jest-util": "28.1.3" + "@edge-runtime/vm": "3.0.4", + "@jest/environment": "29.5.0", + "@jest/fake-timers": "29.5.0", + "jest-mock": "29.5.0", + "jest-util": "29.5.0" }, "dependencies": { - "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -15537,12 +15364,12 @@ } }, "jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", "dev": true, "requires": { - "@jest/types": "^28.1.3", + "@jest/types": "^29.5.0", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -15553,18 +15380,18 @@ } }, "@edge-runtime/primitives": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@edge-runtime/primitives/-/primitives-1.1.0.tgz", - "integrity": "sha512-MpL5fKlOs9mz5DMRuFchLe3Il84t7XpfbPq4qtaEK37uNMCRx1MzA3d7A4aTsR/guXSZvV/AtEbKVqBWjuSThA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@edge-runtime/primitives/-/primitives-3.0.4.tgz", + "integrity": "sha512-ZisWDavCpjroNbXZ1FZ2qHqlNfTXjIcQx+7aRdC+n6DVsTShUW7S+urGj2Wags5CH0oICWRoksYX5GngW7SZzw==", "dev": true }, "@edge-runtime/vm": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@edge-runtime/vm/-/vm-1.1.0.tgz", - "integrity": "sha512-a3PSCdznoop5+ifkNDaSINB9V+Anwh+wpoaASIWhq9PLQuBF9D6Yxe/mLRZkuZRkOJ2ZmaTzMGDI5ROUChTL7g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@edge-runtime/vm/-/vm-3.0.4.tgz", + "integrity": "sha512-/QvVIn2+nO48dO3cl5tD3cjoF/xJ7WfTKBIs/oKgfmmWbEQ4EixKlOTJEpcBdOni0E8FCHIc7yxoJGq1qcLdCg==", "dev": true, "requires": { - "@edge-runtime/primitives": "1.1.0" + "@edge-runtime/primitives": "3.0.4" } }, "@eslint/eslintrc": { @@ -15664,12 +15491,6 @@ "p-limit": "^2.2.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -15691,50 +15512,19 @@ "dev": true }, "@jest/console": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", - "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.1.tgz", + "integrity": "sha512-Aj772AYgwTSr5w8qnyoJ0eDYvN6bMsH3ORH1ivMotrInHLKdUz6BDlaEXHdM6kODaBIkNIyQGzsMvRdOv7VG7Q==", "dev": true, "requires": { - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", "slash": "^3.0.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -15745,92 +15535,154 @@ "supports-color": "^7.1.0" } }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "picomatch": "^2.2.3" } } } }, "@jest/core": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", - "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.1.tgz", + "integrity": "sha512-CcowHypRSm5oYQ1obz1wfvkjZZ2qoQlrKKvlfPwh5jUXVU12TWr2qMeH8chLMuTFzHh5a1g2yaqlqDICbr+ukQ==", "dev": true, "requires": { - "@jest/console": "^27.5.1", - "@jest/reporters": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/console": "^29.6.1", + "@jest/reporters": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "emittery": "^0.8.1", + "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^27.5.1", - "jest-config": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-resolve-dependencies": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "jest-watcher": "^27.5.1", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.6.1", + "jest-haste-map": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-resolve-dependencies": "^29.6.1", + "jest-runner": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", + "jest-watcher": "^29.6.1", "micromatch": "^4.0.4", - "rimraf": "^3.0.0", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/types": "^29.6.1", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" } }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } } }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + } + } + }, + "@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + } + }, + "@jest/expect": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-N5xlPrAYaRNyFgVf2s9Uyyvr795jnB6rObuPx4QFvNJz8aAjpZUDfO4bh5G/xuplMID8PrnuF1+SfSyDxhsgYg==", + "dev": true, + "requires": { + "expect": "^29.6.1", + "jest-snapshot": "^29.6.1" + } + }, + "@jest/expect-utils": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.1.tgz", + "integrity": "sha512-o319vIf5pEMx0LmzSxxkYYxo4wrRLKHq9dP1yJU7FoPTB0LfAKSz8SWD6D/6U3v/O52t9cF5t+MeJiRsfk7zMw==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3" + } + }, + "@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "dependencies": { "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -15841,49 +15693,58 @@ "supports-color": "^7.1.0" } }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "picomatch": "^2.2.3" } } } }, - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "@jest/globals": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.1.tgz", + "integrity": "sha512-2VjpaGy78JY9n9370H8zGRCFbYVWwjY6RdDMhoJHa1sYfwe6XM/azGN0SjY8kk7BOZApIejQ1BFPyH7FPG0w3A==", "dev": true, "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" + "@jest/environment": "^29.6.1", + "@jest/expect": "^29.6.1", + "@jest/types": "^29.6.1", + "jest-mock": "^29.6.1" }, "dependencies": { - "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "@jest/environment": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "requires": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "jest-mock": "^29.6.1" + } + }, + "@jest/fake-timers": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" } }, "chalk": { @@ -15895,37 +15756,66 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-mock": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-util": "^29.6.1" + } + }, + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } } } }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "@jest/reporters": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.1.tgz", + "integrity": "sha512-9zuaI9QKr9JnoZtFQlw4GREQbxgmNYXU6QuWtmuODvk5nvPUeBYapVR/VYMyi2WSx3jXTLJTJji8rN6+Cm4+FA==", "dev": true, "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", + "jest-worker": "^29.6.1", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" }, "dependencies": { - "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -15937,12 +15827,12 @@ } }, "jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "@jest/types": "^28.1.3", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -15952,83 +15842,73 @@ } } }, - "@jest/globals": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", - "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", + "@jest/schemas": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", "dev": true, "requires": { - "@jest/environment": "^27.5.1", - "@jest/types": "^27.5.1", - "expect": "^27.5.1" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - } - }, - "@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - } - }, - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, + "@sinclair/typebox": "^0.27.8" + } + }, + "@jest/source-map": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz", + "integrity": "sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.1.tgz", + "integrity": "sha512-Ynr13ZRcpX6INak0TPUukU8GWRfm/vAytE3JbJNGAvINySWYdfE7dGZMbk36oVuK4CigpbhMn8eg1dixZ7ZJOw==", + "dev": true, + "requires": { + "@jest/console": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.1.tgz", + "integrity": "sha512-oBkC36PCDf/wb6dWeQIhaviU0l5u6VCsXa119yqdUosYAt7/FbQU2M2UoziO3igj/HBDEgp57ONQ3fm0v9uyyg==", + "dev": true, + "requires": { + "@jest/test-result": "^29.6.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.1", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.1.tgz", + "integrity": "sha512-URnTneIU3ZjRSaf906cvf6Hpox3hIeJXRnz3VDSw5/X93gR8ycdfSIEy19FlVx8NFmpN7fe3Gb1xF+NjXaQLWg==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.1", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "dependencies": { "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -16039,90 +15919,46 @@ "supports-color": "^7.1.0" } }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "picomatch": "^2.2.3" } }, - "jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*" + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" } } } }, - "@jest/reporters": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", - "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", + "@jest/types": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", "dev": true, "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/schemas": "^29.6.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -16135,138 +15971,50 @@ } } }, - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "requires": { - "@sinclair/typebox": "^0.24.1" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" } }, - "@jest/source-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", - "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", - "dev": true, - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9", - "source-map": "^0.6.0" - } + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true }, - "@jest/test-result": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", - "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", - "dev": true, - "requires": { - "@jest/console": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true }, - "@jest/test-sequencer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", - "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", - "dev": true, - "requires": { - "@jest/test-result": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-runtime": "^27.5.1" - } + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true }, - "@jest/transform": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", - "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", "dev": true, "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.5.1", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-util": "^27.5.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" }, "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true } } }, @@ -16412,9 +16160,9 @@ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" }, "@sinclair/typebox": { - "version": "0.24.38", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.38.tgz", - "integrity": "sha512-IbYB6vdhLFmzGEyXXEdFAJKyq7S4/RsivkgxNzs/LzwYuUJHmeNQ0cHkjG/Yqm6VgUzzZDLMZAf0XgeeaZAocA==", + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, "@sindresorhus/is": { @@ -16433,14 +16181,25 @@ } }, "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, + "@sinonjs/commons": "^3.0.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } + } + }, "@swc/helpers": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", @@ -16541,9 +16300,9 @@ } }, "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true }, "@types/aria-query": { @@ -16553,13 +16312,13 @@ "dev": true }, "@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", "dev": true, "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" @@ -16585,12 +16344,12 @@ } }, "@types/babel__traverse": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz", - "integrity": "sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", + "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", "dev": true, "requires": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.20.7" } }, "@types/body-parser": { @@ -16652,9 +16411,9 @@ } }, "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", "dev": true, "requires": { "@types/node": "*" @@ -16666,12 +16425,6 @@ "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", "dev": true }, - "@types/http-errors": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz", - "integrity": "sha512-2aoSC4UUbHDj2uCsCxcG/vRMXey/m17bC7UwitVm5hn22nI8O8Y9iDpA76Orc+DWkQ4zZrOKEshCqR/jSuXAHA==", - "dev": true - }, "@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -16697,13 +16450,49 @@ } }, "@types/jest": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.1.tgz", - "integrity": "sha512-HTLpVXHrY69556ozYkcq47TtQJXpcWAWfkoqz+ZGz2JnmZhzlRjprCIyFnetSy8gpDWwTTGBcRVv1J1I1vBrHw==", + "version": "29.5.3", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.3.tgz", + "integrity": "sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + } + } + }, + "@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", "dev": true, "requires": { - "jest-diff": "^27.0.0", - "pretty-format": "^27.0.0" + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" } }, "@types/json-buffer": { @@ -16754,6 +16543,29 @@ "integrity": "sha512-GXZxEtOxYGFchyUzxvKI14iff9KZ2DI+A6a37o6EQevtg6uO9t+aUZKcaC1Te5Ng1OnLM7K9NVVj+FbecD9cJg==", "dev": true }, + "@types/node-fetch": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", + "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + }, + "dependencies": { + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "@types/on-headers": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/on-headers/-/on-headers-1.0.0.tgz", @@ -16764,9 +16576,9 @@ } }, "@types/prettier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", - "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", "dev": true }, "@types/prop-types": { @@ -16926,9 +16738,9 @@ } }, "@types/yargs": { - "version": "17.0.20", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.20.tgz", - "integrity": "sha512-eknWrTHofQuPk2iuqDm1waA7V6xPlbgBoaaXEgYkClhLOnB0TtbW+srJaOToAgawPxPlHQzwypFA2bhZaUGP5A==", + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -17109,13 +16921,21 @@ "dev": true }, "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", "dev": true, "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + }, + "dependencies": { + "acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true + } } }, "acorn-jsx": { @@ -17126,9 +16946,9 @@ "requires": {} }, "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, "agent-base": { @@ -17526,43 +17346,20 @@ } }, "babel-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", - "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.1.tgz", + "integrity": "sha512-qu+3bdPEQC6KZSPz+4Fyjbga5OODNcp49j6GKzG1EKbkfyJBxEYGVUmVGpwCSeGouG52R4EgYMLb6p9YeEEQ4A==", "dev": true, "requires": { - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/transform": "^29.6.1", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.5.1", + "babel-preset-jest": "^29.5.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -17589,14 +17386,14 @@ } }, "babel-plugin-jest-hoist": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", - "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", "dev": true, "requires": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", + "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, @@ -17621,12 +17418,12 @@ } }, "babel-preset-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", - "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^27.5.1", + "babel-plugin-jest-hoist": "^29.5.0", "babel-preset-current-node-syntax": "^1.0.0" } }, @@ -17817,12 +17614,6 @@ "fill-range": "^7.0.1" } }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -17830,16 +17621,15 @@ "dev": true }, "browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" } }, "browserstack-cypress-cli": { @@ -17996,12 +17786,6 @@ "p-limit": "^2.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, "slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", @@ -18265,9 +18049,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001429", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz", - "integrity": "sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg==", + "version": "1.0.30001517", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz", + "integrity": "sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==", "dev": true }, "caseless": { @@ -18321,9 +18105,9 @@ "dev": true }, "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, "clean-stack": { @@ -18409,9 +18193,9 @@ "dev": true }, "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, "color-convert": { @@ -18535,13 +18319,10 @@ "dev": true }, "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true }, "cookie": { "version": "0.5.0", @@ -18629,9 +18410,9 @@ "dev": true }, "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", "dev": true }, "cssstyle": { @@ -18776,14 +18557,14 @@ } }, "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", "dev": true, "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" } }, "dayjs": { @@ -18807,9 +18588,9 @@ "dev": true }, "decimal.js": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", - "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, "decompress": { @@ -19009,9 +18790,9 @@ "dev": true }, "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, "defer-to-connect": { @@ -19051,7 +18832,8 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true }, "destroy": { "version": "1.2.0", @@ -19072,9 +18854,9 @@ "dev": true }, "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", "dev": true }, "dir-glob": { @@ -19110,20 +18892,12 @@ "dev": true }, "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", "dev": true, "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } + "webidl-conversions": "^7.0.0" } }, "dot-prop": { @@ -19179,15 +18953,15 @@ } }, "electron-to-chromium": { - "version": "1.3.836", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.836.tgz", - "integrity": "sha512-Ney3pHOJBWkG/AqYjrW0hr2AUCsao+2uvq9HUlRP8OlpSdk/zOHOUJP7eu0icDvePC9DlgffuelP4TnOJmMRUg==", + "version": "1.4.470", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.470.tgz", + "integrity": "sha512-zZM48Lmy2FKWgqyvsX9XK+J6FfP7aCDUFLmgooLJzA7v1agCs/sxSoBpTIwDLhmbhpx9yJIxj2INig/ncjJRqg==", "dev": true }, "emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true }, "emoji-regex": { @@ -19230,6 +19004,12 @@ "ansi-colors": "^4.1.1" } }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -19340,15 +19120,14 @@ "dev": true }, "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dev": true, "requires": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2", - "optionator": "^0.8.1", "source-map": "~0.6.1" }, "dependencies": { @@ -19357,45 +19136,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } } } }, @@ -19862,48 +19602,19 @@ "dev": true }, "expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-XEdDLonERCU1n9uR56/Stx9OqojaLAQtZf9PrCHH9Hl8YXiEIka3H4NXJ3NOIBmQJTg7+j7buh34PMHfJujc8g==", "dev": true, "requires": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" + "@jest/expect-utils": "^29.6.1", + "@types/node": "*", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1" }, "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -19914,21 +19625,18 @@ "supports-color": "^7.1.0" } }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "picomatch": "^2.2.3" } } } @@ -20010,9 +19718,9 @@ } }, "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, "requires": { "bser": "2.1.1" @@ -20547,12 +20255,12 @@ "dev": true }, "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", "dev": true, "requires": { - "whatwg-encoding": "^1.0.5" + "whatwg-encoding": "^2.0.0" } }, "html-escaper": { @@ -20581,6 +20289,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.4", @@ -20590,12 +20299,12 @@ } }, "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, "requires": { - "@tootallnate/once": "1", + "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } @@ -20709,7 +20418,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "ini": { "version": "2.0.0", @@ -21076,9 +20786,9 @@ "dev": true }, "istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "requires": { "@babel/core": "^7.12.3", @@ -21145,59 +20855,27 @@ } }, "jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", - "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.1.tgz", + "integrity": "sha512-Nirw5B4nn69rVUZtemCQhwxOBhm0nsp3hmtF4rzCeWD7BkjAXRIji7xWQfnTNbz9g0aVsBX6aZK3n+23LM6uDw==", "dev": true, "requires": { - "@jest/core": "^27.5.1", + "@jest/core": "^29.6.1", + "@jest/types": "^29.6.1", "import-local": "^3.0.2", - "jest-cli": "^27.5.1" + "jest-cli": "^29.6.1" } }, "jest-changed-files": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", - "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", "dev": true, "requires": { - "@jest/types": "^27.5.1", "execa": "^5.0.0", - "throat": "^6.0.1" + "p-limit": "^3.1.0" }, "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, "execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -21230,96 +20908,57 @@ } }, "jest-circus": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", - "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.1.tgz", + "integrity": "sha512-tPbYLEiBU4MYAL2XoZme/bgfUeotpDBd81lgHLCbDZZFaGmECk0b+/xejPFtmiBP87GgP/y4jplcRpbH+fgCzQ==", "dev": true, "requires": { - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/environment": "^29.6.1", + "@jest/expect": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^0.7.0", - "expect": "^27.5.1", "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", + "jest-each": "^29.6.1", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", + "p-limit": "^3.1.0", + "pretty-format": "^29.6.1", + "pure-rand": "^6.0.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" + "stack-utils": "^2.0.3" }, "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "@jest/environment": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-mock": "^29.6.1" } }, "@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - } - }, - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" } }, "chalk": { @@ -21332,141 +20971,196 @@ "supports-color": "^7.1.0" } }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "jest-mock": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-util": "^29.6.1" + } + }, + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "picomatch": "^2.2.3" } }, - "jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true } } }, "jest-cli": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", - "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.1.tgz", + "integrity": "sha512-607dSgTA4ODIN6go9w6xY3EYkyPFGicx51a69H7yfvt7lN53xNswEVLovq+E77VsTRi5fWprLH0yl4DJgE8Ing==", "dev": true, "requires": { - "@jest/core": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/core": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", + "jest-config": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "prompts": "^2.0.1", - "yargs": "^16.2.0" + "yargs": "^17.3.1" }, "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true } } }, "jest-config": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", - "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.1.tgz", + "integrity": "sha512-XdjYV2fy2xYixUiV2Wc54t3Z4oxYPAELUzWnV6+mcbq0rh742X2p52pii5A3oeRzYjLnQxCsZmp0qpI6klE2cQ==", "dev": true, "requires": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.5.1", - "@jest/types": "^27.5.1", - "babel-jest": "^27.5.1", + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.6.1", + "@jest/types": "^29.6.1", + "babel-jest": "^29.6.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", - "glob": "^7.1.1", + "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-jasmine2": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", + "jest-circus": "^29.6.1", + "jest-environment-node": "^29.6.1", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-runner": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^27.5.1", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "@jest/environment": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "jest-mock": "^29.6.1" } }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "@jest/fake-timers": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" } }, "chalk": { @@ -21478,19 +21172,83 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-environment-node": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.1.tgz", + "integrity": "sha512-ZNIfAiE+foBog24W+2caIldl4Irh8Lx1PUhg/GZ0odM1d/h2qORAsejiFc7zb+SEmYPn1yDZzEDSU5PmDkmVLQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" + } + }, + "jest-mock": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-util": "^29.6.1" + } + }, + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true } } }, "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.1.tgz", + "integrity": "sha512-FsNCvinvl8oVxpNLttNQX7FAq7vR+gMDGj90tiP7siWw1UdakWUGqrylpsYrpvj908IYckm5Y0Q7azNAozU1Kg==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.1" }, "dependencies": { "chalk": { @@ -21502,53 +21260,56 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true } } }, "jest-docblock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", - "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", "dev": true, "requires": { "detect-newline": "^3.0.0" } }, "jest-each": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", - "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.1.tgz", + "integrity": "sha512-n5eoj5eiTHpKQCAVcNTT7DRqeUmJ01hsAL0Q1SMiBHcBcvTKDELixQOGMCpqhbIuTcfC4kMfSnpmDqRgRJcLNQ==", "dev": true, "requires": { - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.1", "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1" + "jest-get-type": "^29.4.3", + "jest-util": "^29.6.1", + "pretty-format": "^29.6.1" }, "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -21558,88 +21319,88 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } - } - } - }, - "jest-environment-jsdom": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", - "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", - "dev": true, - "requires": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1", - "jsdom": "^16.6.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } }, - "@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-mock": "^27.5.1" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" } }, - "@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", + "pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "requires": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } } }, - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + } + } + }, + "jest-environment-jsdom": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.6.1.tgz", + "integrity": "sha512-PoY+yLaHzVRhVEjcVKSfJ7wXmJW4UqPYNhR05h7u/TK0ouf6DmRNZFBL/Z00zgQMyWGMBXn69/FmOvhEJu8cIw==", + "dev": true, + "requires": { + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1", + "jsdom": "^20.0.0" + }, + "dependencies": { + "@jest/environment": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" + "jest-mock": "^29.6.1" } }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "@jest/fake-timers": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" } }, "chalk": { @@ -21652,31 +21413,29 @@ "supports-color": "^7.1.0" } }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "jest-mock": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-util": "^29.6.1" } }, - "jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*" + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" } } } @@ -21810,54 +21569,104 @@ } }, "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", "dev": true }, "jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.1.tgz", + "integrity": "sha512-0m7f9PZXxOCk1gRACiVgX85knUKPKLPg4oRCjLoqIm9brTHXaorMA0JpmtmVkQiT8nmXyIVoZd/nnH1cfC33ig==", "dev": true, "requires": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", + "@jest/types": "^29.6.1", + "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "fsevents": "^2.3.2", "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.1", + "jest-worker": "^29.6.1", "micromatch": "^4.0.4", - "walker": "^1.0.7" + "walker": "^1.0.8" }, "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" } + } + } + }, + "jest-leak-detector": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.1.tgz", + "integrity": "sha512-OrxMNyZirpOEwkF3UHnIkAiZbtkBWiye+hhBweCHkVbCgyEy71Mwbb5zgeTNYWJBi1qgDVfPC1IwO9dVEeTLwQ==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" } }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + } + } + }, + "jest-matcher-utils": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz", + "integrity": "sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.6.1", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.1" + }, + "dependencies": { "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -21867,100 +21676,108 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true } } }, - "jest-jasmine2": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", - "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", + "jest-message-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.1.tgz", + "integrity": "sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ==", "dev": true, "requires": { - "@jest/environment": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.1", + "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "throat": "^6.0.1" + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.6.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "dependencies": { "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.5" } }, - "@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", + "pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "requires": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - } - }, - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } } }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + } + } + }, + "jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + }, + "dependencies": { "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -21971,55 +21788,50 @@ "supports-color": "^7.1.0" } }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*" + "picomatch": "^2.2.3" } } } }, - "jest-leak-detector": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", - "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, - "requires": { - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } + "requires": {} }, - "jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true + }, + "jest-resolve": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.1.tgz", + "integrity": "sha512-AeRkyS8g37UyJiP9w3mmI/VXU/q8l/IH52vj/cDAyScDcemRbSBhfX/NMYIGilQgSVwsjxrCHf3XJu4f+lxCMg==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.1", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "dependencies": { "chalk": { @@ -22031,47 +21843,86 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } } } }, - "jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "jest-resolve-dependencies": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.1.tgz", + "integrity": "sha512-BbFvxLXtcldaFOhNMXmHRWx1nXQO5LoXiKSGQcA1LxxirYceZT6ch8KTE1bK3X31TNG/JbkI7OkS/ABexVahiw==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.6.1" + } + }, + "jest-runner": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.1.tgz", + "integrity": "sha512-tw0wb2Q9yhjAQ2w8rHRDxteryyIck7gIzQE4Reu3JuOBpGp96xWgF0nY8MDdejzrLCZKDcp8JlZrBN/EtkQvPQ==", + "dev": true, + "requires": { + "@jest/console": "^29.6.1", + "@jest/environment": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", + "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.6.1", + "jest-haste-map": "^29.6.1", + "jest-leak-detector": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-resolve": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-util": "^29.6.1", + "jest-watcher": "^29.6.1", + "jest-worker": "^29.6.1", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "@jest/environment": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-mock": "^29.6.1" } }, - "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "@jest/fake-timers": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "requires": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" } }, "chalk": { @@ -22084,121 +21935,101 @@ "supports-color": "^7.1.0" } }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "jest-environment-node": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.1.tgz", + "integrity": "sha512-ZNIfAiE+foBog24W+2caIldl4Irh8Lx1PUhg/GZ0odM1d/h2qORAsejiFc7zb+SEmYPn1yDZzEDSU5PmDkmVLQ==", "dev": true, "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" } }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - } - } - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "dependencies": { - "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "jest-mock": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", "dev": true, "requires": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/types": "^29.6.1", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "jest-util": "^29.6.1" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" } } } }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "dev": true - }, - "jest-resolve": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", - "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", + "jest-runtime": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.1.tgz", + "integrity": "sha512-D6/AYOA+Lhs5e5il8+5pSLemjtJezUr+8zx+Sn8xlmOux3XOqx4d8l/2udBea8CRPqqrzhsKUsN/gBDE/IcaPQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/globals": "^29.6.1", + "@jest/source-map": "^29.6.0", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" + "jest-haste-map": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "@jest/environment": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "jest-mock": "^29.6.1" } }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "@jest/fake-timers": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" } }, "chalk": { @@ -22210,42 +22041,69 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } - } - } - }, - "jest-resolve-dependencies": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", - "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-snapshot": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + }, + "jest-mock": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/types": "^29.6.1", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "jest-util": "^29.6.1" } }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" } }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + } + } + }, + "jest-snapshot": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.1.tgz", + "integrity": "sha512-G4UQE1QQ6OaCgfY+A0uR1W2AY0tGXUPQpoUClhWHq1Xdnx1H6JOrC2nH5lqnOEqaDgbHFgIwZ7bNq24HpB180A==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.6.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.6.1", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", + "natural-compare": "^1.4.0", + "pretty-format": "^29.6.1", + "semver": "^7.5.3" + }, + "dependencies": { "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -22255,415 +22113,50 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } - } - } - }, - "jest-runner": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", - "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", - "dev": true, - "requires": { - "@jest/console": "^27.5.1", - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-leak-detector": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - } - }, - "@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - } - }, - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*" - } - } - } - }, - "jest-runtime": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", - "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", - "dev": true, - "requires": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/globals": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } }, - "@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - } - }, - "@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - } - }, - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/types": "^29.6.1", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - } - } - }, - "jest-serializer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", - "dev": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - } - }, - "jest-snapshot": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", - "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", - "dev": true, - "requires": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^27.5.1", - "semver": "^7.3.2" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" + "picomatch": "^2.2.3" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } } }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -22720,41 +22213,19 @@ } }, "jest-validate": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", - "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.1.tgz", + "integrity": "sha512-r3Ds69/0KCN4vx4sYAbGL1EVpZ7MSS0vLmd3gV78O+NAx3PDQQukRU5hNHPXlyqCgFY8XUk7EuTMLugh0KzahA==", "dev": true, "requires": { - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.1", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", + "jest-get-type": "^29.4.3", "leven": "^3.1.0", - "pretty-format": "^27.5.1" + "pretty-format": "^29.6.1" }, "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -22770,46 +22241,50 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true } } }, "jest-watcher": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", - "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.1.tgz", + "integrity": "sha512-d4wpjWTS7HEZPaaj8m36QiaP856JthRZkrgcIY/7ISoUWPIillrXM23WPboZVLbiwZBt4/qn2Jke84Sla6JhFA==", "dev": true, "requires": { - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^27.5.1", + "emittery": "^0.13.1", + "jest-util": "^29.6.1", "string-length": "^4.0.1" }, "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -22819,20 +22294,70 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } } } }, "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.1.tgz", + "integrity": "sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA==", "dev": true, "requires": { "@types/node": "*", + "jest-util": "^29.6.1", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -22899,50 +22424,49 @@ "dev": true }, "jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", "dev": true, "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" }, "dependencies": { "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true }, "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, "requires": { "asynckit": "^0.4.0", @@ -23672,6 +23196,39 @@ "propagate": "^2.0.0" } }, + "node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -23690,9 +23247,9 @@ } }, "node-releases": { - "version": "1.1.75", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", - "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, "normalize-path": { @@ -23717,9 +23274,9 @@ } }, "nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", "dev": true }, "oauth-sign": { @@ -23728,6 +23285,11 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, + "oauth4webapi": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.3.0.tgz", + "integrity": "sha512-JGkb5doGrwzVDuHwgrR4nHJayzN4h59VCed6EW8Tql6iHDfZIabCJvg6wtbn5q6pyB2hZruI3b77Nudvq7NmvA==" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -23973,6 +23535,12 @@ "aggregate-error": "^3.0.0" } }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, "package-json": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", @@ -24134,10 +23702,13 @@ } }, "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } }, "parseurl": { "version": "1.3.3", @@ -24237,9 +23808,9 @@ } }, "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true }, "pkg-dir": { @@ -24288,12 +23859,6 @@ "p-limit": "^2.2.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -24462,12 +24027,24 @@ "escape-goat": "^2.0.0" } }, + "pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true + }, "qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "dev": true }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -24784,6 +24361,12 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -24825,9 +24408,9 @@ "dev": true }, "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true }, "responselike": { @@ -24906,9 +24489,9 @@ "dev": true }, "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "requires": { "xmlchars": "^2.2.0" @@ -24973,7 +24556,8 @@ "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true }, "shebang-command": { "version": "2.0.0", @@ -25055,9 +24639,9 @@ "dev": true }, "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -25169,7 +24753,8 @@ "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true }, "stream-combiner": { "version": "0.0.4", @@ -25325,16 +24910,6 @@ "has-flag": "^4.0.0" } }, - "supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - } - }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -25453,16 +25028,6 @@ } } }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -25480,12 +25045,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, "throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", @@ -25538,7 +25097,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, "to-readable-stream": { @@ -25559,52 +25118,78 @@ "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true }, "tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dev": true, "requires": { "psl": "^1.1.33", "punycode": "^2.1.1", - "universalify": "^0.1.2" + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "dependencies": { "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true } } }, "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", "dev": true, "requires": { "punycode": "^2.1.1" } }, "ts-jest": { - "version": "27.1.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.5.tgz", - "integrity": "sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA==", + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", "dev": true, "requires": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", - "jest-util": "^27.0.0", - "json5": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "20.x" + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" }, "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, "json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -25612,13 +25197,19 @@ "dev": true }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true } } }, @@ -25804,6 +25395,16 @@ "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", "dev": true }, + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, "update-notifier": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", @@ -25876,6 +25477,16 @@ "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -25904,20 +25515,20 @@ "dev": true }, "v8-to-istanbul": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", "dev": true, "requires": { + "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" + "convert-source-map": "^1.6.0" }, "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true } } @@ -25951,22 +25562,13 @@ "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", "dev": true }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", "dev": true, "requires": { - "xml-name-validator": "^3.0.0" + "xml-name-validator": "^4.0.0" } }, "wait-on": { @@ -26002,35 +25604,45 @@ } }, "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true }, "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", "dev": true, "requires": { - "iconv-lite": "0.4.24" + "iconv-lite": "0.6.3" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } } }, "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "dev": true }, "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "dev": true, "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" } }, "which": { @@ -26206,9 +25818,9 @@ } }, "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, "requires": {} }, @@ -26219,9 +25831,9 @@ "dev": true }, "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", "dev": true }, "xmlchars": { diff --git a/package.json b/package.json index 995106b40..dfa687dad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@auth0/nextjs-auth0", - "version": "2.7.0", + "version": "3.0.0-beta.3", "description": "Next.js SDK for signing in with Auth0", "exports": { ".": "./dist/index.js", @@ -25,7 +25,7 @@ "testing.d.ts" ], "engines": { - "node": ">=12.19.0" + "node": ">=16" }, "scripts": { "clean": "rimraf dist", @@ -33,24 +33,19 @@ "lint": "tsc --noEmit && eslint --fix --ext .ts ./src", "docs": "typedoc --options typedoc.js src", "prepack": "npm run build", - "install:basic": "npm i --prefix=examples/basic-example --no-package-lock", - "install:kitchen-sink": "npm i --prefix=examples/kitchen-sink-example --no-package-lock", - "install:examples": "npm run install:basic && npm run install:kitchen-sink", + "install:example": "npm i --prefix=example-app --no-package-lock", "build": "npm run clean && tsc -p tsconfig.build.json", "build:test": "next build tests/fixtures/test-app", - "build:basic": "npm run build --prefix=examples/basic-example", - "build:kitchen-sink": "npm run build --prefix=examples/kitchen-sink-example", - "build:examples": "npm run build:basic && npm run build:kitchen-sink", - "build:vercel": "npm run install:kitchen-sink && npm run build && npm run build:kitchen-sink", - "start:basic": "npm run dev --prefix=examples/basic-example", - "start:kitchen-sink": "npm run dev --prefix=examples/kitchen-sink-example", - "start:kitchen-sink-local": "npm run dev:local --prefix=examples/kitchen-sink-example", + "build:example": "npm run build --prefix=example-app", + "build:vercel": "npm run install:example && npm run build && npm run build:example", + "start:example": "npm run dev --prefix=example-app", + "start:example-local": "npm run dev:local --prefix=example-app", "test": "jest tests --coverage", "test:watch": "jest --coverage --watch", - "test:kitchen-sink": "start-server-and-test start:kitchen-sink http-get://localhost:3000 cypress:run", - "test:kitchen-sink:watch": "start-server-and-test start:kitchen-sink 3000 cypress:open", - "test:kitchen-sink-local": "npx start-server-and-test 'start:kitchen-sink-local' http://localhost:3000 'cypress run --config-file=./cypress-local.config.js'", - "test:kitchen-sink-local:watch": "npx start-server-and-test 'start:kitchen-sink-local' http://localhost:3000 'cypress open --config-file=./cypress-local.config.js'", + "test:example": "start-server-and-test start:example http-get://localhost:3000 cypress:run", + "test:example:watch": "start-server-and-test start:example 3000 cypress:open", + "test:example-local": "npx start-server-and-test 'start:example-local' http://localhost:3000 'cypress run --config-file=./cypress-local.config.js'", + "test:example-local:watch": "npx start-server-and-test 'start:example-local' http://localhost:3000 'cypress open --config-file=./cypress-local.config.js'", "cypress:run": "cypress run", "cypress:open": "cypress open" }, @@ -73,7 +68,7 @@ }, "homepage": "https://github.com/auth0/nextjs-auth0#readme", "devDependencies": { - "@edge-runtime/jest-environment": "^1.1.0-beta.31", + "@edge-runtime/jest-environment": "^2.1.0", "@testing-library/jest-dom": "^5.11.9", "@testing-library/react": "^13.4.0", "@testing-library/react-hooks": "^5.0.3", @@ -81,10 +76,10 @@ "@types/clone": "^2.1.0", "@types/cookie": "^0.5.1", "@types/debug": "^4.1.5", - "@types/http-errors": "^1.8.0", - "@types/jest": "^27.0.1", + "@types/jest": "^29.5.2", "@types/jsonwebtoken": "^8.5.0", "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", "@types/on-headers": "^1.0.0", "@types/react": "^18.0.24", "@types/react-dom": "^17.0.9", @@ -104,10 +99,12 @@ "eslint-plugin-prettier": "^3.3.1", "eslint-plugin-react": "^7.22.0", "eslint-plugin-react-hooks": "^4.2.0", - "jest": "^27.2.0", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", "jest-environment-node-single-context": "^27.3.0", "next": "^13.4.10", "nock": "^13.0.5", + "node-fetch": "^2.6.11", "oidc-provider": "^7.6.0", "on-headers": "^1.0.2", "prettier": "^2.2.1", @@ -118,7 +115,7 @@ "start-server-and-test": "^1.11.7", "timekeeper": "^2.2.0", "tough-cookie": "^4.0.0", - "ts-jest": "^27.0.5", + "ts-jest": "^29.1.0", "typedoc": "^0.24.1", "typescript": "^4.1.3" }, @@ -126,9 +123,9 @@ "@panva/hkdf": "^1.0.2", "cookie": "^0.5.0", "debug": "^4.3.4", - "http-errors": "^1.8.1", "joi": "^17.6.0", "jose": "^4.9.2", + "oauth4webapi": "^2.3.0", "openid-client": "^5.2.1", "tslib": "^2.4.0", "url-join": "^4.0.1" @@ -137,16 +134,11 @@ "next": ">=10" }, "jest": { - "testEnvironment": "jest-environment-node-single-context", - "rootDir": ".", - "moduleFileExtensions": [ - "ts", - "tsx", - "js" - ], "collectCoverageFrom": [ "/src/**/*.*", "!/src/edge.ts", + "!/src/index.ts", + "!/src/shared.ts", "!/src/auth0-session/config.ts", "!/src/auth0-session/index.ts", "!/src/auth0-session/session-cache.ts" @@ -165,10 +157,9 @@ } }, "coverageProvider": "v8", - "preset": "ts-jest", - "globalSetup": "./tests/global-setup.ts", - "setupFilesAfterEnv": [ - "./tests/setup.ts" + "projects": [ + "/jest-node.config.js", + "/jest-edge.config.js" ] } } diff --git a/scripts/oidc-provider.js b/scripts/oidc-provider.js index 68410d6ae..cec7f3645 100644 --- a/scripts/oidc-provider.js +++ b/scripts/oidc-provider.js @@ -21,8 +21,12 @@ const config = { { client_id: 'testing', client_secret: 'testing', - redirect_uris: ['http://localhost:3000/api/auth/callback'], - post_logout_redirect_uris: ['http://localhost:3000'], + redirect_uris: [ + 'http://localhost:3000/api/auth/callback', + 'http://localhost:3000/api/page-router-auth/callback', + 'http://localhost:3000/api/edge-auth/callback' + ], + post_logout_redirect_uris: ['http://localhost:3000', 'http://localhost:3000/page-router'], token_endpoint_auth_method: 'client_secret_post', grant_types: ['authorization_code', 'refresh_token'] } @@ -41,7 +45,6 @@ const config = { enabled: true } }, - rotateRefreshToken: true, interactions: { policy } @@ -54,7 +57,7 @@ module.exports = function createProvider(opts) { provider.use(async (ctx, next) => { await next(); - if (ctx.oidc.route === 'end_session_success') { + if (ctx.oidc?.route === 'end_session_success') { ctx.redirect('http://localhost:3000'); } }); diff --git a/src/auth0-session/client.ts b/src/auth0-session/client.ts deleted file mode 100644 index d0ce9f32f..000000000 --- a/src/auth0-session/client.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { Issuer, custom, Client, EndSessionParameters, ClientAuthMethod } from 'openid-client'; -import url, { UrlObject } from 'url'; -import urlJoin from 'url-join'; -import createDebug from './utils/debug'; -import { DiscoveryError } from './utils/errors'; -import { Config } from './config'; -import { ParsedUrlQueryInput } from 'querystring'; -import { exportJWK } from 'jose'; -import { createPrivateKey } from 'crypto'; - -const debug = createDebug('client'); - -export interface ClientFactory { - (): Promise; -} - -export type Telemetry = { - name: string; - version: string; -}; - -function sortSpaceDelimitedString(str: string): string { - return str.split(' ').sort().join(' '); -} - -export default function get(config: Config, { name, version }: Telemetry): ClientFactory { - let client: Client | null = null; - - return async (): Promise => { - if (client) { - return client; - } - - custom.setHttpOptionsDefaults({ - headers: { - 'User-Agent': `${name}/${version}`, - ...(config.enableTelemetry - ? { - 'Auth0-Client': Buffer.from( - JSON.stringify({ - name, - version, - env: { - node: process.version - } - }) - ).toString('base64') - } - : undefined) - }, - timeout: config.httpTimeout - }); - - let issuer: Issuer; - try { - issuer = await Issuer.discover(config.issuerBaseURL); - } catch (e) { - throw new DiscoveryError(e, config.issuerBaseURL); - } - - const issuerTokenAlgs = Array.isArray(issuer.id_token_signing_alg_values_supported) - ? issuer.id_token_signing_alg_values_supported - : []; - if (!issuerTokenAlgs.includes(config.idTokenSigningAlg)) { - debug( - 'ID token algorithm %o is not supported by the issuer. Supported ID token algorithms are: %o.', - config.idTokenSigningAlg, - issuerTokenAlgs - ); - } - - const configRespType = sortSpaceDelimitedString(config.authorizationParams.response_type); - const issuerRespTypes = Array.isArray(issuer.response_types_supported) ? issuer.response_types_supported : []; - issuerRespTypes.map(sortSpaceDelimitedString); - if (!issuerRespTypes.includes(configRespType)) { - debug( - 'Response type %o is not supported by the issuer. Supported response types are: %o.', - configRespType, - issuerRespTypes - ); - } - - const configRespMode = config.authorizationParams.response_mode; - const issuerRespModes = Array.isArray(issuer.response_modes_supported) ? issuer.response_modes_supported : []; - if (configRespMode && !issuerRespModes.includes(configRespMode)) { - debug( - 'Response mode %o is not supported by the issuer. Supported response modes are %o.', - configRespMode, - issuerRespModes - ); - } - - let jwks; - if (config.clientAssertionSigningKey) { - const privateKey = createPrivateKey({ key: config.clientAssertionSigningKey }); - const jwk = await exportJWK(privateKey); - jwks = { keys: [jwk] }; - } - - client = new issuer.Client( - { - client_id: config.clientID, - client_secret: config.clientSecret, - id_token_signed_response_alg: config.idTokenSigningAlg, - token_endpoint_auth_method: config.clientAuthMethod as ClientAuthMethod, - token_endpoint_auth_signing_alg: config.clientAssertionSigningAlg - }, - jwks - ); - client[custom.clock_tolerance] = config.clockTolerance; - - if (config.idpLogout) { - if ( - config.auth0Logout || - ((url.parse(issuer.metadata.issuer).hostname as string).match('\\.auth0\\.com$') && - config.auth0Logout !== false) - ) { - Object.defineProperty(client, 'endSessionUrl', { - value(params: EndSessionParameters) { - const { id_token_hint, post_logout_redirect_uri, ...extraParams } = params; - const parsedUrl: UrlObject = url.parse(urlJoin(issuer.metadata.issuer, '/v2/logout')); - parsedUrl.query = { - ...extraParams, - returnTo: post_logout_redirect_uri, - client_id: config.clientID - }; - Object.entries(parsedUrl.query).forEach(([key, value]) => { - if (value === null || value === undefined) { - delete (parsedUrl.query as ParsedUrlQueryInput)[key]; - } - }); - return url.format(parsedUrl); - } - }); - } else if (!issuer.end_session_endpoint) { - debug('the issuer does not support RP-Initiated Logout'); - } - } - - return client; - }; -} diff --git a/src/auth0-session/client/abstract-client.ts b/src/auth0-session/client/abstract-client.ts new file mode 100644 index 000000000..d51771e73 --- /dev/null +++ b/src/auth0-session/client/abstract-client.ts @@ -0,0 +1,106 @@ +import { Config } from '../config'; +import { Auth0Request } from '../http'; + +export type Telemetry = { + name: string; + version: string; +}; + +export interface CallbackParamsType { + access_token?: string; + code?: string; + error?: string; + error_description?: string; + error_uri?: string; + expires_in?: string; + id_token?: string; + state?: string; + token_type?: string; + session_state?: string; + response?: string; + + [key: string]: any; +} + +export interface CallbackExtras { + exchangeBody?: Record; + clientAssertionPayload?: Record; +} + +export interface OpenIDCallbackChecks { + max_age?: number; + nonce?: string; + response_type: string; + state?: string; + code_verifier?: string; +} + +export interface TokenEndpointResponse { + access_token?: string; + token_type?: string; + id_token?: string; + refresh_token?: string; + scope?: string; + expires_in?: number; + [key: string]: unknown; +} + +export interface EndSessionParameters { + id_token_hint?: string; + post_logout_redirect_uri: string; + state?: string; + client_id?: string; + logout_hint?: string; + + [key: string]: any; +} + +export type ClientAuthMethod = 'client_secret_basic' | 'client_secret_post' | 'private_key_jwt' | 'none'; + +export interface AuthorizationParameters { + acr_values?: string; + audience?: string; + claims_locales?: string; + client_id?: string; + code_challenge_method?: string; + code_challenge?: string; + display?: string; + id_token_hint?: string; + login_hint?: string; + max_age?: number; + nonce?: string; + prompt?: string; + redirect_uri?: string; + registration?: string; + request_uri?: string; + request?: string; + resource?: string | string[]; + response_mode?: string; + response_type?: string; + scope?: string; + state?: string; + ui_locales?: string; + + [key: string]: unknown; +} + +export abstract class AbstractClient { + constructor(protected config: Config, protected telemetry: Telemetry) {} + abstract authorizationUrl(parameters: Record): Promise; + abstract callbackParams(req: Auth0Request, expectedState: string): Promise; + abstract callback( + redirectUri: string, + parameters: URLSearchParams, + checks: OpenIDCallbackChecks, + extras: CallbackExtras + ): Promise; + abstract endSessionUrl(parameters: EndSessionParameters): Promise; + abstract userinfo(accessToken: string): Promise>; + abstract refresh( + refreshToken: string, + extras: { exchangeBody?: Record } + ): Promise; + abstract generateRandomCodeVerifier(): string; + abstract generateRandomNonce(): string; + abstract calculateCodeChallenge(codeVerifier: string): Promise | string; +} diff --git a/src/auth0-session/client/edge-client.ts b/src/auth0-session/client/edge-client.ts new file mode 100644 index 000000000..0529d1c69 --- /dev/null +++ b/src/auth0-session/client/edge-client.ts @@ -0,0 +1,245 @@ +import * as oauth from 'oauth4webapi'; +import * as jose from 'jose'; +import { Auth0Request } from '../http'; +import { + CallbackExtras, + OpenIDCallbackChecks, + TokenEndpointResponse, + AbstractClient, + EndSessionParameters, + Telemetry +} from './abstract-client'; +import { ApplicationError, DiscoveryError, IdentityProviderError, UserInfoError } from '../utils/errors'; +import { AccessTokenError, AccessTokenErrorCode } from '../../utils/errors'; +import urlJoin from 'url-join'; +import { Config } from '../config'; + +const encodeBase64 = (input: string) => { + const unencoded = new TextEncoder().encode(input); + const CHUNK_SIZE = 0x8000; + const arr = []; + for (let i = 0; i < unencoded.length; i += CHUNK_SIZE) { + // @ts-expect-error Argument of type 'Uint8Array' is not assignable to parameter of type 'number[]'. + arr.push(String.fromCharCode.apply(null, unencoded.subarray(i, i + CHUNK_SIZE))); + } + return btoa(arr.join('')); +}; + +export class EdgeClient extends AbstractClient { + private client?: oauth.Client; + private as?: oauth.AuthorizationServer; + private httpOptions: () => oauth.HttpRequestOptions; + + constructor(protected config: Config, protected telemetry: Telemetry) { + super(config, telemetry); + if (config.authorizationParams.response_type !== 'code') { + throw new Error('This SDK only supports `response_type=code` when used in an Edge runtime.'); + } + + this.httpOptions = () => { + const headers = new Headers(); + if (config.enableTelemetry) { + const { name, version } = telemetry; + headers.set('User-Agent', `${name}/${version}`); + headers.set( + 'Auth0-Client', + encodeBase64( + JSON.stringify({ + name, + version, + env: { + edge: true + } + }) + ) + ); + } + return { + signal: AbortSignal.timeout(this.config.httpTimeout), + headers + }; + }; + } + + private async getClient(): Promise<[oauth.AuthorizationServer, oauth.Client]> { + if (this.as) { + return [this.as, this.client as oauth.Client]; + } + + const issuer = new URL(this.config.issuerBaseURL); + try { + this.as = await oauth + .discoveryRequest(issuer, this.httpOptions()) + .then((response) => oauth.processDiscoveryResponse(issuer, response)); + } catch (e) { + throw new DiscoveryError(e, this.config.issuerBaseURL); + } + + this.client = { + client_id: this.config.clientID, + ...(!this.config.clientAssertionSigningKey && { client_secret: this.config.clientSecret }), + token_endpoint_auth_method: this.config.clientAuthMethod, + id_token_signed_response_alg: this.config.idTokenSigningAlg, + [oauth.clockTolerance]: this.config.clockTolerance + }; + + return [this.as, this.client]; + } + + async authorizationUrl(parameters: Record): Promise { + const [as] = await this.getClient(); + const authorizationUrl = new URL(as.authorization_endpoint as string); + authorizationUrl.searchParams.set('client_id', this.config.clientID); + Object.entries(parameters).forEach(([key, value]) => { + if (value === null || value === undefined) { + return; + } + authorizationUrl.searchParams.set(key, String(value)); + }); + return authorizationUrl.toString(); + } + + async callbackParams(req: Auth0Request, expectedState: string) { + const [as, client] = await this.getClient(); + const url = + req.getMethod().toUpperCase() === 'GET' ? new URL(req.getUrl()) : new URLSearchParams(await req.getBody()); + let result: ReturnType; + try { + result = oauth.validateAuthResponse(as, client, url, expectedState); + } catch (e) { + throw new ApplicationError(e); + } + if (oauth.isOAuth2Error(result)) { + throw new IdentityProviderError({ + message: result.error_description || result.error, + error: result.error, + error_description: result.error_description + }); + } + return result; + } + + async callback( + redirectUri: string, + parameters: URLSearchParams, + checks: OpenIDCallbackChecks, + extras: CallbackExtras + ): Promise { + const [as, client] = await this.getClient(); + + const { clientAssertionSigningKey, clientAssertionSigningAlg } = this.config; + + let clientPrivateKey = clientAssertionSigningKey as CryptoKey | undefined; + /* c8 ignore next 3 */ + if (clientPrivateKey && !(clientPrivateKey instanceof CryptoKey)) { + clientPrivateKey = await jose.importPKCS8(clientPrivateKey, clientAssertionSigningAlg || 'RS256'); + } + const response = await oauth.authorizationCodeGrantRequest( + as, + client, + parameters, + redirectUri, + checks.code_verifier as string, + { + additionalParameters: extras.exchangeBody, + ...(clientPrivateKey && { clientPrivateKey }), + ...this.httpOptions() + } + ); + + const result = await oauth.processAuthorizationCodeOpenIDResponse( + as, + client, + response, + checks.nonce, + checks.max_age + ); + if (oauth.isOAuth2Error(result)) { + throw new IdentityProviderError({ + message: result.error_description || /* c8 ignore next */ result.error, + error: result.error, + error_description: result.error_description + }); + } + return result; + } + + async endSessionUrl(parameters: EndSessionParameters): Promise { + const [as] = await this.getClient(); + const issuerUrl = new URL(as.issuer); + + if ( + this.config.idpLogout && + (this.config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && this.config.auth0Logout !== false)) + ) { + const { id_token_hint, post_logout_redirect_uri, ...extraParams } = parameters; + const auth0LogoutUrl: URL = new URL(urlJoin(as.issuer, '/v2/logout')); + post_logout_redirect_uri && auth0LogoutUrl.searchParams.set('returnTo', post_logout_redirect_uri); + auth0LogoutUrl.searchParams.set('client_id', this.config.clientID); + Object.entries(extraParams).forEach(([key, value]: [string, string]) => { + if (value === null || value === undefined) { + return; + } + auth0LogoutUrl.searchParams.set(key, value); + }); + return auth0LogoutUrl.toString(); + } + if (!as.end_session_endpoint) { + throw new Error('RP Initiated Logout is not supported on your Authorization Server.'); + } + const oidcLogoutUrl = new URL(as.end_session_endpoint); + Object.entries(parameters).forEach(([key, value]: [string, string]) => { + if (value === null || value === undefined) { + return; + } + oidcLogoutUrl.searchParams.set(key, value); + }); + + oidcLogoutUrl.searchParams.set('client_id', this.config.clientID); + return oidcLogoutUrl.toString(); + } + + async userinfo(accessToken: string): Promise> { + const [as, client] = await this.getClient(); + const response = await oauth.userInfoRequest(as, client, accessToken, this.httpOptions()); + + try { + return await oauth.processUserInfoResponse(as, client, oauth.skipSubjectCheck, response); + } catch (e) { + throw new UserInfoError(e.message); + } + } + + async refresh(refreshToken: string, extras: { exchangeBody: Record }): Promise { + const [as, client] = await this.getClient(); + const res = await oauth.refreshTokenGrantRequest(as, client, refreshToken, { + additionalParameters: extras.exchangeBody, + ...this.httpOptions() + }); + const result = await oauth.processRefreshTokenResponse(as, client, res); + if (oauth.isOAuth2Error(result)) { + throw new AccessTokenError( + AccessTokenErrorCode.FAILED_REFRESH_GRANT, + 'The request to refresh the access token failed.', + new IdentityProviderError({ + message: result.error_description || /* c8 ignore next */ result.error, + error: result.error, + error_description: result.error_description + }) + ); + } + return result; + } + + generateRandomCodeVerifier(): string { + return oauth.generateRandomCodeVerifier(); + } + + generateRandomNonce(): string { + return oauth.generateRandomNonce(); + } + + calculateCodeChallenge(codeVerifier: string): Promise { + return oauth.calculatePKCECodeChallenge(codeVerifier); + } +} diff --git a/src/auth0-session/client/node-client.ts b/src/auth0-session/client/node-client.ts new file mode 100644 index 000000000..264b6a714 --- /dev/null +++ b/src/auth0-session/client/node-client.ts @@ -0,0 +1,236 @@ +import { Auth0Request } from '../http'; +import { + CallbackExtras, + CallbackParamsType, + OpenIDCallbackChecks, + TokenEndpointResponse, + AbstractClient +} from './abstract-client'; +import { + Client, + ClientAuthMethod, + custom, + CustomHttpOptionsProvider, + EndSessionParameters, + errors, + generators, + Issuer +} from 'openid-client'; +import { ApplicationError, DiscoveryError, EscapedError, IdentityProviderError, UserInfoError } from '../utils/errors'; +import { createPrivateKey } from 'crypto'; +import { exportJWK } from 'jose'; +import urlJoin from 'url-join'; +import createDebug from '../utils/debug'; +import { IncomingMessage } from 'http'; +import { AccessTokenError, AccessTokenErrorCode } from '../../utils/errors'; + +const debug = createDebug('client'); + +function sortSpaceDelimitedString(str: string): string { + return str.split(' ').sort().join(' '); +} + +export class NodeClient extends AbstractClient { + private client?: Client; + + private async getClient(): Promise { + if (this.client) { + return this.client; + } + const { + config, + telemetry: { name, version } + } = this; + + const defaultHttpOptions: CustomHttpOptionsProvider = (_url, options) => ({ + ...options, + headers: { + ...options.headers, + 'User-Agent': `${name}/${version}`, + ...(config.enableTelemetry + ? { + 'Auth0-Client': Buffer.from( + JSON.stringify({ + name, + version, + env: { + node: process.version + } + }) + ).toString('base64') + } + : undefined) + }, + timeout: config.httpTimeout + }); + const applyHttpOptionsCustom = (entity: Issuer | typeof Issuer | Client) => { + entity[custom.http_options] = defaultHttpOptions; + }; + + applyHttpOptionsCustom(Issuer); + let issuer: Issuer; + try { + issuer = await Issuer.discover(config.issuerBaseURL); + } catch (e) { + throw new DiscoveryError(e, config.issuerBaseURL); + } + applyHttpOptionsCustom(issuer); + + const issuerTokenAlgs = Array.isArray(issuer.id_token_signing_alg_values_supported) + ? issuer.id_token_signing_alg_values_supported + : []; + if (!issuerTokenAlgs.includes(config.idTokenSigningAlg)) { + debug( + 'ID token algorithm %o is not supported by the issuer. Supported ID token algorithms are: %o.', + config.idTokenSigningAlg, + issuerTokenAlgs + ); + } + + const configRespType = sortSpaceDelimitedString(config.authorizationParams.response_type); + const issuerRespTypes = Array.isArray(issuer.response_types_supported) ? issuer.response_types_supported : []; + issuerRespTypes.map(sortSpaceDelimitedString); + if (!issuerRespTypes.includes(configRespType)) { + debug( + 'Response type %o is not supported by the issuer. Supported response types are: %o.', + configRespType, + issuerRespTypes + ); + } + + const configRespMode = config.authorizationParams.response_mode; + const issuerRespModes = Array.isArray(issuer.response_modes_supported) ? issuer.response_modes_supported : []; + if (configRespMode && !issuerRespModes.includes(configRespMode)) { + debug( + 'Response mode %o is not supported by the issuer. Supported response modes are %o.', + configRespMode, + issuerRespModes + ); + } + + let jwks; + if (config.clientAssertionSigningKey) { + const privateKey = createPrivateKey({ key: config.clientAssertionSigningKey as string }); + const jwk = await exportJWK(privateKey); + jwks = { keys: [jwk] }; + } + + this.client = new issuer.Client( + { + client_id: config.clientID, + client_secret: config.clientSecret, + id_token_signed_response_alg: config.idTokenSigningAlg, + token_endpoint_auth_method: config.clientAuthMethod as ClientAuthMethod, + token_endpoint_auth_signing_alg: config.clientAssertionSigningAlg + }, + jwks + ); + applyHttpOptionsCustom(this.client); + + this.client[custom.clock_tolerance] = config.clockTolerance; + const issuerUrl = new URL(issuer.metadata.issuer); + + if (config.idpLogout) { + if ( + this.config.idpLogout && + (this.config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && this.config.auth0Logout !== false)) + ) { + Object.defineProperty(this.client, 'endSessionUrl', { + value(params: EndSessionParameters) { + const { id_token_hint, post_logout_redirect_uri, ...extraParams } = params; + const parsedUrl = new URL(urlJoin(issuer.metadata.issuer, '/v2/logout')); + parsedUrl.searchParams.set('client_id', config.clientID); + post_logout_redirect_uri && parsedUrl.searchParams.set('returnTo', post_logout_redirect_uri); + Object.entries(extraParams).forEach(([key, value]) => { + if (value === null || value === undefined) { + return; + } + parsedUrl.searchParams.set(key, value as string); + }); + return parsedUrl.toString(); + } + }); + } else if (!issuer.end_session_endpoint) { + debug('the issuer does not support RP-Initiated Logout'); + } + } + + return this.client; + } + + async authorizationUrl(parameters: Record): Promise { + const client = await this.getClient(); + return client.authorizationUrl(parameters); + } + + async callbackParams(req: Auth0Request) { + const client = await this.getClient(); + const obj: CallbackParamsType = client.callbackParams({ + method: req.getMethod(), + url: req.getUrl(), + body: await req.getBody() + } as unknown as IncomingMessage); + return new URLSearchParams(obj); + } + + async callback( + redirectUri: string, + parameters: URLSearchParams, + checks: OpenIDCallbackChecks, + extras: CallbackExtras + ): Promise { + const params = Object.fromEntries(parameters.entries()); + const client = await this.getClient(); + try { + return await client.callback(redirectUri, params, checks, extras); + } catch (err) { + if (err instanceof errors.OPError) { + throw new IdentityProviderError(err); + } else if (err instanceof errors.RPError) { + throw new ApplicationError(err); + /* c8 ignore next 3 */ + } else { + throw new EscapedError(err.message); + } + } + } + + async endSessionUrl(parameters: EndSessionParameters): Promise { + const client = await this.getClient(); + return client.endSessionUrl(parameters); + } + + async userinfo(accessToken: string): Promise> { + const client = await this.getClient(); + try { + return await client.userinfo(accessToken); + } catch (e) { + throw new UserInfoError(e.message); + } + } + + async refresh(refreshToken: string, extras: { exchangeBody: Record }): Promise { + const client = await this.getClient(); + try { + return await client.refresh(refreshToken, extras); + } catch (e) { + throw new AccessTokenError( + AccessTokenErrorCode.FAILED_REFRESH_GRANT, + 'The request to refresh the access token failed.', + new IdentityProviderError(e as errors.OPError) + ); + } + } + + generateRandomCodeVerifier(): string { + return generators.codeVerifier(); + } + + generateRandomNonce(): string { + return generators.nonce(); + } + + calculateCodeChallenge(codeVerifier: string): string { + return generators.codeChallenge(codeVerifier); + } +} diff --git a/src/auth0-session/config.ts b/src/auth0-session/config.ts index f2e99dbba..1963d6125 100644 --- a/src/auth0-session/config.ts +++ b/src/auth0-session/config.ts @@ -1,5 +1,7 @@ -import type { IncomingMessage } from 'http'; -import type { AuthorizationParameters as OidcAuthorizationParameters, ClientAuthMethod } from 'openid-client'; +import type { + AuthorizationParameters as OidcAuthorizationParameters, + ClientAuthMethod +} from './client/abstract-client'; import { SessionStore } from './session/stateful-session'; /** @@ -111,7 +113,7 @@ export interface Config { * ```js * app.use(auth({ * ... - * getLoginState(req, options) { + * getLoginState(options) { * return { * returnTo: options.returnTo || req.originalUrl, * customState: 'foo' @@ -120,7 +122,7 @@ export interface Config { * })); * ``` */ - getLoginState: (req: IncomingMessage, options: LoginOptions) => Record; + getLoginState: (options: LoginOptions) => Record; /** * Array value of claims to remove from the ID token before storing the cookie session. @@ -173,8 +175,10 @@ export interface Config { * Private key for use with `private_key_jwt` clients. * This should be a string that is the contents of a PEM file. * you can also use the `AUTH0_CLIENT_ASSERTION_SIGNING_KEY` environment variable. + * + * For Edge runtime, you can also provide an instance of `CryptoKey`. */ - clientAssertionSigningKey?: string; + clientAssertionSigningKey?: string | CryptoKey; /** * The algorithm used to sign the client assertion JWT. @@ -302,7 +306,7 @@ export interface AuthorizationParameters extends OidcAuthorizationParameters { response_type: 'id_token' | 'code id_token' | 'code'; } -export type GetLoginState = (req: any, options: LoginOptions) => { [key: string]: any }; +export type GetLoginState = (options: LoginOptions) => { [key: string]: any }; /** * Custom options to pass to the login handler. diff --git a/src/auth0-session/handlers/callback.ts b/src/auth0-session/handlers/callback.ts index 907bd2c31..441d679e8 100644 --- a/src/auth0-session/handlers/callback.ts +++ b/src/auth0-session/handlers/callback.ts @@ -1,31 +1,17 @@ -import { IncomingMessage, ServerResponse } from 'http'; import urlJoin from 'url-join'; -import createHttpError from 'http-errors'; -import { errors } from 'openid-client'; import { AuthorizationParameters, Config } from '../config'; -import { ClientFactory } from '../client'; import TransientStore from '../transient-store'; import { decodeState } from '../utils/encoding'; import { SessionCache } from '../session-cache'; -import { - ApplicationError, - EscapedError, - htmlSafe, - IdentityProviderError, - MissingStateCookieError, - MissingStateParamError -} from '../utils/errors'; +import { MissingStateCookieError, MissingStateParamError } from '../utils/errors'; +import { Auth0Request, Auth0Response } from '../http'; +import { AbstractClient } from '../client/abstract-client'; function getRedirectUri(config: Config): string { return urlJoin(config.baseURL, config.routes.callback); } -export type AfterCallback = ( - req: any, - res: any, - session: any, - state?: Record -) => Promise | any | undefined; +export type AfterCallback = (session: any, state?: Record) => Promise | any | undefined; export type CallbackOptions = { afterCallback?: AfterCallback; @@ -35,34 +21,37 @@ export type CallbackOptions = { authorizationParams?: Partial; }; -type ValidState = { [key: string]: any; returnTo?: string }; - -export type HandleCallback = (req: IncomingMessage, res: ServerResponse, options?: CallbackOptions) => Promise; +export type HandleCallback = (req: Auth0Request, res: Auth0Response, options?: CallbackOptions) => Promise; export default function callbackHandlerFactory( config: Config, - getClient: ClientFactory, + client: AbstractClient, sessionCache: SessionCache, transientCookieHandler: TransientStore ): HandleCallback { return async (req, res, options) => { - const client = await getClient(); const redirectUri = options?.redirectUri || getRedirectUri(config); - let tokenSet; - - const callbackParams = client.callbackParams(req); - - if (!callbackParams.state) { - throw createHttpError(404, new MissingStateParamError()); - } + let tokenResponse; const expectedState = await transientCookieHandler.read('state', req, res); - if (!expectedState) { - throw createHttpError(400, new MissingStateCookieError()); + throw new MissingStateCookieError(); } + let callbackParams: URLSearchParams; + try { + callbackParams = await client.callbackParams(req, expectedState); + } catch (err) { + err.status = 400; + err.statusCode = 400; + err.openIdState = decodeState(expectedState); + throw err; + } + + if (!callbackParams.get('state')) { + throw new MissingStateParamError(); + } const max_age = await transientCookieHandler.read('max_age', req, res); const code_verifier = await transientCookieHandler.read('code_verifier', req, res); const nonce = await transientCookieHandler.read('nonce', req, res); @@ -70,7 +59,7 @@ export default function callbackHandlerFactory( (await transientCookieHandler.read('response_type', req, res)) || config.authorizationParams.response_type; try { - tokenSet = await client.callback( + tokenResponse = await client.callback( redirectUri, callbackParams, { @@ -83,33 +72,23 @@ export default function callbackHandlerFactory( { exchangeBody: options?.authorizationParams } ); } catch (err) { - if (err instanceof errors.OPError) { - err = new IdentityProviderError(err); - } else if (err instanceof errors.RPError) { - err = new ApplicationError(err); - /* c8 ignore next 3 */ - } else { - err = new EscapedError(err.message); - } - throw createHttpError(400, err, { openIdState: decodeState(expectedState) }); + err.status = 400; + err.statusCode = 400; + err.openIdState = decodeState(expectedState); + throw err; } - const openidState: { returnTo?: string } = decodeState(expectedState as string) as ValidState; - let session = await sessionCache.fromTokenSet(tokenSet); + const openidState: { returnTo?: string } = decodeState(expectedState as string)!; + let session = sessionCache.fromTokenEndpointResponse(tokenResponse); if (options?.afterCallback) { - session = await options.afterCallback(req, res, session, openidState); + session = await options.afterCallback(session, openidState); } if (session) { - await sessionCache.create(req, res, session); + await sessionCache.create(req.req, res.res, session); } - if (!res.writableEnded) { - res.writeHead(302, { - Location: res.getHeader('Location') || openidState.returnTo || config.baseURL - }); - res.end(htmlSafe(openidState.returnTo || config.baseURL)); - } + res.redirect(openidState.returnTo || config.baseURL); }; } diff --git a/src/auth0-session/handlers/login.ts b/src/auth0-session/handlers/login.ts index 1a745471d..fb5700f40 100644 --- a/src/auth0-session/handlers/login.ts +++ b/src/auth0-session/handlers/login.ts @@ -1,12 +1,10 @@ -import { IncomingMessage, ServerResponse } from 'http'; import urlJoin from 'url-join'; -import { strict as assert } from 'assert'; import { Config, LoginOptions } from '../config'; import TransientStore, { StoreOptions } from '../transient-store'; import { encodeState } from '../utils/encoding'; -import { ClientFactory } from '../client'; import createDebug from '../utils/debug'; -import { htmlSafe } from '../utils/errors'; +import { Auth0Request, Auth0Response } from '../http'; +import { AbstractClient } from '../client/abstract-client'; const debug = createDebug('handlers'); @@ -14,16 +12,14 @@ function getRedirectUri(config: Config): string { return urlJoin(config.baseURL, config.routes.callback); } -export type HandleLogin = (req: IncomingMessage, res: ServerResponse, options?: LoginOptions) => Promise; +export type HandleLogin = (req: Auth0Request, res: Auth0Response, options?: LoginOptions) => Promise; export default function loginHandlerFactory( config: Config, - getClient: ClientFactory, + client: AbstractClient, transientHandler: TransientStore ): HandleLogin { return async (req, res, options = {}) => { - const client = await getClient(); - const returnTo = options.returnTo || config.baseURL; const opts = { @@ -39,22 +35,22 @@ export default function loginHandlerFactory( ...(opts.authorizationParams || {}) }; - const transientOpts: StoreOptions = { + const transientOpts: Pick = { sameSite: opts.authorizationParams.response_mode === 'form_post' ? 'none' : config.session.cookie.sameSite }; - const stateValue = await opts.getLoginState(req as any, opts); + const stateValue = await opts.getLoginState(opts); if (typeof stateValue !== 'object') { throw new Error('Custom state value must be an object.'); } - stateValue.nonce = transientHandler.generateNonce(); + stateValue.nonce = client.generateRandomNonce(); stateValue.returnTo = stateValue.returnTo || opts.returnTo; const responseType = opts.authorizationParams.response_type as string; const usePKCE = responseType.includes('code'); if (usePKCE) { debug('response_type includes code, the authorization request will use PKCE'); - stateValue.code_verifier = transientHandler.generateCodeVerifier(); + stateValue.code_verifier = client.generateRandomCodeVerifier(); } if (responseType !== config.authorizationParams.response_type) { @@ -66,15 +62,18 @@ export default function loginHandlerFactory( const authParams = { ...opts.authorizationParams, - nonce: await transientHandler.save('nonce', req, res, transientOpts), + nonce: await transientHandler.save('nonce', req, res, { ...transientOpts, value: client.generateRandomNonce() }), state: await transientHandler.save('state', req, res, { ...transientOpts, value: encodeState(stateValue) }), ...(usePKCE ? { - code_challenge: transientHandler.calculateCodeChallenge( - await transientHandler.save('code_verifier', req, res, transientOpts) + code_challenge: await client.calculateCodeChallenge( + await transientHandler.save('code_verifier', req, res, { + ...transientOpts, + value: client.generateRandomCodeVerifier() + }) ), code_challenge_method: 'S256' } @@ -82,11 +81,12 @@ export default function loginHandlerFactory( }; const validResponseTypes = ['id_token', 'code id_token', 'code']; - assert( - validResponseTypes.includes(authParams.response_type as string), - `response_type should be one of ${validResponseTypes.join(', ')}` - ); - assert(/\bopenid\b/.test(authParams.scope as string), 'scope should contain "openid"'); + if (!validResponseTypes.includes(authParams.response_type as string)) { + throw new Error(`response_type should be one of ${validResponseTypes.join(', ')}`); + } + if (!/\bopenid\b/.test(authParams.scope as string)) { + throw new Error('scope should contain "openid"'); + } if (authParams.max_age) { await transientHandler.save('max_age', req, res, { @@ -95,12 +95,9 @@ export default function loginHandlerFactory( }); } - const authorizationUrl = client.authorizationUrl(authParams); + const authorizationUrl = await client.authorizationUrl(authParams); debug('redirecting to %s', authorizationUrl); - res.writeHead(302, { - Location: authorizationUrl - }); - res.end(htmlSafe(authorizationUrl)); + res.redirect(authorizationUrl); }; } diff --git a/src/auth0-session/handlers/logout.ts b/src/auth0-session/handlers/logout.ts index 7154e4316..b511b8317 100644 --- a/src/auth0-session/handlers/logout.ts +++ b/src/auth0-session/handlers/logout.ts @@ -1,62 +1,52 @@ -import { IncomingMessage, ServerResponse } from 'http'; -import url from 'url'; import urlJoin from 'url-join'; import createDebug from '../utils/debug'; import { Config, LogoutOptions } from '../config'; -import { ClientFactory } from '../client'; import { SessionCache } from '../session-cache'; -import { htmlSafe } from '../utils/errors'; +import { Auth0Request, Auth0Response } from '../http'; +import { AbstractClient } from '../client/abstract-client'; const debug = createDebug('logout'); -export type HandleLogout = (req: IncomingMessage, res: ServerResponse, options?: LogoutOptions) => Promise; +export type HandleLogout = (req: Auth0Request, res: Auth0Response, options?: LogoutOptions) => Promise; export default function logoutHandlerFactory( config: Config, - getClient: ClientFactory, + client: AbstractClient, sessionCache: SessionCache ): HandleLogout { return async (req, res, options = {}) => { let returnURL = options.returnTo || config.routes.postLogoutRedirect; debug('logout() with return url: %s', returnURL); - if (url.parse(returnURL).host === null) { + try { + new URL(returnURL); + } catch (_) { returnURL = urlJoin(config.baseURL, returnURL); } - const isAuthenticated = await sessionCache.isAuthenticated(req, res); + const isAuthenticated = await sessionCache.isAuthenticated(req.req, res.res); if (!isAuthenticated) { debug('end-user already logged out, redirecting to %s', returnURL); - res.writeHead(302, { - Location: returnURL - }); - res.end(htmlSafe(returnURL)); + res.redirect(returnURL); return; } - const idToken = await sessionCache.getIdToken(req, res); - await sessionCache.delete(req, res); + const idToken = await sessionCache.getIdToken(req.req, res.res); + await sessionCache.delete(req.req, res.res); if (!config.idpLogout) { debug('performing a local only logout, redirecting to %s', returnURL); - res.writeHead(302, { - Location: returnURL - }); - res.end(htmlSafe(returnURL)); + res.redirect(returnURL); return; } - const client = await getClient(); - returnURL = client.endSessionUrl({ + returnURL = await client.endSessionUrl({ ...options.logoutParams, post_logout_redirect_uri: returnURL, id_token_hint: idToken }); debug('logging out of identity provider, redirecting to %s', returnURL); - res.writeHead(302, { - Location: returnURL - }); - res.end(htmlSafe(returnURL)); + res.redirect(returnURL); }; } diff --git a/src/auth0-session/hooks/get-login-state.ts b/src/auth0-session/hooks/get-login-state.ts index aade6b1e6..06e34e179 100644 --- a/src/auth0-session/hooks/get-login-state.ts +++ b/src/auth0-session/hooks/get-login-state.ts @@ -13,7 +13,7 @@ const debug = createDebug('get-login-state'); * * @return {object} */ -export const getLoginState: GetLoginState = (_req, options) => { +export const getLoginState: GetLoginState = (options) => { const state = { returnTo: options.returnTo }; debug('adding default state %O', state); return state; diff --git a/src/auth0-session/http/auth0-request-cookies.ts b/src/auth0-session/http/auth0-request-cookies.ts new file mode 100644 index 000000000..eb4ce44d5 --- /dev/null +++ b/src/auth0-session/http/auth0-request-cookies.ts @@ -0,0 +1,3 @@ +export default abstract class Auth0RequestCookies { + public abstract getCookies(): Record; +} diff --git a/src/auth0-session/http/auth0-request.ts b/src/auth0-session/http/auth0-request.ts new file mode 100644 index 000000000..1bda10f61 --- /dev/null +++ b/src/auth0-session/http/auth0-request.ts @@ -0,0 +1,11 @@ +import Auth0RequestCookies from './auth0-request-cookies'; + +export default abstract class Auth0Request extends Auth0RequestCookies { + protected constructor(public req: Req) { + super(); + } + + public abstract getUrl(): string; + public abstract getMethod(): string; + public abstract getBody(): Promise | string> | Record | string; +} diff --git a/src/auth0-session/http/auth0-response-cookies.ts b/src/auth0-session/http/auth0-response-cookies.ts new file mode 100644 index 000000000..658aa48df --- /dev/null +++ b/src/auth0-session/http/auth0-response-cookies.ts @@ -0,0 +1,21 @@ +import { CookieSerializeOptions } from 'cookie'; + +export default abstract class Auth0ResponseCookies { + public abstract setCookie(name: string, value: string, options?: CookieSerializeOptions): void; + + public clearCookie(name: string, options: CookieSerializeOptions = {}): void { + const { domain, path, secure, sameSite } = options; + const clearOptions: CookieSerializeOptions = { + domain, + path, + maxAge: 0 + }; + // If SameSite=None is set, the cookie Secure attribute must also be set (or the cookie will be blocked) + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#none + if (sameSite === 'none') { + clearOptions.secure = secure; + clearOptions.sameSite = sameSite; + } + this.setCookie(name, '', clearOptions); + } +} diff --git a/src/auth0-session/http/auth0-response.ts b/src/auth0-session/http/auth0-response.ts new file mode 100644 index 000000000..87d0565be --- /dev/null +++ b/src/auth0-session/http/auth0-response.ts @@ -0,0 +1,9 @@ +import Auth0ResponseCookies from './auth0-response-cookies'; + +export default abstract class Auth0Response extends Auth0ResponseCookies { + protected constructor(public res: Res) { + super(); + } + + public abstract redirect(location: string, status?: number): void; +} diff --git a/src/auth0-session/http/index.ts b/src/auth0-session/http/index.ts new file mode 100644 index 000000000..8779b06e1 --- /dev/null +++ b/src/auth0-session/http/index.ts @@ -0,0 +1,6 @@ +export { default as Auth0Request } from './auth0-request'; +export { default as Auth0Response } from './auth0-response'; +export { default as Auth0RequestCookies } from './auth0-request-cookies'; +export { default as Auth0ResponseCookies } from './auth0-response-cookies'; +export { default as NodeRequest } from './node-request'; +export { default as NodeResponse } from './node-response'; diff --git a/src/auth0-session/http/node-request.ts b/src/auth0-session/http/node-request.ts new file mode 100644 index 000000000..d08241363 --- /dev/null +++ b/src/auth0-session/http/node-request.ts @@ -0,0 +1,26 @@ +import { IncomingMessage } from 'http'; +import { parse } from 'cookie'; +import Auth0Request from './auth0-request'; + +export default class NodeRequest extends Auth0Request { + public constructor(public req: IncomingMessage) { + /* c8 ignore next */ + super(req); + } + + public getUrl() { + return this.req.url as string; + } + + public getMethod() { + return this.req.method as string; + } + + public getBody() { + return (this.req as IncomingMessage & { body: Record }).body; + } + + public getCookies(): Record { + return parse(this.req.headers.cookie || ''); + } +} diff --git a/src/auth0-session/http/node-response.ts b/src/auth0-session/http/node-response.ts new file mode 100644 index 000000000..733866064 --- /dev/null +++ b/src/auth0-session/http/node-response.ts @@ -0,0 +1,33 @@ +import { ServerResponse } from 'http'; +import { CookieSerializeOptions, serialize } from 'cookie'; +import Auth0Response from './auth0-response'; +import { htmlSafe } from '../utils/errors'; + +export default class NodeResponse extends Auth0Response { + public constructor(public res: T) { + /* c8 ignore next */ + super(res); + } + + public setCookie(name: string, value: string, options?: CookieSerializeOptions) { + let cookies = this.res.getHeader('Set-Cookie') || []; + if (!Array.isArray(cookies)) { + cookies = [cookies as string]; + } + + this.res.setHeader('Set-Cookie', [ + ...cookies.filter((cookie) => !cookie.startsWith(`${name}=`)), + serialize(name, value, options) + ]); + } + + public redirect(location: string, status = 302): void { + if (this.res.writableEnded) { + return; + } + this.res.writeHead(status, { + Location: this.res.getHeader('Location') || location + }); + this.res.end(htmlSafe(location)); + } +} diff --git a/src/auth0-session/index.ts b/src/auth0-session/index.ts index fad584a89..d1becc606 100644 --- a/src/auth0-session/index.ts +++ b/src/auth0-session/index.ts @@ -1,4 +1,3 @@ -export { default as NodeCookies, Cookies } from './utils/cookies'; export { MissingStateParamError, MissingStateCookieError, @@ -14,5 +13,5 @@ export { get as getConfig, ConfigParameters, DeepPartial } from './get-config'; export { default as loginHandler, HandleLogin } from './handlers/login'; export { default as logoutHandler, HandleLogout } from './handlers/logout'; export { default as callbackHandler, CallbackOptions, AfterCallback, HandleCallback } from './handlers/callback'; -export { default as clientFactory, ClientFactory } from './client'; +export { TokenEndpointResponse, AbstractClient } from './client/abstract-client'; export { SessionCache } from './session-cache'; diff --git a/src/auth0-session/session-cache.ts b/src/auth0-session/session-cache.ts index 5dcc1f756..d59fd9068 100644 --- a/src/auth0-session/session-cache.ts +++ b/src/auth0-session/session-cache.ts @@ -1,10 +1,9 @@ -import type { TokenSet } from 'openid-client'; -import type { IncomingMessage, ServerResponse } from 'http'; +import { TokenEndpointResponse } from './client/abstract-client'; -export interface SessionCache { +export interface SessionCache { create(req: Req, res: Res, session: Session): Promise; delete(req: Req, res: Res): Promise; isAuthenticated(req: Req, res: Res): Promise; getIdToken(req: Req, res: Res): Promise; - fromTokenSet(tokenSet: TokenSet): Session; + fromTokenEndpointResponse(tokenSet: TokenEndpointResponse): Session; } diff --git a/src/auth0-session/session/abstract-session.ts b/src/auth0-session/session/abstract-session.ts index 5d6f9ecb2..76f158ace 100644 --- a/src/auth0-session/session/abstract-session.ts +++ b/src/auth0-session/session/abstract-session.ts @@ -1,7 +1,7 @@ import createDebug from '../utils/debug'; import { CookieSerializeOptions } from 'cookie'; import { Config } from '../config'; -import { Cookies } from '../utils/cookies'; +import { Auth0RequestCookies, Auth0ResponseCookies } from '../http'; const debug = createDebug('session'); @@ -35,14 +35,14 @@ const assert = (bool: boolean, msg: string) => { } }; -export abstract class AbstractSession { - constructor(protected config: Config, protected Cookies: new () => Cookies) {} +export abstract class AbstractSession { + constructor(protected config: Config) {} - abstract getSession(req: Req): Promise | undefined | null>; + abstract getSession(req: Auth0RequestCookies): Promise | undefined | null>; abstract setSession( - req: Req, - res: Res, + req: Auth0RequestCookies, + res: Auth0ResponseCookies, session: Session, uat: number, iat: number, @@ -51,9 +51,13 @@ export abstract class AbstractSession { isNewSession: boolean ): Promise; - abstract deleteSession(req: Req, res: Res, cookieOptions: CookieSerializeOptions): Promise; + abstract deleteSession( + req: Auth0RequestCookies, + res: Auth0ResponseCookies, + cookieOptions: CookieSerializeOptions + ): Promise; - public async read(req: Req): Promise<[Session?, number?]> { + public async read(req: Auth0RequestCookies): Promise<[Session?, number?]> { const { rollingDuration, absoluteDuration } = this.config.session; try { @@ -85,7 +89,12 @@ export abstract class AbstractSession { return []; } - public async save(req: Req, res: Res, session: Session | null | undefined, createdAt?: number): Promise { + public async save( + req: Auth0RequestCookies, + res: Auth0ResponseCookies, + session: Session | null | undefined, + createdAt?: number + ): Promise { const { cookie: { transient, ...cookieConfig } } = this.config.session; diff --git a/src/auth0-session/session/stateful-session.ts b/src/auth0-session/session/stateful-session.ts index eb56d4305..c83cf84d3 100644 --- a/src/auth0-session/session/stateful-session.ts +++ b/src/auth0-session/session/stateful-session.ts @@ -1,10 +1,10 @@ import { CookieSerializeOptions } from 'cookie'; import createDebug from '../utils/debug'; import { Config } from '../config'; -import { Cookies } from '../utils/cookies'; import { AbstractSession, SessionPayload } from './abstract-session'; import { generateCookieValue, getCookieValue } from '../utils/signed-cookies'; import { signing } from '../utils/hkdf'; +import { Auth0RequestCookies, Auth0ResponseCookies } from '../http'; const debug = createDebug('stateful-session'); @@ -26,15 +26,13 @@ export interface SessionStore { } export class StatefulSession< - Req, - Res, Session extends { [key: string]: any } = { [key: string]: any } -> extends AbstractSession { +> extends AbstractSession { private keys?: Uint8Array[]; private store: SessionStore; - constructor(protected config: Config, protected Cookies: new () => Cookies) { - super(config, Cookies); + constructor(protected config: Config) { + super(config); this.store = config.session.store as SessionStore; } @@ -47,9 +45,9 @@ export class StatefulSession< return this.keys; } - async getSession(req: Req): Promise | undefined | null> { + async getSession(req: Auth0RequestCookies): Promise | undefined | null> { const { name: sessionName } = this.config.session; - const cookies = new this.Cookies().getAll(req); + const cookies = req.getCookies(); const keys = await this.getKeys(); const sessionId = await getCookieValue(sessionName, cookies[sessionName], keys); @@ -61,8 +59,8 @@ export class StatefulSession< } async setSession( - req: Req, - res: Res, + req: Auth0RequestCookies, + res: Auth0ResponseCookies, session: Session, uat: number, iat: number, @@ -71,8 +69,7 @@ export class StatefulSession< isNewSession: boolean ): Promise { const { name: sessionName, genId } = this.config.session; - const cookieSetter = new this.Cookies(); - const cookies = cookieSetter.getAll(req); + const cookies = req.getCookies(); const keys = await this.getKeys(); let sessionId = await getCookieValue(sessionName, cookies[sessionName], keys); @@ -90,25 +87,26 @@ export class StatefulSession< } debug('set session %o', sessionId); const cookieValue = await generateCookieValue(sessionName, sessionId, keys[0]); - cookieSetter.set(sessionName, cookieValue, cookieOptions); - cookieSetter.commit(res); + res.setCookie(sessionName, cookieValue, cookieOptions); await this.store.set(sessionId, { header: { iat, uat, exp }, data: session }); } - async deleteSession(req: Req, res: Res, cookieOptions: CookieSerializeOptions): Promise { + async deleteSession( + req: Auth0RequestCookies, + res: Auth0ResponseCookies, + cookieOptions: CookieSerializeOptions + ): Promise { const { name: sessionName } = this.config.session; - const cookieSetter = new this.Cookies(); - const cookies = cookieSetter.getAll(req); + const cookies = req.getCookies(); const keys = await this.getKeys(); const sessionId = await getCookieValue(sessionName, cookies[sessionName], keys); if (sessionId) { debug('deleting session %o', sessionId); - cookieSetter.clear(sessionName, cookieOptions); - cookieSetter.commit(res); + res.clearCookie(sessionName, cookieOptions); await this.store.delete(sessionId); } } diff --git a/src/auth0-session/session/stateless-session.ts b/src/auth0-session/session/stateless-session.ts index 40e737918..34ee07bcc 100644 --- a/src/auth0-session/session/stateless-session.ts +++ b/src/auth0-session/session/stateless-session.ts @@ -2,9 +2,9 @@ import * as jose from 'jose'; import { CookieSerializeOptions, serialize } from 'cookie'; import createDebug from '../utils/debug'; import { Config } from '../config'; -import { Cookies } from '../utils/cookies'; import { encryption } from '../utils/hkdf'; import { AbstractSession, Header, SessionPayload } from './abstract-session'; +import { Auth0RequestCookies, Auth0ResponseCookies } from '../http'; const debug = createDebug('stateless-session'); @@ -15,15 +15,13 @@ const enc = 'A256GCM'; const notNull = (value: T | null): value is T => value !== null; export class StatelessSession< - Req, - Res, Session extends { [key: string]: any } = { [key: string]: any } -> extends AbstractSession { +> extends AbstractSession { private keys?: Uint8Array[]; private chunkSize: number; - constructor(protected config: Config, protected Cookies: new () => Cookies) { - super(config, Cookies); + constructor(protected config: Config) { + super(config); const { cookie: { transient, ...cookieConfig }, name: sessionName @@ -66,12 +64,12 @@ export class StatelessSession< throw err; } - async getSession(req: Req): Promise | undefined | null> { + async getSession(req: Auth0RequestCookies): Promise | undefined | null> { const { name: sessionName } = this.config.session; - const cookies = new this.Cookies().getAll(req); + const cookies = req.getCookies(); let existingSessionValue: string | undefined; if (sessionName in cookies) { - // get JWE from unchunked session cookie + // get JWE from un-chunked session cookie debug('reading session from %s cookie', sessionName); existingSessionValue = cookies[sessionName]; } else if (`${sessionName}.0` in cookies) { @@ -106,8 +104,8 @@ export class StatelessSession< } async setSession( - req: Req, - res: Res, + req: Auth0RequestCookies, + res: Auth0ResponseCookies, session: Session, uat: number, iat: number, @@ -115,8 +113,7 @@ export class StatelessSession< cookieOptions: CookieSerializeOptions ): Promise { const { name: sessionName } = this.config.session; - const cookieSetter = new this.Cookies(); - const cookies = cookieSetter.getAll(req); + const cookies = req.getCookies(); debug('found session, creating signed session cookie(s) with name %o(.i)', sessionName); const value = await this.encrypt(session, { iat, uat, exp }); @@ -132,29 +129,30 @@ export class StatelessSession< for (let i = 0; i < chunkCount; i++) { const chunkValue = value.slice(i * this.chunkSize, (i + 1) * this.chunkSize); const chunkCookieName = `${sessionName}.${i}`; - cookieSetter.set(chunkCookieName, chunkValue, cookieOptions); + res.setCookie(chunkCookieName, chunkValue, cookieOptions); existingCookies.delete(chunkCookieName); } } else { - cookieSetter.set(sessionName, value, cookieOptions); + res.setCookie(sessionName, value, cookieOptions); existingCookies.delete(sessionName); } // When the number of chunks changes due to the cookie size changing, // you need to delete any obsolete cookies. - existingCookies.forEach((cookie) => cookieSetter.clear(cookie, cookieOptions)); - cookieSetter.commit(res, this.config.session.name); + existingCookies.forEach((cookie) => res.clearCookie(cookie, cookieOptions)); } - async deleteSession(req: Req, res: Res, cookieOptions: CookieSerializeOptions): Promise { + async deleteSession( + req: Auth0RequestCookies, + res: Auth0ResponseCookies, + cookieOptions: CookieSerializeOptions + ): Promise { const { name: sessionName } = this.config.session; - const cookieSetter = new this.Cookies(); - const cookies = cookieSetter.getAll(req); + const cookies = req.getCookies(); for (const cookieName of Object.keys(cookies)) { if (cookieName.match(`^${sessionName}(?:\\.\\d)?$`)) { - cookieSetter.clear(cookieName, cookieOptions); - cookieSetter.commit(res, this.config.session.name); + res.clearCookie(cookieName, cookieOptions); } } } diff --git a/src/auth0-session/transient-store.ts b/src/auth0-session/transient-store.ts index 43101d93a..0a70a327a 100644 --- a/src/auth0-session/transient-store.ts +++ b/src/auth0-session/transient-store.ts @@ -1,13 +1,11 @@ -import { IncomingMessage, ServerResponse } from 'http'; -import { generators } from 'openid-client'; import { generateCookieValue, getCookieValue } from './utils/signed-cookies'; import { signing } from './utils/hkdf'; -import NodeCookies from './utils/cookies'; import { Config } from './config'; +import { Auth0Request, Auth0Response } from './http'; export interface StoreOptions { sameSite?: boolean | 'lax' | 'strict' | 'none'; - value?: string; + value: string; } export default class TransientStore { @@ -38,9 +36,9 @@ export default class TransientStore { */ async save( key: string, - _req: IncomingMessage, - res: ServerResponse, - { sameSite = 'none', value = this.generateNonce() }: StoreOptions + _req: Auth0Request, + res: Auth0Response, + { sameSite = 'none', value }: StoreOptions ): Promise { const isSameSiteNone = sameSite === 'none'; const { domain, path, secure } = this.config.session.cookie; @@ -51,12 +49,11 @@ export default class TransientStore { path }; const [signingKey] = await this.getKeys(); - const cookieSetter = new NodeCookies(); { const cookieValue = await generateCookieValue(key, value, signingKey); // Set the cookie with the SameSite attribute and, if needed, the Secure flag. - cookieSetter.set(key, cookieValue, { + res.setCookie(key, cookieValue, { ...basicAttr, sameSite, secure: isSameSiteNone ? true : basicAttr.secure @@ -66,10 +63,9 @@ export default class TransientStore { if (isSameSiteNone && this.config.legacySameSiteCookie) { const cookieValue = await generateCookieValue(`_${key}`, value, signingKey); // Set the fallback cookie with no SameSite or Secure attributes. - cookieSetter.set(`_${key}`, cookieValue, basicAttr); + res.setCookie(`_${key}`, cookieValue, basicAttr); } - cookieSetter.commit(res); return value; } @@ -82,15 +78,14 @@ export default class TransientStore { * * @return {String|undefined} Cookie value or undefined if cookie was not found. */ - async read(key: string, req: IncomingMessage, res: ServerResponse): Promise { - const cookies = new NodeCookies().getAll(req); + async read(key: string, req: Auth0Request, res: Auth0Response): Promise { + const cookies = req.getCookies(); const cookie = cookies[key]; const cookieConfig = this.config.session.cookie; - const cookieSetter = new NodeCookies(); const verifyingKeys = await this.getKeys(); let value = await getCookieValue(key, cookie, verifyingKeys); - cookieSetter.clear(key, cookieConfig); + res.clearCookie(key, cookieConfig); if (this.config.legacySameSiteCookie) { const fallbackKey = `_${key}`; @@ -98,38 +93,9 @@ export default class TransientStore { const fallbackCookie = cookies[fallbackKey]; value = await getCookieValue(fallbackKey, fallbackCookie, verifyingKeys); } - cookieSetter.clear(fallbackKey, cookieConfig); + res.clearCookie(fallbackKey, cookieConfig); } - cookieSetter.commit(res); return value; } - - /** - * Generates a `nonce` value. - * - * @return {String} - */ - generateNonce(): string { - return generators.nonce(); - } - - /** - * Generates a `code_verifier` value. - * - * @return {String} - */ - generateCodeVerifier(): string { - return generators.codeVerifier(); - } - - /** - * Calculates a `code_challenge` value for a given `codeVerifier`. - * - * @param {String} codeVerifier Code verifier to calculate the `code_challenge` value from. - * @return {String} - */ - calculateCodeChallenge(codeVerifier: string): string { - return generators.codeChallenge(codeVerifier); - } } diff --git a/src/auth0-session/utils/cookies.ts b/src/auth0-session/utils/cookies.ts deleted file mode 100644 index 9ee36c3b0..000000000 --- a/src/auth0-session/utils/cookies.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { IncomingMessage, ServerResponse } from 'http'; -import { CookieSerializeOptions, parse, serialize } from 'cookie'; - -export abstract class Cookies { - protected cookies: string[]; - - constructor() { - this.cookies = []; - } - - set(name: string, value: string, options: CookieSerializeOptions = {}): void { - this.cookies.push(serialize(name, value, options)); - } - - clear(name: string, options: CookieSerializeOptions = {}): void { - const { domain, path, secure, sameSite } = options; - const clearOptions: CookieSerializeOptions = { - domain, - path, - maxAge: 0 - }; - // If SameSite=None is set, the cookie Secure attribute must also be set (or the cookie will be blocked) - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#none - if (sameSite === 'none') { - clearOptions.secure = secure; - clearOptions.sameSite = sameSite; - } - this.set(name, '', clearOptions); - } - - commit(res: unknown, filterCookiePrefix?: string): void { - let previousCookies = this.getSetCookieHeader(res); - if (filterCookiePrefix) { - const re = new RegExp(`^${filterCookiePrefix}(\\.\\d+)?=`); - previousCookies = previousCookies.filter((cookie: string) => !re.test(cookie)); - } - this.setSetCookieHeader(res, [...previousCookies, ...this.cookies]); - } - - protected abstract getSetCookieHeader(res: unknown): string[]; - protected abstract setSetCookieHeader(res: unknown, cookies: string[]): void; - abstract getAll(req: unknown): Record; -} - -export default class NodeCookies extends Cookies { - protected getSetCookieHeader(res: ServerResponse): string[] { - let cookies = res.getHeader('Set-Cookie') || []; - if (!Array.isArray(cookies)) { - cookies = [cookies as string]; - } - return cookies; - } - - protected setSetCookieHeader(res: ServerResponse, cookies: string[]): void { - res.setHeader('Set-Cookie', cookies); - } - - getAll(req: IncomingMessage): Record { - return parse(req.headers.cookie || ''); - } -} diff --git a/src/auth0-session/utils/encoding.ts b/src/auth0-session/utils/encoding.ts index aa3354508..c4eeed5a8 100644 --- a/src/auth0-session/utils/encoding.ts +++ b/src/auth0-session/utils/encoding.ts @@ -1,5 +1,4 @@ import * as jose from 'jose'; -import { TextDecoder } from 'util'; /** * Prepare a state object to send. diff --git a/src/auth0-session/utils/errors.ts b/src/auth0-session/utils/errors.ts index 3bb531296..62c455a53 100644 --- a/src/auth0-session/utils/errors.ts +++ b/src/auth0-session/utils/errors.ts @@ -1,5 +1,3 @@ -import type { errors } from 'openid-client'; - export class EscapedError extends Error { /** * **WARNING** The message can contain user input and is only escaped using basic escaping for putting untrusted data @@ -14,6 +12,8 @@ export class EscapedError extends Error { export class MissingStateParamError extends Error { static message = 'Missing state parameter in Authorization Response.'; + status = 400; + statusCode = 400; constructor() { /* c8 ignore next */ @@ -24,6 +24,8 @@ export class MissingStateParamError extends Error { export class MissingStateCookieError extends Error { static message = 'Missing state cookie from login request (check login URL, callback URL and cookie config).'; + status = 400; + statusCode = 400; constructor() { /* c8 ignore next */ @@ -37,7 +39,7 @@ export class ApplicationError extends EscapedError { * **WARNING** The message can contain user input and is only escaped using basic escaping for putting untrusted data * directly into the HTML body */ - constructor(rpError: errors.RPError) { + constructor(rpError: Error) { /* c8 ignore next */ super(rpError.message); Object.setPrototypeOf(this, ApplicationError.prototype); @@ -62,7 +64,7 @@ export class IdentityProviderError extends EscapedError { * **WARNING** The message can contain user input and is only escaped using basic escaping for putting untrusted data * directly into the HTML body */ - constructor(rpError: errors.OPError) { + constructor(rpError: { message: string; error?: string; error_description?: string }) { /* c8 ignore next */ super(rpError.message); this.error = htmlSafe(rpError.error); @@ -79,6 +81,14 @@ export class DiscoveryError extends EscapedError { } } +export class UserInfoError extends EscapedError { + constructor(msg: string) { + /* c8 ignore next */ + super(`Userinfo request failing with: ${msg}`); + Object.setPrototypeOf(this, UserInfoError.prototype); + } +} + // eslint-disable-next-line max-len // Basic escaping for putting untrusted data directly into the HTML body, per: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#rule-1-html-encode-before-inserting-untrusted-data-into-html-element-content. export function htmlSafe(input?: string): string | undefined { diff --git a/src/client/index.ts b/src/client/index.ts index 23960346e..3d4747170 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -10,8 +10,7 @@ import { import { default as withPageAuthRequired, WithPageAuthRequired, - WithPageAuthRequiredProps, WithPageAuthRequiredOptions } from './with-page-auth-required'; export { UserProvider, UserProviderProps, UserProfile, UserContext, RequestError, useUser }; -export { withPageAuthRequired, WithPageAuthRequired, WithPageAuthRequiredProps, WithPageAuthRequiredOptions }; +export { withPageAuthRequired, WithPageAuthRequired, WithPageAuthRequiredOptions }; diff --git a/src/client/with-page-auth-required.tsx b/src/client/with-page-auth-required.tsx index 9b86015af..ee7837e25 100644 --- a/src/client/with-page-auth-required.tsx +++ b/src/client/with-page-auth-required.tsx @@ -52,13 +52,6 @@ export interface WithPageAuthRequiredOptions { onError?: (error: Error) => JSX.Element; } -/** - * @ignore - */ -export interface WithPageAuthRequiredProps { - [key: string]: any; -} - export interface UserProps { user: UserProfile; } @@ -73,7 +66,7 @@ export interface UserProps { * * @category Client */ -export type WithPageAuthRequired =

( +export type WithPageAuthRequired =

( Component: ComponentType

, options?: WithPageAuthRequiredOptions ) => React.FC

; diff --git a/src/config.ts b/src/config.ts index 45e1d24f5..2bd873b43 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,4 @@ -import { IncomingMessage } from 'http'; -import type { AuthorizationParameters as OidcAuthorizationParameters } from 'openid-client'; -import type { LoginOptions } from './auth0-session/config'; +import type { LoginOptions, AuthorizationParameters as OidcAuthorizationParameters } from './auth0-session/config'; import { SessionStore } from './auth0-session/session/stateful-session'; import Session from './session/session'; import { DeepPartial, get as getBaseConfig } from './auth0-session/get-config'; @@ -113,7 +111,7 @@ export interface BaseConfig { * ```js * { * ... - * getLoginState(req, options) { + * getLoginState(options) { * return { * returnTo: options.returnTo || req.originalUrl, * customState: 'foo' @@ -122,7 +120,7 @@ export interface BaseConfig { * } * ``` */ - getLoginState: (req: IncomingMessage, options: LoginOptions) => Record; + getLoginState: (options: LoginOptions) => Record; /** * Array value of claims to remove from the ID token before storing the cookie session. @@ -181,8 +179,10 @@ export interface BaseConfig { * Private key for use with `private_key_jwt` clients. * This should be a string that is the contents of a PEM file. * You can also use the `AUTH0_CLIENT_ASSERTION_SIGNING_KEY` environment variable. + * + * For Edge runtime, you can also provide an instance of `CryptoKey`. */ - clientAssertionSigningKey?: string; + clientAssertionSigningKey?: string | CryptoKey; /** * The algorithm to sign the client assertion JWT. @@ -355,7 +355,6 @@ export interface NextConfig extends Pick { routes: { callback: string; login: string; - unauthorized: string; }; session: Pick; } @@ -443,8 +442,8 @@ export interface NextConfig extends Pick { * * **IMPORTANT** If you use {@link InitAuth0}, you should *not* use the other named exports as they will use a different * instance of the SDK. Also note - this is for the server side part of the SDK - you will always use named exports for - * the front end components: {@Link UserProvider}, {@Link UseUser} and the - * front end version of {@Link WithPageAuthRequired} + * the front end components: {@link UserProvider}, {@link UseUser} and the + * front end version of {@link WithPageAuthRequired} * * @category Server */ @@ -578,11 +577,10 @@ export const getConfig = (params: ConfigParameters = {}): { baseConfig: BaseConf clientAssertionSigningAlg: AUTH0_CLIENT_ASSERTION_SIGNING_ALG }); - const nextConfig = { + const nextConfig: NextConfig = { routes: { ...baseConfig.routes, - login: baseParams.routes?.login || getLoginUrl(), - unauthorized: baseParams.routes?.unauthorized || '/api/auth/401' + login: baseParams.routes?.login || getLoginUrl() }, identityClaimFilter: baseConfig.identityClaimFilter, organization: organization || AUTH0_ORGANIZATION, diff --git a/src/edge.ts b/src/edge.ts index 8b73b246c..02447535d 100644 --- a/src/edge.ts +++ b/src/edge.ts @@ -1,25 +1,25 @@ -import { NextMiddleware, NextRequest, NextResponse } from 'next/server'; -import { StatelessSession } from './auth0-session/session/stateless-session'; -import { StatefulSession } from './auth0-session/session/stateful-session'; -import MiddlewareCookies from './utils/middleware-cookies'; -import Session from './session/session'; -import SessionCache from './session/cache'; import { - WithMiddlewareAuthRequired, - default as withMiddlewareAuthRequiredFactory -} from './helpers/with-middleware-auth-required'; -import { getConfig, ConfigParameters } from './config'; + Auth0Server, + ConfigParameters, + GetAccessToken, + GetSession, + HandleAuth, + HandleCallback, + HandleLogin, + HandleLogout, + HandleProfile, + SessionCache, + UpdateSession, + WithApiAuthRequired, + WithPageAuthRequired, + telemetry +} from './shared'; +import { _initAuth } from './init'; import { setIsUsingNamedExports, setIsUsingOwnInstance } from './utils/instance-check'; - -export type Auth0Edge = { withMiddlewareAuthRequired: WithMiddlewareAuthRequired; getSession: GetSession }; - -export type GetSession = (req: NextRequest, res: NextResponse) => Promise; - -export type InitAuth0 = (params?: ConfigParameters) => Auth0Edge; - -export { WithMiddlewareAuthRequired }; - -let instance: Auth0Edge; +import { getConfig, getLoginUrl } from './config'; +import { withPageAuthRequiredFactory } from './helpers'; +import { EdgeClient } from './auth0-session/client/edge-client'; +import { WithMiddlewareAuthRequired } from './helpers/with-middleware-auth-required'; const genId = () => { const bytes = new Uint8Array(16); @@ -29,39 +29,55 @@ const genId = () => { .join(''); }; -function getInstance(params?: ConfigParameters): Auth0Edge { +let instance: Auth0Server & { sessionCache: SessionCache }; + +/** + * Initialise your own instance of the SDK. + * + * See {@link ConfigParameters}. + * + * @category Server + */ +export type InitAuth0 = (params?: ConfigParameters) => Auth0Server; + +// For using managed instance with named exports. +function getInstance(): Auth0Server & { sessionCache: SessionCache } { setIsUsingNamedExports(); if (instance) { return instance; } - instance = _initAuth0(params); + const { baseConfig, nextConfig } = getConfig({ session: { genId } }); + const client = new EdgeClient(baseConfig, telemetry); + instance = _initAuth({ baseConfig, nextConfig, client }); return instance; } -export const initAuth0: InitAuth0 = (params?) => { +// For creating own instance. +export const initAuth0: InitAuth0 = (params) => { setIsUsingOwnInstance(); - return _initAuth0(params); -}; - -const _initAuth0: InitAuth0 = (params?) => { const { baseConfig, nextConfig } = getConfig({ ...params, session: { genId, ...params?.session } }); - - // Init base layer (with base config) - const sessionStore = baseConfig.session.store - ? new StatefulSession(baseConfig, MiddlewareCookies) - : new StatelessSession(baseConfig, MiddlewareCookies); - const sessionCache = new SessionCache(baseConfig, sessionStore); - - // Init Next layer (with next config) - const getSession: GetSession = (req, res) => sessionCache.get(req, res); - const withMiddlewareAuthRequired = withMiddlewareAuthRequiredFactory(nextConfig.routes, () => sessionCache); - - return { - getSession, - withMiddlewareAuthRequired - }; + const client = new EdgeClient(baseConfig, telemetry); + const { sessionCache, ...publicApi } = _initAuth({ baseConfig, nextConfig, client }); + return publicApi; }; +const getSessionCache = () => getInstance().sessionCache; export const getSession: GetSession = (...args) => getInstance().getSession(...args); -export const withMiddlewareAuthRequired: WithMiddlewareAuthRequired = (middleware?: NextMiddleware) => - getInstance().withMiddlewareAuthRequired(middleware); +export const updateSession: UpdateSession = (...args) => getInstance().updateSession(...args); +export const getAccessToken: GetAccessToken = (...args) => getInstance().getAccessToken(...args); +export const withApiAuthRequired: WithApiAuthRequired = (...args) => + (getInstance().withApiAuthRequired as any)(...args); +export const withPageAuthRequired: WithPageAuthRequired = withPageAuthRequiredFactory(getLoginUrl(), getSessionCache); +export const handleLogin: HandleLogin = ((...args: Parameters) => + getInstance().handleLogin(...args)) as HandleLogin; +export const handleLogout: HandleLogout = ((...args: Parameters) => + getInstance().handleLogout(...args)) as HandleLogout; +export const handleCallback: HandleCallback = ((...args: Parameters) => + getInstance().handleCallback(...args)) as HandleCallback; +export const handleProfile: HandleProfile = ((...args: Parameters) => + getInstance().handleProfile(...args)) as HandleProfile; +export const handleAuth: HandleAuth = (...args) => getInstance().handleAuth(...args); +export const withMiddlewareAuthRequired: WithMiddlewareAuthRequired = (...args) => + getInstance().withMiddlewareAuthRequired(...args); + +export * from './shared'; diff --git a/src/handlers/auth.ts b/src/handlers/auth.ts index c3291e2bd..26919b61c 100644 --- a/src/handlers/auth.ts +++ b/src/handlers/auth.ts @@ -1,9 +1,12 @@ import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'; +import { NextRequest } from 'next/server'; import { HandleLogin } from './login'; import { HandleLogout } from './logout'; import { HandleCallback } from './callback'; import { HandleProfile } from './profile'; import { HandlerError } from '../utils/errors'; +import { AppRouteHandlerFn, AppRouteHandlerFnContext, Handler } from './router-helpers'; +import { isRequest } from '../utils/req-helpers'; /** * If you want to add some custom behavior to the default auth handlers, you can pass in custom handlers for @@ -60,17 +63,23 @@ import { HandlerError } from '../utils/errors'; */ export type Handlers = ApiHandlers | ErrorHandlers; -type ApiHandlers = { - [key: string]: NextApiHandler; -}; +/** + * @ignore + */ +type ApiHandlers = { [key: string]: Handler }; +/** + * @ignore + */ type ErrorHandlers = { - onError?: OnError; + onError?: PageRouterOnError | AppRouterOnError; }; /** * The main way to use the server SDK. * + * *Page Router* + * * Simply set the environment variables per {@link ConfigParameters} then create the file * `pages/api/auth/[auth0].js`. For example: * @@ -80,6 +89,18 @@ type ErrorHandlers = { * * export default handleAuth(); * ``` + + * *App Router* + * + * Simply set the environment variables per {@link ConfigParameters} then create the file + * `app/api/auth/[auth0]/route.js`. For example: + * + * ```js + * // app/api/auth/[auth0]/route.js + * import { handleAuth } from '@auth0/nextjs-auth0'; + * + * export const GET = handleAuth(); + * ``` * * This will create 5 handlers for the following urls: * @@ -87,11 +108,11 @@ type ErrorHandlers = { * - `/api/auth/callback`: The page that your identity provider will redirect the user back to on login. * - `/api/auth/logout`: log the user out of your app. * - `/api/auth/me`: View the user profile JSON (used by the {@link UseUser} hook). - * - `/api/auth/unauthorized`: Returns a 401 for use by {@link WithMiddlewareAuthRequired} when protecting API routes. * * @category Server */ -export type HandleAuth = (userHandlers?: Handlers) => NextApiHandler; +// any is required for app router ts check +export type HandleAuth = (userHandlers?: Handlers) => NextApiHandler | AppRouteHandlerFn | any; /** * Error handler for the default auth routes. @@ -114,27 +135,26 @@ export type HandleAuth = (userHandlers?: Handlers) => NextApiHandler; * * @category Server */ -export type OnError = (req: NextApiRequest, res: NextApiResponse, error: HandlerError) => Promise | void; +export type PageRouterOnError = ( + req: NextApiRequest, + res: NextApiResponse, + error: HandlerError +) => Promise | void; +export type AppRouterOnError = (req: NextRequest, error: HandlerError) => Promise | Response | void; /** * @ignore */ -const defaultOnError: OnError = (_req, res, error) => { +const defaultPageRouterOnError: PageRouterOnError = (_req, res, error) => { console.error(error); res.status(error.status || 500).end(); }; /** - * This is a handler for use by {@link WithMiddlewareAuthRequired} when protecting an API route. - * Middleware can't return a response body, so an unauthorized request for an API route - * needs to rewrite to this handler. * @ignore */ -const unauthorized: NextApiHandler = (_req, res) => { - res.status(401).json({ - error: 'not_authenticated', - description: 'The user does not have an active session or is not authenticated' - }); +const defaultAppRouterOnError: AppRouterOnError = (_req, error) => { + console.error(error); }; /** @@ -151,43 +171,87 @@ export default function handlerFactory({ handleCallback: HandleCallback; handleProfile: HandleProfile; }): HandleAuth { - return ({ onError, ...handlers }: Handlers = {}): NextApiHandler => { + return ({ onError, ...handlers }: Handlers = {}): NextApiHandler | AppRouteHandlerFn => { const customHandlers: ApiHandlers = { login: handleLogin, logout: handleLogout, callback: handleCallback, me: (handlers as ApiHandlers).profile || handleProfile, - 401: unauthorized, ...handlers }; - return async (req, res): Promise => { - let { - query: { auth0: route } - } = req; - - if (Array.isArray(route)) { - let otherRoutes; - [route, ...otherRoutes] = route; - if (otherRoutes.length) { - res.status(404).end(); - return; - } - } - try { - const handler = route && customHandlers.hasOwnProperty(route) && customHandlers[route]; - if (handler) { - await handler(req, res); - } else { - res.status(404).end(); - } - } catch (error) { - await (onError || defaultOnError)(req, res, error as HandlerError); - if (!res.writableEnded) { - // 200 is the default, so we assume it has not been set in the custom error handler if it equals 200 - res.status(res.statusCode === 200 ? 500 : res.statusCode).end(); - } + const appRouteHandler = appRouteHandlerFactory(customHandlers, onError as AppRouterOnError); + const pageRouteHandler = pageRouteHandlerFactory(customHandlers, onError as PageRouterOnError); + + return (req: NextRequest | NextApiRequest, resOrCtx: NextApiResponse | AppRouteHandlerFnContext) => { + if (isRequest(req)) { + return appRouteHandler(req as NextRequest, resOrCtx as AppRouteHandlerFnContext); } + return pageRouteHandler(req as NextApiRequest, resOrCtx as NextApiResponse); }; }; } + +/** + * @ignore + */ +const appRouteHandlerFactory: (customHandlers: ApiHandlers, onError?: AppRouterOnError) => AppRouteHandlerFn = + (customHandlers, onError) => async (req: NextRequest, ctx) => { + const { params } = ctx; + let route = params.auth0; + + if (Array.isArray(route)) { + let otherRoutes; + [route, ...otherRoutes] = route; + if (otherRoutes.length) { + return new Response(null, { status: 404 }); + } + } + + const handler = route && customHandlers.hasOwnProperty(route) && customHandlers[route]; + try { + if (handler) { + return await (handler as AppRouteHandlerFn)(req, ctx); + } else { + return new Response(null, { status: 404 }); + } + } catch (error) { + const res = await (onError || defaultAppRouterOnError)(req, error as HandlerError); + return res || new Response(null, { status: error.status || 500 }); + } + }; + +/** + * @ignore + */ +const pageRouteHandlerFactory: (customHandlers: ApiHandlers, onError?: PageRouterOnError) => NextApiHandler = + (customHandlers, onError) => + async (req: NextApiRequest, res: NextApiResponse): Promise => { + let { + query: { auth0: route } + } = req; + + if (Array.isArray(route)) { + let otherRoutes; + [route, ...otherRoutes] = route; + if (otherRoutes.length) { + res.status(404).end(); + return; + } + } + + try { + const handler = route && customHandlers.hasOwnProperty(route) && customHandlers[route]; + if (handler) { + await (handler as NextApiHandler)(req, res); + } else { + res.status(404).end(); + } + } catch (error) { + await (onError || defaultPageRouterOnError)(req, res, error as HandlerError); + if (!res.writableEnded) { + // 200 is the default, so we assume it has not been set in the custom error handler if it equals 200 + res.status(res.statusCode === 200 ? 500 : res.statusCode).end(); + } + } + }; diff --git a/src/handlers/callback.ts b/src/handlers/callback.ts index 7b9d30b70..b38c874c4 100644 --- a/src/handlers/callback.ts +++ b/src/handlers/callback.ts @@ -1,11 +1,23 @@ -import { IncomingMessage } from 'http'; -import { strict as assert } from 'assert'; import { NextApiResponse, NextApiRequest } from 'next'; -import { AuthorizationParameters, HandleCallback as BaseHandleCallback } from '../auth0-session'; +import { NextRequest, NextResponse } from 'next/server'; +import { + AuthorizationParameters, + HandleCallback as BaseHandleCallback, + AfterCallback as BaseAfterCallback, + HandleLogin as BaseHandleLogin +} from '../auth0-session'; import { Session } from '../session'; import { assertReqRes } from '../utils/assert'; -import { NextConfig } from '../config'; +import { BaseConfig, NextConfig } from '../config'; import { CallbackHandlerError, HandlerErrorCause } from '../utils/errors'; +import { Auth0NextApiRequest, Auth0NextApiResponse, Auth0NextRequest, Auth0NextResponse } from '../http'; +import { LoginOptions } from './login'; +import { AppRouteHandlerFnContext, AuthHandler, getHandler, Handler, OptionsProvider } from './router-helpers'; + +/** + * afterCallback hook for page router {@link AfterCallbackPageRoute} and app router {@link AfterCallbackAppRoute} + */ +export type AfterCallback = AfterCallbackPageRoute | AfterCallbackAppRoute; /** * Use this function for validating additional claims on the user's ID token or adding removing items from @@ -87,13 +99,84 @@ import { CallbackHandlerError, HandlerErrorCause } from '../utils/errors'; * * @category Server */ -export type AfterCallback = ( +export type AfterCallbackPageRoute = ( req: NextApiRequest, res: NextApiResponse, session: Session, state?: { [key: string]: any } ) => Promise | Session | undefined; +/** + * Use this function for validating additional claims on the user's ID token or adding removing items from + * the session after login. + * + * @example Validate additional claims + * + * ```js + * // app/api/auth/[auth0]/route.js + * import { handleAuth, handleCallback } from '@auth0/nextjs-auth0'; + * import { redirect } from 'next/navigation'; + * + * const afterCallback = (req, session, state) => { + * if (session.user.isAdmin) { + * return session; + * } else { + * redirect('/unauthorized'); + * } + * }; + * + * export default handleAuth({ + * callback: handleCallback({ afterCallback }) + * }); + * ``` + * + * @example Modify the session after login + * + * ```js + * // pages/api/auth/[auth0].js + * import { handleAuth, handleCallback } from '@auth0/nextjs-auth0'; + * import { NextResponse } from 'next/server'; + * + * const afterCallback = (req, session, state) => { + * session.user.customProperty = 'foo'; + * delete session.refreshToken; + * return session; + * }; + * + * export default handleAuth({ + * callback: handleCallback({ afterCallback }) + * }); + * ``` + * + * @example Redirect successful login based on claim + * + * ```js + * // pages/api/auth/[auth0].js + * import { handleAuth, handleCallback } from '@auth0/nextjs-auth0'; + * import { headers } from 'next/headers'; + * + * const afterCallback = (req, session, state) => { + * if (!session.user.isAdmin) { + * headers.set('location', '/admin'); + * } + * return session; + * }; + * + * export default handleAuth({ + * callback: handleCallback({ afterCallback }) + * }); + * ``` + * + * @throws {@link HandlerError} + * + * @category Server + */ +export type AfterCallbackAppRoute = ( + req: NextRequest, + session: Session, + state?: { [key: string]: any } +) => Promise | Session | Response | undefined; + /** * Options to customize the callback handler. * @@ -129,7 +212,7 @@ export interface CallbackOptions { * * @category Server */ -export type CallbackOptionsProvider = (req: NextApiRequest) => CallbackOptions; +export type CallbackOptionsProvider = OptionsProvider; /** * Use this to customize the default callback handler without overriding it. @@ -181,11 +264,7 @@ export type CallbackOptionsProvider = (req: NextApiRequest) => CallbackOptions; * * @category Server */ -export type HandleCallback = { - (req: NextApiRequest, res: NextApiResponse, options?: CallbackOptions): Promise; - (provider: CallbackOptionsProvider): CallbackHandler; - (options: CallbackOptions): CallbackHandler; -}; +export type HandleCallback = AuthHandler; /** * The handler for the `/api/auth/callback` API route. @@ -194,65 +273,101 @@ export type HandleCallback = { * * @category Server */ -export type CallbackHandler = (req: NextApiRequest, res: NextApiResponse, options?: CallbackOptions) => Promise; +export type CallbackHandler = Handler; /** * @ignore */ -const idTokenValidator = - (afterCallback?: AfterCallback, organization?: string): AfterCallback => - (req, res, session, state) => { - if (organization) { - if (organization.startsWith('org_')) { - assert(session.user.org_id, 'Organization Id (org_id) claim must be a string present in the ID token'); - assert.equal( - session.user.org_id, - organization, - `Organization Id (org_id) claim value mismatch in the ID token; ` + - `expected "${organization}", found "${session.user.org_id}"` - ); - } else { - assert(session.user.org_name, 'Organization Name (org_name) claim must be a string present in the ID token'); - assert.equal( - session.user.org_name, - organization.toLowerCase(), - `Organization Name (org_name) claim value mismatch in the ID token; ` + - `expected "${organization}", found "${session.user.org_name}"` - ); +export default function handleCallbackFactory(handler: BaseHandleCallback, config: NextConfig): HandleCallback { + const appRouteHandler = appRouteHandlerFactory(handler, config); + const pageRouteHandler = pageRouteHandlerFactory(handler, config); + + return getHandler(appRouteHandler, pageRouteHandler) as HandleCallback; +} + +const applyOptions = ( + req: NextApiRequest | NextRequest, + res: NextApiResponse | undefined, + options: CallbackOptions, + config: NextConfig +) => { + const opts = { ...options }; + const idTokenValidator = + (afterCallback?: AfterCallback, organization?: string): BaseAfterCallback => + (session, state) => { + if (organization) { + if (organization.startsWith('org_')) { + if (!session.user.org_id) { + throw new Error('Organization Id (org_id) claim must be a string present in the ID token'); + } + if (session.user.org_id !== organization) { + throw new Error( + `Organization Id (org_id) claim value mismatch in the ID token; ` + + `expected "${organization}", found "${session.user.org_id}"` + ); + } + } else { + if (!session.user.org_name) { + throw new Error('Organization Name (org_name) claim must be a string present in the ID token'); + } + if (session.user.org_name !== organization.toLowerCase()) { + throw new Error( + `Organization Name (org_name) claim value mismatch in the ID token; ` + + `expected "${organization}", found "${session.user.org_name}"` + ); + } + } } - } - if (afterCallback) { - return afterCallback(req, res, session, state); - } - return session; + if (afterCallback) { + if (res) { + return (afterCallback as AfterCallbackPageRoute)(req as NextApiRequest, res, session, state); + } else { + return (afterCallback as AfterCallbackAppRoute)(req as NextRequest, session, state); + } + } + return session; + }; + return { + ...opts, + afterCallback: idTokenValidator(opts.afterCallback, opts.organization || config.organization) }; +}; /** * @ignore */ -export default function handleCallbackFactory(handler: BaseHandleCallback, config: NextConfig): HandleCallback { - const callback: CallbackHandler = async (req: NextApiRequest, res: NextApiResponse, options = {}): Promise => { +const appRouteHandlerFactory: ( + handler: BaseHandleLogin, + config: NextConfig +) => (req: NextRequest, ctx: AppRouteHandlerFnContext, options?: CallbackOptions) => Promise | Response = + (handler, config) => + async (req, _ctx, options = {}) => { try { - assertReqRes(req, res); - return await handler(req, res, { - ...options, - afterCallback: idTokenValidator(options.afterCallback, options.organization || config.organization) - }); + const auth0Res = new Auth0NextResponse(new NextResponse()); + await handler(new Auth0NextRequest(req), auth0Res, applyOptions(req, undefined, options, config)); + return auth0Res.res; } catch (e) { throw new CallbackHandlerError(e as HandlerErrorCause); } }; - return ( - reqOrOptions: NextApiRequest | CallbackOptionsProvider | CallbackOptions, - res?: NextApiResponse, - options?: CallbackOptions - ): any => { - if (reqOrOptions instanceof IncomingMessage && res) { - return callback(reqOrOptions, res, options); - } - if (typeof reqOrOptions === 'function') { - return (req: NextApiRequest, res: NextApiResponse) => callback(req, res, reqOrOptions(req)); + +/** + * @ignore + */ +const pageRouteHandlerFactory: ( + handler: BaseHandleCallback, + config: NextConfig +) => (req: NextApiRequest, res: NextApiResponse, options?: CallbackOptions) => Promise = + (handler, config) => + async (req: NextApiRequest, res: NextApiResponse, options = {}): Promise => { + try { + assertReqRes(req, res); + return await handler( + new Auth0NextApiRequest(req), + new Auth0NextApiResponse(res), + applyOptions(req, res, options, config) + ); + } catch (e) { + throw new CallbackHandlerError(e as HandlerErrorCause); } - return (req: NextApiRequest, res: NextApiResponse) => callback(req, res, reqOrOptions as CallbackOptions); }; -} diff --git a/src/handlers/index.ts b/src/handlers/index.ts index a059ed4ab..22f8b8a29 100644 --- a/src/handlers/index.ts +++ b/src/handlers/index.ts @@ -1,5 +1,28 @@ -export { default as callbackHandler, HandleCallback, CallbackOptions, AfterCallback } from './callback'; -export { default as loginHandler, HandleLogin, LoginOptions, GetLoginState } from './login'; +export { + default as callbackHandler, + HandleCallback, + CallbackOptions, + AfterCallback, + AfterCallbackPageRoute, + AfterCallbackAppRoute +} from './callback'; +export { + default as loginHandler, + HandleLogin, + LoginOptions, + GetLoginState, + GetLoginStatePageRoute, + GetLoginStateAppRoute +} from './login'; export { default as logoutHandler, HandleLogout, LogoutOptions } from './logout'; -export { default as profileHandler, HandleProfile, ProfileOptions, AfterRefetch } from './profile'; -export { default as handlerFactory, Handlers, HandleAuth, OnError } from './auth'; +export { + default as profileHandler, + HandleProfile, + ProfileOptions, + AfterRefetch, + AfterRefetchPageRoute, + AfterRefetchAppRoute +} from './profile'; +export { default as handlerFactory, Handlers, HandleAuth, AppRouterOnError, PageRouterOnError } from './auth'; +export { AppRouteHandlerFn } from './router-helpers'; +export { AppRouteHandlerFnContext } from './router-helpers'; diff --git a/src/handlers/login.ts b/src/handlers/login.ts index 223747eb4..3e71550e8 100644 --- a/src/handlers/login.ts +++ b/src/handlers/login.ts @@ -1,10 +1,21 @@ -import { IncomingMessage } from 'http'; import { NextApiResponse, NextApiRequest } from 'next'; -import { AuthorizationParameters, HandleLogin as BaseHandleLogin } from '../auth0-session'; +import { NextRequest, NextResponse } from 'next/server'; +import { + AuthorizationParameters, + HandleLogin as BaseHandleLogin, + LoginOptions as BaseLoginOptions +} from '../auth0-session'; import toSafeRedirect from '../utils/url-helpers'; import { assertReqRes } from '../utils/assert'; import { BaseConfig, NextConfig } from '../config'; import { HandlerErrorCause, LoginHandlerError } from '../utils/errors'; +import { Auth0NextApiRequest, Auth0NextApiResponse, Auth0NextRequest, Auth0NextResponse } from '../http'; +import { AppRouteHandlerFnContext, getHandler, OptionsProvider, Handler, AuthHandler } from './router-helpers'; + +/** + * Get login state hook for page router {@link GetLoginStatePageRoute} and app router {@link GetLoginStateAppRoute}. + */ +export type GetLoginState = GetLoginStatePageRoute | GetLoginStateAppRoute; /** * Use this to store additional state for the user before they visit the identity provider to log in. @@ -18,19 +29,33 @@ import { HandlerErrorCause, LoginHandlerError } from '../utils/errors'; * }; * * export default handleAuth({ - * async login(req, res) { - * try { - * await handleLogin(req, res, { getLoginState }); - * } catch (error) { - * res.status(error.status || 500).end(); - * } - * } + * login: handleLogin({ getLoginState }) + * }); + * ``` + * + * @category Server + */ +export type GetLoginStatePageRoute = (req: NextApiRequest, options: LoginOptions) => { [key: string]: any }; + +/** + * Use this to store additional state for the user before they visit the identity provider to log in. + * + * ```js + * // app/api/auth/[auth0]/route.js + * import { handleAuth, handleLogin } from '@auth0/nextjs-auth0'; + * + * const getLoginState = (req, loginOptions) => { + * return { basket_id: getBasketId(req) }; + * }; + * + * export default handleAuth({ + * login: handleLogin({ getLoginState }) * }); * ``` * * @category Server */ -export type GetLoginState = (req: NextApiRequest, options: LoginOptions) => { [key: string]: any }; +export type GetLoginStateAppRoute = (req: NextRequest, options: LoginOptions) => { [key: string]: any }; /** * Authorization params to pass to the login handler. @@ -164,7 +189,7 @@ export interface LoginOptions { * * @category Server */ -export type LoginOptionsProvider = (req: NextApiRequest) => LoginOptions; +export type LoginOptionsProvider = OptionsProvider; /** * Use this to customize the default login handler without overriding it. @@ -220,11 +245,7 @@ export type LoginOptionsProvider = (req: NextApiRequest) => LoginOptions; * * @category Server */ -export type HandleLogin = { - (req: NextApiRequest, res: NextApiResponse, options?: LoginOptions): Promise; - (provider: LoginOptionsProvider): LoginHandler; - (options: LoginOptions): LoginHandler; -}; +export type HandleLogin = AuthHandler; /** * The handler for the `/api/auth/login` API route. @@ -233,7 +254,7 @@ export type HandleLogin = { * * @category Server */ -export type LoginHandler = (req: NextApiRequest, res: NextApiResponse, options?: LoginOptions) => Promise; +export type LoginHandler = Handler; /** * @ignore @@ -243,38 +264,90 @@ export default function handleLoginFactory( nextConfig: NextConfig, baseConfig: BaseConfig ): HandleLogin { - const login: LoginHandler = async (req: NextApiRequest, res: NextApiResponse, options = {}): Promise => { + const appRouteHandler = appRouteHandlerFactory(handler, nextConfig, baseConfig); + const pageRouteHandler = pageRouteHandlerFactory(handler, nextConfig, baseConfig); + + return getHandler(appRouteHandler, pageRouteHandler) as HandleLogin; +} + +/** + * @ignore + */ +const applyOptions = ( + req: NextApiRequest | NextRequest, + options: LoginOptions, + dangerousReturnTo: string | undefined | null, + nextConfig: NextConfig, + baseConfig: BaseConfig +): BaseLoginOptions => { + let opts: BaseLoginOptions; + let getLoginState: GetLoginState | undefined; + // eslint-disable-next-line prefer-const + ({ getLoginState, ...opts } = options); + if (dangerousReturnTo) { + const safeBaseUrl = new URL(options.authorizationParams?.redirect_uri || baseConfig.baseURL); + const returnTo = toSafeRedirect(dangerousReturnTo, safeBaseUrl); + opts = { ...opts, returnTo }; + } + if (nextConfig.organization) { + opts = { + ...opts, + authorizationParams: { organization: nextConfig.organization, ...opts.authorizationParams } + }; + } + if (getLoginState) { + opts.getLoginState = (_opts) => (getLoginState as GetLoginState)(req as any, _opts as any); + } + return opts; +}; + +/** + * @ignore + */ +const appRouteHandlerFactory: ( + handler: BaseHandleLogin, + nextConfig: NextConfig, + baseConfig: BaseConfig +) => (req: NextRequest, ctx: AppRouteHandlerFnContext, options?: LoginOptions) => Promise | Response = + (handler, nextConfig, baseConfig) => + async (req, _ctx, options = {}) => { try { - assertReqRes(req, res); - if (req.query.returnTo) { - const dangerousReturnTo = Array.isArray(req.query.returnTo) ? req.query.returnTo[0] : req.query.returnTo; - const safeBaseUrl = new URL(options.authorizationParams?.redirect_uri || baseConfig.baseURL); - const returnTo = toSafeRedirect(dangerousReturnTo, safeBaseUrl); + const url = new URL(req.url); + const dangerousReturnTo = url.searchParams.get('returnTo'); - options = { ...options, returnTo }; - } - if (nextConfig.organization) { - options = { - ...options, - authorizationParams: { organization: nextConfig.organization, ...options.authorizationParams } - }; - } - return await handler(req, res, options); + const auth0Res = new Auth0NextResponse(new NextResponse()); + await handler( + new Auth0NextRequest(req), + auth0Res, + applyOptions(req, options, dangerousReturnTo, nextConfig, baseConfig) as BaseLoginOptions + ); + return auth0Res.res; } catch (e) { throw new LoginHandlerError(e as HandlerErrorCause); } }; - return ( - reqOrOptions: NextApiRequest | LoginOptionsProvider | LoginOptions, - res?: NextApiResponse, - options?: LoginOptions - ): any => { - if (reqOrOptions instanceof IncomingMessage && res) { - return login(reqOrOptions, res, options); - } - if (typeof reqOrOptions === 'function') { - return (req: NextApiRequest, res: NextApiResponse) => login(req, res, reqOrOptions(req)); + +/** + * @ignore + */ +const pageRouteHandlerFactory: ( + handler: BaseHandleLogin, + nextConfig: NextConfig, + baseConfig: BaseConfig +) => (req: NextApiRequest, res: NextApiResponse, options?: LoginOptions) => Promise | void = + (handler, nextConfig, baseConfig) => + async (req, res, options = {}) => { + try { + assertReqRes(req, res); + const dangerousReturnTo = + req.query.returnTo && Array.isArray(req.query.returnTo) ? req.query.returnTo[0] : req.query.returnTo; + + return await handler( + new Auth0NextApiRequest(req), + new Auth0NextApiResponse(res), + applyOptions(req, options, dangerousReturnTo, nextConfig, baseConfig) as BaseLoginOptions + ); + } catch (e) { + throw new LoginHandlerError(e as HandlerErrorCause); } - return (req: NextApiRequest, res: NextApiResponse) => login(req, res, reqOrOptions as LoginOptions); }; -} diff --git a/src/handlers/logout.ts b/src/handlers/logout.ts index 7239f823a..517382e11 100644 --- a/src/handlers/logout.ts +++ b/src/handlers/logout.ts @@ -1,8 +1,11 @@ -import { IncomingMessage } from 'http'; import { NextApiResponse, NextApiRequest } from 'next'; -import { HandleLogout as BaseHandleLogout } from '../auth0-session'; +import { NextRequest, NextResponse } from 'next/server'; +import { HandleLogin as BaseHandleLogin, HandleLogout as BaseHandleLogout } from '../auth0-session'; import { assertReqRes } from '../utils/assert'; import { HandlerErrorCause, LogoutHandlerError } from '../utils/errors'; +import { Auth0NextApiRequest, Auth0NextApiResponse, Auth0NextRequest, Auth0NextResponse } from '../http'; +import { BaseConfig } from '../config'; +import { AppRouteHandlerFnContext, AuthHandler, Handler, getHandler, OptionsProvider } from './router-helpers'; /** * Options to customize the logout handler. @@ -30,7 +33,7 @@ export interface LogoutOptions { * * @category Server */ -export type LogoutOptionsProvider = (req: NextApiRequest) => LogoutOptions; +export type LogoutOptionsProvider = OptionsProvider; /** * Use this to customize the default logout handler without overriding it. @@ -82,11 +85,7 @@ export type LogoutOptionsProvider = (req: NextApiRequest) => LogoutOptions; * * @category Server */ -export type HandleLogout = { - (req: NextApiRequest, res: NextApiResponse, options?: LogoutOptions): Promise; - (provider: LogoutOptionsProvider): LogoutHandler; - (options: LogoutOptions): LogoutHandler; -}; +export type HandleLogout = AuthHandler; /** * The handler for the `/api/auth/logout` API route. @@ -95,31 +94,41 @@ export type HandleLogout = { * * @category Server */ -export type LogoutHandler = (req: NextApiRequest, res: NextApiResponse, options?: LogoutOptions) => Promise; +export type LogoutHandler = Handler; /** * @ignore */ export default function handleLogoutFactory(handler: BaseHandleLogout): HandleLogout { - const logout: LogoutHandler = async (req: NextApiRequest, res: NextApiResponse, options = {}): Promise => { + const appRouteHandler = appRouteHandlerFactory(handler); + const pageRouteHandler = pageRouteHandlerFactory(handler); + + return getHandler(appRouteHandler, pageRouteHandler) as HandleLogout; +} + +const appRouteHandlerFactory: ( + handler: BaseHandleLogin +) => (req: NextRequest, ctx: AppRouteHandlerFnContext, options?: LogoutOptions) => Promise | Response = + (handler) => + async (req, _ctx, options = {}) => { try { - assertReqRes(req, res); - return await handler(req, res, options); + const auth0Res = new Auth0NextResponse(new NextResponse()); + await handler(new Auth0NextRequest(req), auth0Res, options); + return auth0Res.res; } catch (e) { throw new LogoutHandlerError(e as HandlerErrorCause); } }; - return ( - reqOrOptions: NextApiRequest | LogoutOptionsProvider | LogoutOptions, - res?: NextApiResponse, - options?: LogoutOptions - ): any => { - if (reqOrOptions instanceof IncomingMessage && res) { - return logout(reqOrOptions, res, options); - } - if (typeof reqOrOptions === 'function') { - return (req: NextApiRequest, res: NextApiResponse) => logout(req, res, reqOrOptions(req)); + +const pageRouteHandlerFactory: ( + handler: BaseHandleLogin +) => (req: NextApiRequest, res: NextApiResponse, options?: LogoutOptions) => Promise | void = + (handler) => + async (req, res, options = {}) => { + try { + assertReqRes(req, res); + return await handler(new Auth0NextApiRequest(req), new Auth0NextApiResponse(res), options); + } catch (e) { + throw new LogoutHandlerError(e as HandlerErrorCause); } - return (req: NextApiRequest, res: NextApiResponse) => logout(req, res, reqOrOptions as LogoutOptions); }; -} diff --git a/src/handlers/profile.ts b/src/handlers/profile.ts index f915615ba..3d12d80ce 100644 --- a/src/handlers/profile.ts +++ b/src/handlers/profile.ts @@ -1,11 +1,35 @@ -import { IncomingMessage } from 'http'; import { NextApiResponse, NextApiRequest } from 'next'; -import { ClientFactory } from '../auth0-session'; +import { NextRequest, NextResponse } from 'next/server'; +import { AbstractClient } from '../auth0-session'; import { SessionCache, Session, fromJson, GetAccessToken } from '../session'; import { assertReqRes } from '../utils/assert'; import { ProfileHandlerError, HandlerErrorCause } from '../utils/errors'; +import { AppRouteHandlerFnContext, AuthHandler, getHandler, Handler, OptionsProvider } from './router-helpers'; -export type AfterRefetch = (req: NextApiRequest, res: NextApiResponse, session: Session) => Promise | Session; +/** + * After refetch handler for page router {@link AfterRefetchPageRoute} and app router {@link AfterRefetchAppRoute}. + * + * @category Server + */ +export type AfterRefetch = AfterRefetchPageRoute | AfterRefetchAppRoute; + +/** + * After refetch handler for page router. + * + * @category Server + */ +export type AfterRefetchPageRoute = ( + req: NextApiRequest, + res: NextApiResponse, + session: Session +) => Promise | Session; + +/** + * After refetch handler for app router. + * + * @category Server + */ +export type AfterRefetchAppRoute = (req: NextRequest, session: Session) => Promise | Session; /** * Options to customize the profile handler. @@ -35,7 +59,7 @@ export type ProfileOptions = { * * @category Server */ -export type ProfileOptionsProvider = (req: NextApiRequest) => ProfileOptions; +export type ProfileOptionsProvider = OptionsProvider; /** * Use this to customize the default profile handler without overriding it. @@ -85,11 +109,7 @@ export type ProfileOptionsProvider = (req: NextApiRequest) => ProfileOptions; * * @category Server */ -export type HandleProfile = { - (req: NextApiRequest, res: NextApiResponse, options?: ProfileOptions): Promise; - (provider: ProfileOptionsProvider): ProfileHandler; - (options: ProfileOptions): ProfileHandler; -}; +export type HandleProfile = AuthHandler; /** * The handler for the `/api/auth/me` API route. @@ -98,17 +118,83 @@ export type HandleProfile = { * * @category Server */ -export type ProfileHandler = (req: NextApiRequest, res: NextApiResponse, options?: ProfileOptions) => Promise; +export type ProfileHandler = Handler; /** * @ignore */ export default function profileHandler( - getClient: ClientFactory, + client: AbstractClient, getAccessToken: GetAccessToken, sessionCache: SessionCache ): HandleProfile { - const profile: ProfileHandler = async (req: NextApiRequest, res: NextApiResponse, options = {}): Promise => { + const appRouteHandler = appRouteHandlerFactory(client, getAccessToken, sessionCache); + const pageRouteHandler = pageRouteHandlerFactory(client, getAccessToken, sessionCache); + + return getHandler(appRouteHandler, pageRouteHandler) as HandleProfile; +} + +/** + * @ignore + */ +const appRouteHandlerFactory: ( + client: AbstractClient, + getAccessToken: GetAccessToken, + sessionCache: SessionCache +) => (req: NextRequest, ctx: AppRouteHandlerFnContext, options?: ProfileOptions) => Promise | Response = + (client, getAccessToken, sessionCache) => + async (req, _ctx, options = {}) => { + try { + const res = new NextResponse(); + + if (!(await sessionCache.isAuthenticated(req, res))) { + return new Response(null, { status: 204 }); + } + + const session = (await sessionCache.get(req, res)) as Session; + res.headers.set('Cache-Control', 'no-store'); + + if (options.refetch) { + const { accessToken } = await getAccessToken(req, res); + if (!accessToken) { + throw new Error('No access token available to refetch the profile'); + } + + const userInfo = await client.userinfo(accessToken); + + let newSession = fromJson({ + ...session, + user: { + ...session.user, + ...userInfo + } + }) as Session; + + if (options.afterRefetch) { + newSession = await (options.afterRefetch as AfterRefetchAppRoute)(req, newSession); + } + + await sessionCache.set(req, res, newSession); + + return NextResponse.json(newSession.user, res); + } + + return NextResponse.json(session.user, res); + } catch (e) { + throw new ProfileHandlerError(e as HandlerErrorCause); + } + }; + +/** + * @ignore + */ +const pageRouteHandlerFactory: ( + client: AbstractClient, + getAccessToken: GetAccessToken, + sessionCache: SessionCache +) => (req: NextApiRequest, res: NextApiResponse, options?: ProfileOptions) => Promise = + (client, getAccessToken, sessionCache) => + async (req: NextApiRequest, res: NextApiResponse, options = {}): Promise => { try { assertReqRes(req, res); @@ -126,7 +212,6 @@ export default function profileHandler( throw new Error('No access token available to refetch the profile'); } - const client = await getClient(); const userInfo = await client.userinfo(accessToken); let newSession = fromJson({ @@ -138,7 +223,7 @@ export default function profileHandler( }) as Session; if (options.afterRefetch) { - newSession = await options.afterRefetch(req, res, newSession); + newSession = await (options.afterRefetch as AfterRefetchPageRoute)(req, res, newSession); } await sessionCache.set(req, res, newSession); @@ -152,17 +237,3 @@ export default function profileHandler( throw new ProfileHandlerError(e as HandlerErrorCause); } }; - return ( - reqOrOptions: NextApiRequest | ProfileOptionsProvider | ProfileOptions, - res?: NextApiResponse, - options?: ProfileOptions - ): any => { - if (reqOrOptions instanceof IncomingMessage && res) { - return profile(reqOrOptions, res, options); - } - if (typeof reqOrOptions === 'function') { - return (req: NextApiRequest, res: NextApiResponse) => profile(req, res, reqOrOptions(req)); - } - return (req: NextApiRequest, res: NextApiResponse) => profile(req, res, reqOrOptions as ProfileOptions); - }; -} diff --git a/src/handlers/router-helpers.ts b/src/handlers/router-helpers.ts new file mode 100644 index 000000000..ff36aa671 --- /dev/null +++ b/src/handlers/router-helpers.ts @@ -0,0 +1,85 @@ +import { NextRequest } from 'next/server'; +import { NextApiRequest, NextApiResponse } from 'next'; +import { isRequest } from '../utils/req-helpers'; + +export type AppRouteHandlerFnContext = { + params: Record; +}; + +/** + * Handler function for app routes. + */ +export type AppRouteHandlerFn = ( + /** + * Incoming request object. + */ + req: NextRequest, + /** + * Context properties on the request (including the parameters if this was a + * dynamic route). + */ + ctx: AppRouteHandlerFnContext, + + opts?: Options +) => Promise | Response; + +/** + * Handler function for app routes. + */ +export type PageRouteHandlerFn = ( + /** + * Incoming request object. + */ + req: NextApiRequest, + /** + * Context properties on the request (including the parameters if this was a + * dynamic route). + */ + res: NextApiResponse, + + opts?: Options +) => Promise | void; + +export type OptionsProvider = (req: NextApiRequest | NextRequest) => Opts; + +export type AuthHandler = Handler & { + (provider?: OptionsProvider): Handler; + (options?: Opts): Handler; +}; + +export type Handler = { + (req: NextRequest, ctx: AppRouteHandlerFnContext, options?: Opts): Promise | Response; + (req: NextApiRequest, res: NextApiResponse, options?: Opts): Promise | unknown; + (req: NextApiRequest | NextRequest, resOrOpts: NextApiResponse | AppRouteHandlerFnContext, options?: Opts): + | Promise + | Response + | unknown; +}; + +export const getHandler = + >( + appRouteHandler: AppRouteHandlerFn, + pageRouteHandler: PageRouteHandlerFn + ) => + ( + reqOrOptions: NextApiRequest | NextRequest | Opts, + resOrCtx: NextApiResponse | AppRouteHandlerFnContext, + options?: Opts + ) => { + if (isRequest(reqOrOptions)) { + return appRouteHandler(reqOrOptions as NextRequest, resOrCtx as AppRouteHandlerFnContext, options); + } + if ('socket' in reqOrOptions) { + return pageRouteHandler(reqOrOptions as NextApiRequest, resOrCtx as NextApiResponse, options); + } + return (req: NextApiRequest | NextRequest, resOrCtxInner: NextApiResponse | AppRouteHandlerFnContext) => { + const opts = ( + typeof reqOrOptions === 'function' ? (reqOrOptions as OptionsProvider)(req) : reqOrOptions + ) as Opts; + + if (isRequest(req)) { + return appRouteHandler(req as NextRequest, resOrCtxInner as AppRouteHandlerFnContext, opts); + } + return pageRouteHandler(req as NextApiRequest, resOrCtxInner as NextApiResponse, opts); + }; + }; diff --git a/src/helpers/index.ts b/src/helpers/index.ts index b2024e2e5..14a6fcdb2 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -1,8 +1,19 @@ -export { default as withApiAuthRequiredFactory, WithApiAuthRequired } from './with-api-auth-required'; +export { + default as withApiAuthRequiredFactory, + WithApiAuthRequired, + AppRouteHandlerFn, + WithApiAuthRequiredAppRoute, + WithApiAuthRequiredPageRoute +} from './with-api-auth-required'; export { default as withPageAuthRequiredFactory, GetServerSidePropsResultWithSession, WithPageAuthRequired, - WithPageAuthRequiredOptions, - PageRoute + WithPageAuthRequiredPageRouterOptions, + WithPageAuthRequiredAppRouterOptions, + PageRoute, + AppRouterPageRouteOpts, + AppRouterPageRoute, + WithPageAuthRequiredPageRouter, + WithPageAuthRequiredAppRouter } from './with-page-auth-required'; diff --git a/src/helpers/testing.ts b/src/helpers/testing.ts index 72a981fd2..400da27d1 100644 --- a/src/helpers/testing.ts +++ b/src/helpers/testing.ts @@ -1,4 +1,4 @@ -import { Config as BaseConfig, CookieConfig, StatelessSession, NodeCookies as Cookies } from '../auth0-session'; +import { Config as BaseConfig, CookieConfig, StatelessSession } from '../auth0-session'; import { Session } from '../session'; /** @@ -27,7 +27,7 @@ export const generateSessionCookie = async ( const weekInSeconds = 7 * 24 * 60 * 60; const { secret, duration: absoluteDuration = weekInSeconds, ...cookie } = config; const cookieStoreConfig = { secret, session: { absoluteDuration, cookie } }; - const cookieStore = new StatelessSession(cookieStoreConfig as BaseConfig, Cookies); + const cookieStore = new StatelessSession(cookieStoreConfig as BaseConfig); const epoch = (Date.now() / 1000) | 0; return cookieStore.encrypt(session, { iat: epoch, uat: epoch, exp: epoch + absoluteDuration }); }; diff --git a/src/helpers/with-api-auth-required.ts b/src/helpers/with-api-auth-required.ts index 76597cfb7..2972e049d 100644 --- a/src/helpers/with-api-auth-required.ts +++ b/src/helpers/with-api-auth-required.ts @@ -1,9 +1,61 @@ import { NextApiResponse, NextApiRequest, NextApiHandler } from 'next'; -import { SessionCache } from '../session'; +import { NextRequest, NextResponse } from 'next/server'; +import { get, SessionCache } from '../session'; import { assertReqRes } from '../utils/assert'; +import { isRequest } from '../utils/req-helpers'; /** - * Wrap an API route to check that the user has a valid session. If they're not logged in the + * This contains `param`s, which is an object containing the dynamic route parameters for the current route. + * + * See https://nextjs.org/docs/app/api-reference/file-conventions/route#context-optional + * + * @category Server + */ +export type AppRouteHandlerFnContext = { + params?: Record; +}; + +/** + * Handler function for app directory api routes. + * + * See: https://nextjs.org/docs/app/api-reference/file-conventions/route + * + * @category Server + */ +export type AppRouteHandlerFn = ( + /** + * Incoming request object. + */ + req: NextRequest, + /** + * Context properties on the request (including the parameters if this was a + * dynamic route). + */ + ctx: AppRouteHandlerFnContext +) => Promise | NextResponse; + +/** + * Wrap an app router API route to check that the user has a valid session. If they're not logged in the + * handler will return a 401 Unauthorized. + * + * ```js + * // app/protected-api/route.js + * import { withApiAuthRequired, getSession } from '@auth0/nextjs-auth0'; + * + * export default withApiAuthRequired(function Protected(req) { + * const session = getSession(); + * ... + * }); + * ``` + * + * If you visit `/protected-api` without a valid session cookie, you will get a 401 response. + * + * @category Server + */ +export type WithApiAuthRequiredAppRoute = (apiRoute: AppRouteHandlerFn) => AppRouteHandlerFn; + +/** + * Wrap a page router API route to check that the user has a valid session. If they're not logged in the * handler will return a 401 Unauthorized. * * ```js @@ -20,25 +72,80 @@ import { assertReqRes } from '../utils/assert'; * * @category Server */ -export type WithApiAuthRequired = (apiRoute: NextApiHandler) => NextApiHandler; +export type WithApiAuthRequiredPageRoute = (apiRoute: NextApiHandler) => NextApiHandler; + +/** + * Protects API routes for Page router pages {@link WithApiAuthRequiredPageRoute} or + * App router pages {@link WithApiAuthRequiredAppRoute} + * + * @category Server + */ +export type WithApiAuthRequired = WithApiAuthRequiredAppRoute & WithApiAuthRequiredPageRoute; /** * @ignore */ export default function withApiAuthFactory(sessionCache: SessionCache): WithApiAuthRequired { - return (apiRoute) => - async (req: NextApiRequest, res: NextApiResponse): Promise => { - assertReqRes(req, res); - - const session = await sessionCache.get(req, res); - if (!session || !session.user) { - res.status(401).json({ - error: 'not_authenticated', - description: 'The user does not have an active session or is not authenticated' - }); - return; - } + const pageRouteHandler = pageRouteHandlerFactory(sessionCache); + const appRouteHandler = appRouteHandlerFactory(sessionCache); - await apiRoute(req, res); + return (apiRoute: AppRouteHandlerFn | NextApiHandler): any => + (req: NextRequest | NextApiRequest, resOrParams: AppRouteHandlerFnContext | NextApiResponse) => { + if (isRequest(req)) { + return appRouteHandler(apiRoute as AppRouteHandlerFn)( + req as NextRequest, + resOrParams as AppRouteHandlerFnContext + ); + } + return (pageRouteHandler as WithApiAuthRequiredPageRoute)(apiRoute as NextApiHandler)( + req as NextApiRequest, + resOrParams as NextApiResponse + ); }; } + +/** + * @ignore + */ +const appRouteHandlerFactory = + (sessionCache: SessionCache): WithApiAuthRequiredAppRoute => + (apiRoute) => + async (req, params): Promise => { + const res = new NextResponse(); + const [session] = await get({ sessionCache, req, res }); + if (!session || !session.user) { + return NextResponse.json( + { error: 'not_authenticated', description: 'The user does not have an active session or is not authenticated' }, + { status: 401 } + ); + } + const apiRes: NextResponse | Response = await apiRoute(req, params); + const nextApiRes: NextResponse = apiRes instanceof NextResponse ? apiRes : new NextResponse(apiRes.body, apiRes); + for (const cookie of res.cookies.getAll()) { + if (!nextApiRes.cookies.get(cookie.name)) { + nextApiRes.cookies.set(cookie); + } + } + return nextApiRes; + }; + +/** + * @ignore + */ +const pageRouteHandlerFactory = + (sessionCache: SessionCache): WithApiAuthRequiredPageRoute => + (apiRoute) => + async (req, res) => { + assertReqRes(req, res); + + const session = await sessionCache.get(req, res); + if (!session || !session.user) { + res.status(401).json({ + error: 'not_authenticated', + description: 'The user does not have an active session or is not authenticated' + }); + return; + } + + await apiRoute(req, res); + }; diff --git a/src/helpers/with-middleware-auth-required.ts b/src/helpers/with-middleware-auth-required.ts index a65a6c57a..005242314 100644 --- a/src/helpers/with-middleware-auth-required.ts +++ b/src/helpers/with-middleware-auth-required.ts @@ -1,6 +1,5 @@ -import { NextMiddleware, NextRequest, NextResponse } from 'next/server'; +import { NextMiddleware, NextResponse } from 'next/server'; import { SessionCache } from '../session'; -import { splitCookiesString } from '../utils/middleware-cookies'; /** * Protect your pages with Next.js Middleware. For example: @@ -50,14 +49,14 @@ export type WithMiddlewareAuthRequired = (middleware?: NextMiddleware) => NextMi * @ignore */ export default function withMiddlewareAuthRequiredFactory( - { login, callback, unauthorized }: { login: string; callback: string; unauthorized: string }, - getSessionCache: () => SessionCache + { login, callback }: { login: string; callback: string }, + getSessionCache: () => SessionCache ): WithMiddlewareAuthRequired { return function withMiddlewareAuthRequired(middleware?): NextMiddleware { return async function wrappedMiddleware(...args) { const [req] = args; const { pathname, origin, search } = req.nextUrl; - const ignorePaths = [login, callback, unauthorized, '/_next', '/favicon.ico']; + const ignorePaths = [login, callback, '/_next', '/favicon.ico']; if (ignorePaths.some((p) => pathname.startsWith(p))) { return; } @@ -68,7 +67,13 @@ export default function withMiddlewareAuthRequiredFactory( const session = await sessionCache.get(req, authRes); if (!session?.user) { if (pathname.startsWith('/api')) { - return NextResponse.rewrite(new URL(unauthorized, origin), { status: 401 }); + return NextResponse.json( + { + error: 'not_authenticated', + description: 'The user does not have an active session or is not authenticated' + }, + { status: 401 } + ); } return NextResponse.redirect( new URL(`${login}?returnTo=${encodeURIComponent(`${pathname}${search}`)}`, origin) @@ -77,14 +82,19 @@ export default function withMiddlewareAuthRequiredFactory( const res = await (middleware && middleware(...args)); if (res) { - const headers = new Headers(res.headers); - const authCookies = splitCookiesString(authRes.headers.get('set-cookie')!); - if (authCookies.length) { - for (const cookie of authCookies) { - headers.append('set-cookie', cookie); + const nextRes = new NextResponse(res.body, res); + let cookies = authRes.cookies.getAll(); + if ('cookies' in res) { + for (const cookie of res.cookies.getAll()) { + nextRes.cookies.set(cookie); } } - return NextResponse.next({ ...res, status: res.status, headers }); + for (const cookie of cookies) { + if (!nextRes.cookies.get(cookie.name)) { + nextRes.cookies.set(cookie); + } + } + return nextRes; } else { return authRes; } diff --git a/src/helpers/with-page-auth-required.ts b/src/helpers/with-page-auth-required.ts index 69db1774d..b5061e38a 100644 --- a/src/helpers/with-page-auth-required.ts +++ b/src/helpers/with-page-auth-required.ts @@ -1,5 +1,7 @@ +import type React from 'react'; import { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from 'next'; -import { Claims, SessionCache } from '../session'; +import { redirect } from 'next/navigation'; +import { Claims, get, SessionCache } from '../session'; import { assertCtx } from '../utils/assert'; import { ParsedUrlQuery } from 'querystring'; @@ -28,9 +30,26 @@ export type GetServerSidePropsResultWithSession

= GetServerSidePropsRes * @category Server */ export type PageRoute = ( - cts: GetServerSidePropsContext + ctx: GetServerSidePropsContext ) => Promise>; +/** + * Objects containing the route parameters and search parameters of th page. + * + * @category Server + */ +export type AppRouterPageRouteOpts = { + params?: { slug: string }; + searchParams?: { [key: string]: string | string[] | undefined }; +}; + +/** + * An app route that has been augmented with {@link WithPageAuthRequired}. + * + * @category Server + */ +export type AppRouterPageRoute = (obj: AppRouterPageRouteOpts) => Promise; + /** * If you have a custom returnTo url you should specify it in `returnTo`. * @@ -62,7 +81,7 @@ export type PageRoute = ( * * @category Server */ -export type WithPageAuthRequiredOptions< +export type WithPageAuthRequiredPageRouterOptions< P extends { [key: string]: any } = { [key: string]: any }, Q extends ParsedUrlQuery = ParsedUrlQuery > = { @@ -90,13 +109,74 @@ export type WithPageAuthRequiredOptions< * * @category Server */ -export type WithPageAuthRequired = < +export type WithPageAuthRequiredPageRouter = < P extends { [key: string]: any } = { [key: string]: any }, Q extends ParsedUrlQuery = ParsedUrlQuery >( - opts?: WithPageAuthRequiredOptions + opts?: WithPageAuthRequiredPageRouterOptions ) => PageRoute; +/** + * Specify the URL to `returnTo` - this is important in app router pages because the server component + * won't know the URL of the page. + * + * @category Server + */ +export type WithPageAuthRequiredAppRouterOptions = { + returnTo?: string | ((obj: AppRouterPageRouteOpts) => Promise | string); +}; + +/** + * Wrap your Server Component with this method to make sure the user is authenticated before + * visiting the page. + * + * ```js + * // app/protected-page/page.js + * import { withPageAuthRequired } from '@auth0/nextjs-auth0'; + * + * export default function withPageAuthRequired(ProtectedPage() { + * return

Protected content
; + * }, { returnTo: '/protected-page' }); + * ``` + * + * If the user visits `/protected-page` without a valid session, it will redirect the user to the + * login page. + * + * Note: Server Components are not aware of the req or the url of the page. So if you want the user to return to the + * page after login, you must specify the `returnTo` option. + * + * You can specify a function to `returnTo` that accepts the `params` (An object containing the dynamic + * route parameters) and `searchParams` (An object containing the search parameters of the current URL) + * argument from the page, to preserve dynamic routes and search params. + * + * ```js + * // app/protected-page/[slug]/page.js + * import { withPageAuthRequired } from '@auth0/nextjs-auth0'; + * + * export default function withPageAuthRequired(ProtectedPage() { + * return
Protected content
; + * }, { + * returnTo({ params }) { + * return `/protected-page/${params.slug}` + * } + * }); + * ``` + * + * @category Server + */ +export type WithPageAuthRequiredAppRouter = ( + fn: AppRouterPageRoute, + opts?: WithPageAuthRequiredAppRouterOptions +) => AppRouterPageRoute; + +/** + * Protects Page router pages {@link WithPageAuthRequiredPageRouter} or + * App router pages {@link WithPageAuthRequiredAppRouter} + * + * @category Server + */ +export type WithPageAuthRequired = WithPageAuthRequiredPageRouter & WithPageAuthRequiredAppRouter; + /** * @ignore */ @@ -104,26 +184,60 @@ export default function withPageAuthRequiredFactory( loginUrl: string, getSessionCache: () => SessionCache ): WithPageAuthRequired { - return ({ getServerSideProps, returnTo } = {}) => - async (ctx) => { - assertCtx(ctx); - const sessionCache = getSessionCache(); - const session = await sessionCache.get(ctx.req, ctx.res); - if (!session?.user) { - return { - redirect: { - destination: `${loginUrl}?returnTo=${encodeURIComponent(returnTo || ctx.resolvedUrl)}`, - permanent: false - } - }; - } - let ret: any = { props: {} }; - if (getServerSideProps) { - ret = await getServerSideProps(ctx); - } - if (ret.props instanceof Promise) { - return { ...ret, props: ret.props.then((props: any) => ({ user: session.user, ...props })) }; - } - return { ...ret, props: { user: session.user, ...ret.props } }; - }; + const appRouteHandler = appRouteHandlerFactory(loginUrl, getSessionCache); + const pageRouteHandler = pageRouteHandlerFactory(loginUrl, getSessionCache); + + return (( + fnOrOpts?: WithPageAuthRequiredPageRouterOptions | AppRouterPageRoute, + opts?: WithPageAuthRequiredAppRouterOptions + ) => { + if (typeof fnOrOpts === 'function') { + return appRouteHandler(fnOrOpts, opts); + } + return pageRouteHandler(fnOrOpts); + }) as WithPageAuthRequired; } + +/** + * @ignore + */ +const appRouteHandlerFactory = + (loginUrl: string, getSessionCache: () => SessionCache): WithPageAuthRequiredAppRouter => + (handler, opts = {}) => + async (params) => { + const sessionCache = getSessionCache(); + const [session] = await get({ sessionCache }); + if (!session?.user) { + const returnTo = typeof opts.returnTo === 'function' ? await opts.returnTo(params) : opts.returnTo; + redirect(`${loginUrl}${opts.returnTo ? `?returnTo=${returnTo}` : ''}`); + } + return handler(params); + }; + +/** + * @ignore + */ +const pageRouteHandlerFactory = + (loginUrl: string, getSessionCache: () => SessionCache): WithPageAuthRequiredPageRouter => + ({ getServerSideProps, returnTo } = {}) => + async (ctx) => { + assertCtx(ctx); + const sessionCache = getSessionCache(); + const session = await sessionCache.get(ctx.req, ctx.res); + if (!session?.user) { + return { + redirect: { + destination: `${loginUrl}?returnTo=${encodeURIComponent(returnTo || ctx.resolvedUrl)}`, + permanent: false + } + }; + } + let ret: any = { props: {} }; + if (getServerSideProps) { + ret = await getServerSideProps(ctx); + } + if (ret.props instanceof Promise) { + return { ...ret, props: ret.props.then((props: any) => ({ user: session.user, ...props })) }; + } + return { ...ret, props: { user: session.user, ...ret.props } }; + }; diff --git a/src/http/auth0-next-api-request.ts b/src/http/auth0-next-api-request.ts new file mode 100644 index 000000000..6814cb319 --- /dev/null +++ b/src/http/auth0-next-api-request.ts @@ -0,0 +1,22 @@ +import { Auth0Request } from '../auth0-session/http'; +import { NextApiRequest } from 'next'; + +export default class Auth0NextApiRequest extends Auth0Request { + public constructor(req: NextApiRequest) { + /* c8 ignore next */ + super(req); + } + + public getUrl(): string { + return this.req.url as string; + } + public getMethod(): string { + return this.req.method as string; + } + public getBody(): Record { + return this.req.body; + } + public getCookies(): Record { + return this.req.cookies as Record; + } +} diff --git a/src/http/auth0-next-api-response.ts b/src/http/auth0-next-api-response.ts new file mode 100644 index 000000000..b031878e1 --- /dev/null +++ b/src/http/auth0-next-api-response.ts @@ -0,0 +1,11 @@ +import { NodeResponse } from '../auth0-session/http'; +import { NextApiResponse } from 'next'; + +export default class Auth0NextApiResponse extends NodeResponse { + public redirect(location: string, status = 302): void { + if (this.res.writableEnded) { + return; + } + this.res.redirect(status, (this.res.getHeader('Location') as string) || location); + } +} diff --git a/src/http/auth0-next-request-cookies.ts b/src/http/auth0-next-request-cookies.ts new file mode 100644 index 000000000..87459fc69 --- /dev/null +++ b/src/http/auth0-next-request-cookies.ts @@ -0,0 +1,13 @@ +import { cookies } from 'next/headers'; +import { Auth0RequestCookies } from '../auth0-session/http'; + +export default class Auth0NextRequestCookies extends Auth0RequestCookies { + public constructor() { + super(); + } + + public getCookies(): Record { + const cookieStore = cookies(); + return cookieStore.getAll().reduce((memo, { name, value }) => ({ ...memo, [name]: value }), {}); + } +} diff --git a/src/http/auth0-next-request.ts b/src/http/auth0-next-request.ts new file mode 100644 index 000000000..f750e9a90 --- /dev/null +++ b/src/http/auth0-next-request.ts @@ -0,0 +1,31 @@ +import { Auth0Request } from '../auth0-session/http'; +import { NextRequest } from 'next/server'; + +export default class Auth0NextRequest extends Auth0Request { + public constructor(req: NextRequest) { + /* c8 ignore next */ + super(req); + } + + public getUrl(): string { + return this.req.url as string; + } + public getMethod(): string { + return this.req.method as string; + } + public async getBody(): Promise | string> { + return this.req.text(); + } + public getCookies(): Record { + const { cookies } = this.req; + if (typeof cookies.getAll === 'function') { + return this.req.cookies.getAll().reduce((memo, { name, value }) => ({ ...memo, [name]: value }), {}); + } + // Edge cookies before Next 13.0.1 have no `getAll` and extend `Map`. + const legacyCookies = cookies as unknown as Map; + return Array.from(legacyCookies.keys()).reduce((memo: { [key: string]: string }, key) => { + memo[key] = legacyCookies.get(key) as string; + return memo; + }, {}); + } +} diff --git a/src/http/auth0-next-response-cookies.ts b/src/http/auth0-next-response-cookies.ts new file mode 100644 index 000000000..c0ee9d540 --- /dev/null +++ b/src/http/auth0-next-response-cookies.ts @@ -0,0 +1,40 @@ +import { cookies } from 'next/headers'; +import type { CookieSerializeOptions } from 'cookie'; +import { Auth0ResponseCookies } from '../auth0-session/http'; + +let warned = false; + +const warn = () => { + /* c8 ignore next 8 */ + if (process.env.NODE_ENV === 'development' && !warned) { + console.warn( + 'nextjs-auth0 is attempting to set cookies from a server component,' + + 'see https://github.com/auth0/nextjs-auth0/tree/beta#important-limitations-of-the-app-directory' + ); + warned = true; + } +}; + +export default class Auth0NextResponseCookies extends Auth0ResponseCookies { + public constructor() { + super(); + } + + public setCookie(name: string, value: string, options?: CookieSerializeOptions) { + const cookieSetter = cookies(); + try { + cookieSetter.set({ ...options, name, value }); + } catch (_) { + warn(); + } + } + + public clearCookie(name: string, options?: CookieSerializeOptions) { + const cookieSetter = cookies(); + try { + cookieSetter.delete({ ...options, name, value: '' }); + } catch (_) { + warn(); + } + } +} diff --git a/src/http/auth0-next-response.ts b/src/http/auth0-next-response.ts new file mode 100644 index 000000000..cb037b0ec --- /dev/null +++ b/src/http/auth0-next-response.ts @@ -0,0 +1,30 @@ +import { NextResponse } from 'next/server'; +import type { CookieSerializeOptions } from 'cookie'; +import { Auth0Response } from '../auth0-session/http'; + +export default class Auth0NextResponse extends Auth0Response { + public constructor(res: NextResponse) { + /* c8 ignore next */ + super(res); + } + + public setCookie(name: string, value: string, options?: CookieSerializeOptions) { + this.res.cookies.set(name, value, options); + } + + public clearCookie(name: string, options?: CookieSerializeOptions) { + this.res.cookies.delete({ ...options, name, value: '' }); + } + + public redirect(location: string, status = 302): void { + const oldRes = this.res; + this.res = new NextResponse(null, { status }); + oldRes.headers.forEach((value, key) => { + this.res.headers.set(key, value); + }); + this.res.headers.set('location', location); + for (const cookie of oldRes.cookies.getAll()) { + this.res.cookies.set(cookie); + } + } +} diff --git a/src/http/index.ts b/src/http/index.ts new file mode 100644 index 000000000..25f8d76b2 --- /dev/null +++ b/src/http/index.ts @@ -0,0 +1,6 @@ +export { default as Auth0NextApiRequest } from './auth0-next-api-request'; +export { default as Auth0NextApiResponse } from './auth0-next-api-response'; +export { default as Auth0NextRequest } from './auth0-next-request'; +export { default as Auth0NextResponse } from './auth0-next-response'; +export { default as Auth0NextRequestCookies } from './auth0-next-request-cookies'; +export { default as Auth0NextResponseCookies } from './auth0-next-response-cookies'; diff --git a/src/index.ts b/src/index.ts index 3b5001c98..221ba3022 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,132 +1,31 @@ import crypto from 'crypto'; import { - NodeCookies as Cookies, - StatelessSession, - StatefulSession, - SessionStore as GenericSessionStore, - SessionPayload, - TransientStore, - clientFactory, - loginHandler as baseLoginHandler, - logoutHandler as baseLogoutHandler, - callbackHandler as baseCallbackHandler -} from './auth0-session'; -import { - handlerFactory, - callbackHandler, - loginHandler, - logoutHandler, - profileHandler, - Handlers, + Auth0Server as Auth0ServerShared, + ConfigParameters, + GetAccessToken, + GetSession, HandleAuth, + HandleCallback, HandleLogin, - HandleProfile, HandleLogout, - HandleCallback, - LoginOptions, - LogoutOptions, - GetLoginState, - ProfileOptions, - CallbackOptions, - AfterCallback, - AfterRefetch, - OnError -} from './handlers'; -import { - sessionFactory, - accessTokenFactory, + HandleProfile, SessionCache, - GetSession, - GetAccessToken, - Session, - AccessTokenRequest, - GetAccessTokenResult, - Claims, - touchSessionFactory, - TouchSession, - updateSessionFactory, - UpdateSession -} from './session/'; -import { - withPageAuthRequiredFactory, - withApiAuthRequiredFactory, + UpdateSession, WithApiAuthRequired, WithPageAuthRequired, - GetServerSidePropsResultWithSession, - WithPageAuthRequiredOptions, - PageRoute -} from './helpers'; -import version from './version'; -import { getConfig, getLoginUrl, ConfigParameters } from './config'; + telemetry +} from './shared'; +import { _initAuth } from './init'; import { setIsUsingNamedExports, setIsUsingOwnInstance } from './utils/instance-check'; -import { IncomingMessage, ServerResponse } from 'http'; -import { NextApiRequest, NextApiResponse } from 'next'; - -/** - * The SDK server instance. - * - * This is created for you when you use the named exports, or you can create your own using {@link InitAuth0}. - * - * See {@link ConfigParameters} for more info. - * - * @category Server - */ -export interface Auth0Server { - /** - * Session getter. - */ - getSession: GetSession; - - /** - * Update the expiry of a rolling session when autoSave is disabled. - */ - touchSession: TouchSession; +import { getConfig, getLoginUrl } from './config'; +import { withPageAuthRequiredFactory } from './helpers'; +import { NodeClient } from './auth0-session/client/node-client'; - /** - * Append properties to the user. - */ - updateSession: UpdateSession; - - /** - * Access token getter. - */ - getAccessToken: GetAccessToken; - - /** - * Login handler which will redirect the user to Auth0. - */ - handleLogin: HandleLogin; - - /** - * Callback handler which will complete the transaction and create a local session. - */ - handleCallback: HandleCallback; - - /** - * Logout handler which will clear the local session and the Auth0 session. - */ - handleLogout: HandleLogout; - - /** - * Profile handler which return profile information about the user. - */ - handleProfile: HandleProfile; - - /** - * Helper that adds auth to an API route. - */ - withApiAuthRequired: WithApiAuthRequired; +const genId = () => crypto.randomBytes(16).toString('hex'); - /** - * Helper that adds auth to a Page route. - */ - withPageAuthRequired: WithPageAuthRequired; +export type Auth0Server = Omit; - /** - * Create the main handlers for your api routes. - */ - handleAuth: HandleAuth; -} +let instance: Auth0ServerShared & { sessionCache: SessionCache }; /** * Initialise your own instance of the SDK. @@ -135,85 +34,35 @@ export interface Auth0Server { * * @category Server */ -export type InitAuth0 = (params?: ConfigParameters) => Auth0Server; - -let instance: Auth0Server & { sessionCache: SessionCache }; - -const genId = () => crypto.randomBytes(16).toString('hex'); +export type InitAuth0 = (params?: ConfigParameters) => Omit; // For using managed instance with named exports. -function getInstance(): Auth0Server & { sessionCache: SessionCache } { +function getInstance(): Auth0ServerShared & { sessionCache: SessionCache } { setIsUsingNamedExports(); if (instance) { return instance; } - instance = _initAuth(); + const { baseConfig, nextConfig } = getConfig({ session: { genId } }); + const client = new NodeClient(baseConfig, telemetry); + instance = _initAuth({ baseConfig, nextConfig, client }); return instance; } // For creating own instance. export const initAuth0: InitAuth0 = (params) => { setIsUsingOwnInstance(); - const { sessionCache, ...publicApi } = _initAuth(params); // eslint-disable-line @typescript-eslint/no-unused-vars - return publicApi; -}; - -export const _initAuth = (params?: ConfigParameters): Auth0Server & { sessionCache: SessionCache } => { const { baseConfig, nextConfig } = getConfig({ ...params, session: { genId, ...params?.session } }); - - // Init base layer (with base config) - const getClient = clientFactory(baseConfig, { name: 'nextjs-auth0', version }); - const transientStore = new TransientStore(baseConfig); - - const sessionStore = baseConfig.session.store - ? new StatefulSession( - baseConfig, - Cookies - ) - : new StatelessSession( - baseConfig, - Cookies - ); - const sessionCache = new SessionCache(baseConfig, sessionStore); - const baseHandleLogin = baseLoginHandler(baseConfig, getClient, transientStore); - const baseHandleLogout = baseLogoutHandler(baseConfig, getClient, sessionCache); - const baseHandleCallback = baseCallbackHandler(baseConfig, getClient, sessionCache, transientStore); - - // Init Next layer (with next config) - const getSession = sessionFactory(sessionCache); - const touchSession = touchSessionFactory(sessionCache); - const updateSession = updateSessionFactory(sessionCache); - const getAccessToken = accessTokenFactory(nextConfig, getClient, sessionCache); - const withApiAuthRequired = withApiAuthRequiredFactory(sessionCache); - const withPageAuthRequired = withPageAuthRequiredFactory(nextConfig.routes.login, () => sessionCache); - const handleLogin = loginHandler(baseHandleLogin, nextConfig, baseConfig); - const handleLogout = logoutHandler(baseHandleLogout); - const handleCallback = callbackHandler(baseHandleCallback, nextConfig); - const handleProfile = profileHandler(getClient, getAccessToken, sessionCache); - const handleAuth = handlerFactory({ handleLogin, handleLogout, handleCallback, handleProfile }); - - return { - sessionCache, - getSession, - touchSession, - updateSession, - getAccessToken, - withApiAuthRequired, - withPageAuthRequired, - handleLogin, - handleLogout, - handleCallback, - handleProfile, - handleAuth - }; + const client = new NodeClient(baseConfig, telemetry); + const { sessionCache, withMiddlewareAuthRequired, ...publicApi } = _initAuth({ baseConfig, nextConfig, client }); + return publicApi; }; -/* c8 ignore start */ const getSessionCache = () => getInstance().sessionCache; export const getSession: GetSession = (...args) => getInstance().getSession(...args); export const updateSession: UpdateSession = (...args) => getInstance().updateSession(...args); export const getAccessToken: GetAccessToken = (...args) => getInstance().getAccessToken(...args); -export const withApiAuthRequired: WithApiAuthRequired = (...args) => getInstance().withApiAuthRequired(...args); +export const withApiAuthRequired: WithApiAuthRequired = (...args) => + (getInstance().withApiAuthRequired as any)(...args); export const withPageAuthRequired: WithPageAuthRequired = withPageAuthRequiredFactory(getLoginUrl(), getSessionCache); export const handleLogin: HandleLogin = ((...args: Parameters) => getInstance().handleLogin(...args)) as HandleLogin; @@ -225,56 +74,4 @@ export const handleProfile: HandleProfile = ((...args: Parameters getInstance().handleProfile(...args)) as HandleProfile; export const handleAuth: HandleAuth = (...args) => getInstance().handleAuth(...args); -export { - AuthError, - AccessTokenErrorCode, - AccessTokenError, - HandlerError, - CallbackHandlerError, - LoginHandlerError, - LogoutHandlerError, - ProfileHandlerError -} from './utils/errors'; - -export { - MissingStateCookieError, - MissingStateParamError, - IdentityProviderError, - ApplicationError -} from './auth0-session'; - -export { - ConfigParameters, - HandleAuth, - HandleLogin, - HandleProfile, - HandleLogout, - HandleCallback, - ProfileOptions, - Handlers, - GetServerSidePropsResultWithSession, - WithPageAuthRequiredOptions, - PageRoute, - WithApiAuthRequired, - WithPageAuthRequired, - SessionCache, - GetSession, - TouchSession, - UpdateSession, - GetAccessToken, - Session, - Claims, - AccessTokenRequest, - GetAccessTokenResult, - CallbackOptions, - AfterCallback, - AfterRefetch, - LoginOptions, - LogoutOptions, - GetLoginState, - OnError -}; - -export type SessionStore = GenericSessionStore; -export type SessionStorePayload = SessionPayload; -/* c8 ignore stop */ +export * from './shared'; diff --git a/src/init.ts b/src/init.ts new file mode 100644 index 000000000..73cb5779f --- /dev/null +++ b/src/init.ts @@ -0,0 +1,84 @@ +import { + StatelessSession, + StatefulSession, + TransientStore, + loginHandler as baseLoginHandler, + logoutHandler as baseLogoutHandler, + callbackHandler as baseCallbackHandler, + AbstractClient +} from './auth0-session'; +import { handlerFactory, callbackHandler, loginHandler, logoutHandler, profileHandler } from './handlers'; +import { + sessionFactory, + accessTokenFactory, + SessionCache, + Session, + touchSessionFactory, + updateSessionFactory +} from './session/'; +import { withPageAuthRequiredFactory, withApiAuthRequiredFactory } from './helpers'; +import { ConfigParameters, BaseConfig, NextConfig } from './config'; +import { Auth0Server } from './shared'; +import withMiddlewareAuthRequiredFactory from './helpers/with-middleware-auth-required'; + +/** + * Initialise your own instance of the SDK. + * + * See {@link ConfigParameters}. + * + * @category Server + */ +export type InitAuth0 = (params?: ConfigParameters) => Auth0Server; + +export const _initAuth = ({ + baseConfig, + nextConfig, + client +}: { + baseConfig: BaseConfig; + nextConfig: NextConfig; + client: AbstractClient; +}): Auth0Server & { + sessionCache: SessionCache; +} => { + // Init base layer (with base config) + const transientStore = new TransientStore(baseConfig); + + const sessionStore = baseConfig.session.store + ? new StatefulSession(baseConfig) + : new StatelessSession(baseConfig); + const sessionCache = new SessionCache(baseConfig, sessionStore); + const baseHandleLogin = baseLoginHandler(baseConfig, client, transientStore); + const baseHandleLogout = baseLogoutHandler(baseConfig, client, sessionCache); + const baseHandleCallback = baseCallbackHandler(baseConfig, client, sessionCache, transientStore); + + // Init Next layer (with next config) + const getSession = sessionFactory(sessionCache); + const touchSession = touchSessionFactory(sessionCache); + const updateSession = updateSessionFactory(sessionCache); + const getAccessToken = accessTokenFactory(nextConfig, client, sessionCache); + const withApiAuthRequired = withApiAuthRequiredFactory(sessionCache); + const withPageAuthRequired = withPageAuthRequiredFactory(nextConfig.routes.login, () => sessionCache); + const handleLogin = loginHandler(baseHandleLogin, nextConfig, baseConfig); + const handleLogout = logoutHandler(baseHandleLogout); + const handleCallback = callbackHandler(baseHandleCallback, nextConfig); + const handleProfile = profileHandler(client, getAccessToken, sessionCache); + const handleAuth = handlerFactory({ handleLogin, handleLogout, handleCallback, handleProfile }); + const withMiddlewareAuthRequired = withMiddlewareAuthRequiredFactory(nextConfig.routes, () => sessionCache); + + return { + sessionCache, + getSession, + touchSession, + updateSession, + getAccessToken, + withApiAuthRequired, + withPageAuthRequired, + handleLogin, + handleLogout, + handleCallback, + handleProfile, + handleAuth, + withMiddlewareAuthRequired + }; +}; diff --git a/src/session/cache.ts b/src/session/cache.ts index f7b9e01a8..4c7ac7c77 100644 --- a/src/session/cache.ts +++ b/src/session/cache.ts @@ -1,25 +1,46 @@ import { IncomingMessage, ServerResponse } from 'http'; import { NextApiRequest, NextApiResponse } from 'next'; -import type { TokenSet } from 'openid-client'; +import { NextRequest, NextResponse } from 'next/server'; +import type { TokenEndpointResponse } from '../auth0-session'; import { Config, SessionCache as ISessionCache, AbstractSession } from '../auth0-session'; -import Session, { fromJson, fromTokenSet } from './session'; +import Session, { fromJson, fromTokenEndpointResponse } from './session'; +import { Auth0Request, Auth0Response, NodeRequest, NodeResponse } from '../auth0-session/http'; +import { + Auth0NextApiRequest, + Auth0NextApiResponse, + Auth0NextRequestCookies, + Auth0NextResponseCookies, + Auth0NextRequest, + Auth0NextResponse +} from '../http'; +import { isNextApiRequest, isRequest } from '../utils/req-helpers'; -export default class SessionCache< - Req extends object = IncomingMessage | NextApiRequest, // eslint-disable-line @typescript-eslint/ban-types - Res extends object = ServerResponse | NextApiResponse // eslint-disable-line @typescript-eslint/ban-types -> implements ISessionCache -{ - private cache: WeakMap; +type Req = IncomingMessage | NextRequest | NextApiRequest; +type Res = ServerResponse | NextResponse | NextApiResponse; + +const getAuth0ReqRes = (req: Req, res: Res): [Auth0Request, Auth0Response] => { + if (isRequest(req)) { + return [new Auth0NextRequest(req as NextRequest), new Auth0NextResponse(res as NextResponse)]; + } + if (isNextApiRequest(req)) { + return [new Auth0NextApiRequest(req as NextApiRequest), new Auth0NextApiResponse(res as NextApiResponse)]; + } + return [new NodeRequest(req as IncomingMessage), new NodeResponse(res as ServerResponse)]; +}; + +export default class SessionCache implements ISessionCache { + private cache: WeakMap; private iatCache: WeakMap; - constructor(private config: Config, private sessionStore: AbstractSession) { + constructor(public config: Config, public sessionStore: AbstractSession) { this.cache = new WeakMap(); this.iatCache = new WeakMap(); } private async init(req: Req, res: Res, autoSave = true): Promise { if (!this.cache.has(req)) { - const [json, iat] = await this.sessionStore.read(req); + const [auth0Req] = getAuth0ReqRes(req, res); + const [json, iat] = await this.sessionStore.read(auth0Req); this.iatCache.set(req, iat); this.cache.set(req, fromJson(json)); if (this.config.session.rolling && this.config.session.autoSave && autoSave) { @@ -29,7 +50,8 @@ export default class SessionCache< } async save(req: Req, res: Res): Promise { - await this.sessionStore.save(req, res, this.cache.get(req), this.iatCache.get(req)); + const [auth0Req, auth0Res] = getAuth0ReqRes(req, res); + await this.sessionStore.save(auth0Req, auth0Res, this.cache.get(req), this.iatCache.get(req)); } async create(req: Req, res: Res, session: Session): Promise { @@ -55,7 +77,7 @@ export default class SessionCache< return session?.idToken; } - async set(req: Req, res: Res, session: Session | null): Promise { + async set(req: Req, res: Res, session: Session | null | undefined): Promise { await this.init(req, res, false); this.cache.set(req, session); await this.save(req, res); @@ -66,7 +88,53 @@ export default class SessionCache< return this.cache.get(req); } - fromTokenSet(tokenSet: TokenSet): Session { - return fromTokenSet(tokenSet, this.config); + fromTokenEndpointResponse(tokenSet: TokenEndpointResponse): Session { + return fromTokenEndpointResponse(tokenSet, this.config); } } + +export const get = async ({ + sessionCache, + req, + res +}: { + sessionCache: SessionCache; + req?: Req; + res?: Res; +}): Promise<[(Session | null)?, number?]> => { + if (req && res) { + return [await sessionCache.get(req, res)]; + } + const { + sessionStore, + config: { + session: { rolling, autoSave } + } + } = sessionCache; + const auth0Req = new Auth0NextRequestCookies(); + const [session, iat] = await sessionStore.read(auth0Req); + if (rolling && autoSave) { + await set({ session, sessionCache, iat }); + } + return [session, iat]; +}; + +export const set = async ({ + session, + sessionCache, + iat, + req, + res +}: { + session?: Session | null; + sessionCache: SessionCache; + iat?: number; + req?: Req; + res?: Res; +}) => { + if (req && res) { + return sessionCache.set(req, res, session); + } + const { sessionStore } = sessionCache; + await sessionStore.save(new Auth0NextRequestCookies(), new Auth0NextResponseCookies(), session, iat); +}; diff --git a/src/session/get-access-token.ts b/src/session/get-access-token.ts index cf99fe83c..a25c85d96 100644 --- a/src/session/get-access-token.ts +++ b/src/session/get-access-token.ts @@ -1,13 +1,36 @@ import { IncomingMessage, ServerResponse } from 'http'; import { NextApiRequest, NextApiResponse } from 'next'; -import type { errors } from 'openid-client'; -import { ClientFactory, IdentityProviderError } from '../auth0-session'; +import { AbstractClient } from '../auth0-session'; import { AccessTokenError, AccessTokenErrorCode } from '../utils/errors'; import { intersect, match } from '../utils/array'; -import { Session, SessionCache, fromTokenSet } from '../session'; +import { Session, SessionCache, fromTokenEndpointResponse, get, set } from '../session'; import { AuthorizationParameters, NextConfig } from '../config'; +import { NextRequest, NextResponse } from 'next/server'; -export type AfterRefresh = (req: NextApiRequest, res: NextApiResponse, session: Session) => Promise | Session; +/** + * After refresh handler for page router {@link AfterRefreshPageRoute} and app router {@link AfterRefreshAppRoute}. + * + * @category Server + */ +export type AfterRefresh = AfterRefreshPageRoute | AfterRefreshAppRoute; + +/** + * After refresh handler for page router. + * + * @category Server + */ +export type AfterRefreshPageRoute = ( + req: NextApiRequest | IncomingMessage, + res: NextApiRequest | ServerResponse, + session: Session +) => Promise | Session; + +/** + * After refresh handler for app router. + * + * @category Server + */ +export type AfterRefreshAppRoute = (session: Session) => Promise | Session; /** * Custom options to get an access token. @@ -84,9 +107,11 @@ export interface GetAccessTokenResult { * @category Server */ export type GetAccessToken = ( - req: IncomingMessage | NextApiRequest, - res: ServerResponse | NextApiResponse, - accessTokenRequest?: AccessTokenRequest + ...args: + | [IncomingMessage, ServerResponse, AccessTokenRequest?] + | [NextApiRequest, NextApiResponse, AccessTokenRequest?] + | [NextRequest, NextResponse, AccessTokenRequest?] + | [AccessTokenRequest?] ) => Promise; /** @@ -94,11 +119,16 @@ export type GetAccessToken = ( */ export default function accessTokenFactory( config: NextConfig, - getClient: ClientFactory, + client: AbstractClient, sessionCache: SessionCache ): GetAccessToken { - return async (req, res, accessTokenRequest): Promise => { - let session = await sessionCache.get(req, res); + return async (reqOrOpts?, res?, accessTokenRequest?): Promise => { + const options = (res ? accessTokenRequest : reqOrOpts) as AccessTokenRequest | undefined; + const req = (res ? reqOrOpts : undefined) as IncomingMessage | NextApiRequest | undefined; + + const parts = await get({ sessionCache, req, res }); + let [session] = parts; + const [, iat] = parts; if (!session) { throw new AccessTokenError(AccessTokenErrorCode.MISSING_SESSION, 'The user does not have a valid session.'); } @@ -117,7 +147,7 @@ export default function accessTokenFactory( ); } - if (accessTokenRequest && accessTokenRequest.scopes) { + if (options && options.scopes) { const persistedScopes = session.accessTokenScope; if (!persistedScopes || persistedScopes.length === 0) { throw new AccessTokenError( @@ -126,11 +156,11 @@ export default function accessTokenFactory( ); } - const matchingScopes = intersect(accessTokenRequest.scopes, persistedScopes.split(' ')); - if (!match(accessTokenRequest.scopes, [...matchingScopes])) { + const matchingScopes = intersect(options.scopes, persistedScopes.split(' ')); + if (!match(options.scopes, [...matchingScopes])) { throw new AccessTokenError( AccessTokenErrorCode.INSUFFICIENT_SCOPE, - `Could not retrieve an access token with scopes "${accessTokenRequest.scopes.join( + `Could not retrieve an access token with scopes "${options.scopes.join( ' ' )}". The user will need to sign in again.` ); @@ -147,7 +177,7 @@ export default function accessTokenFactory( ); } - if (accessTokenRequest?.refresh && !session.refreshToken) { + if (options?.refresh && !session.refreshToken) { throw new AccessTokenError( AccessTokenErrorCode.MISSING_REFRESH_TOKEN, 'A refresh token is required to refresh the access token, but none is present.' @@ -159,35 +189,33 @@ export default function accessTokenFactory( // Adding a skew of 1 minute to compensate. if ( (session.refreshToken && session.accessTokenExpiresAt * 1000 - 60000 < Date.now()) || - (session.refreshToken && accessTokenRequest && accessTokenRequest.refresh) + (session.refreshToken && options && options.refresh) ) { - const client = await getClient(); - let tokenSet; - try { - tokenSet = await client.refresh(session.refreshToken, { - exchangeBody: accessTokenRequest?.authorizationParams - }); - } catch (e) { - throw new AccessTokenError( - AccessTokenErrorCode.FAILED_REFRESH_GRANT, - 'The request to refresh the access token failed.', - new IdentityProviderError(e as errors.OPError) - ); - } + const tokenSet = await client.refresh(session.refreshToken, { + exchangeBody: options?.authorizationParams + }); // Update the session. - const newSession = fromTokenSet(tokenSet, config); + const newSession = fromTokenEndpointResponse(tokenSet, config); Object.assign(session, { ...newSession, refreshToken: newSession.refreshToken || session.refreshToken, user: { ...session.user, ...newSession.user } }); - if (accessTokenRequest?.afterRefresh) { - session = await accessTokenRequest.afterRefresh(req as NextApiRequest, res as NextApiResponse, session); + if (options?.afterRefresh) { + if (req) { + session = await (options.afterRefresh as AfterRefreshPageRoute)( + req, + res as NextApiResponse | ServerResponse, + session + ); + } else { + session = await (options.afterRefresh as AfterRefreshAppRoute)(session); + } } - await sessionCache.set(req, res, session); + await set({ sessionCache, req, res, session, iat }); // Return the new access token. return { diff --git a/src/session/get-session.ts b/src/session/get-session.ts index b813879ba..05e98d7ea 100644 --- a/src/session/get-session.ts +++ b/src/session/get-session.ts @@ -1,7 +1,7 @@ import { IncomingMessage, ServerResponse } from 'http'; import { NextApiRequest, NextApiResponse } from 'next'; -import { SessionCache, Session } from '../session'; -import { assertReqRes } from '../utils/assert'; +import { NextRequest, NextResponse } from 'next/server'; +import { SessionCache, Session, get } from '../session'; /** * Get the user's session from the request. @@ -9,16 +9,15 @@ import { assertReqRes } from '../utils/assert'; * @category Server */ export type GetSession = ( - req: IncomingMessage | NextApiRequest, - res: ServerResponse | NextApiResponse + ...args: [IncomingMessage, ServerResponse] | [NextApiRequest, NextApiResponse] | [NextRequest, NextResponse] | [] ) => Promise; /** * @ignore */ export default function sessionFactory(sessionCache: SessionCache): GetSession { - return (req, res) => { - assertReqRes(req, res); - return sessionCache.get(req, res); + return async (req?, res?) => { + const [session] = await get({ req, res, sessionCache }); + return session; }; } diff --git a/src/session/index.ts b/src/session/index.ts index 85dc9031c..694e25462 100644 --- a/src/session/index.ts +++ b/src/session/index.ts @@ -1,11 +1,14 @@ -export { default as Session, Claims, fromJson, fromTokenSet } from './session'; +export { default as Session, Claims, fromJson, fromTokenEndpointResponse } from './session'; export { default as sessionFactory, GetSession } from './get-session'; export { default as accessTokenFactory, GetAccessToken, AccessTokenRequest, - GetAccessTokenResult + GetAccessTokenResult, + AfterRefresh, + AfterRefreshPageRoute, + AfterRefreshAppRoute } from './get-access-token'; -export { default as SessionCache } from './cache'; +export { default as SessionCache, get, set } from './cache'; export { default as touchSessionFactory, TouchSession } from './touch-session'; export { default as updateSessionFactory, UpdateSession } from './update-session'; diff --git a/src/session/session.ts b/src/session/session.ts index c5d98c584..057334952 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -1,4 +1,5 @@ -import type { TokenSet } from 'openid-client'; +import * as jose from 'jose'; +import type { TokenEndpointResponse } from '../auth0-session/client/abstract-client'; import { Config } from '../auth0-session'; import { NextConfig } from '../config'; @@ -60,14 +61,17 @@ export default class Session { /** * @ignore */ -export function fromTokenSet(tokenSet: TokenSet, config: Config | NextConfig): Session { +export function fromTokenEndpointResponse( + tokenEndpointResponse: TokenEndpointResponse, + config: Config | NextConfig +): Session { // Get the claims without any OIDC-specific claim. - const claims = tokenSet.claims(); + const claims = jose.decodeJwt(tokenEndpointResponse.id_token as string); config.identityClaimFilter.forEach((claim) => { delete claims[claim]; }); - const { id_token, access_token, scope, expires_at, refresh_token, ...remainder } = tokenSet; + const { id_token, access_token, scope, expires_in, expires_at, refresh_token, ...remainder } = tokenEndpointResponse; const storeIDToken = config.session.storeIDToken; return Object.assign( @@ -75,7 +79,7 @@ export function fromTokenSet(tokenSet: TokenSet, config: Config | NextConfig): S { accessToken: access_token, accessTokenScope: scope, - accessTokenExpiresAt: expires_at, + accessTokenExpiresAt: Math.floor(Date.now() / 1000) + Number(expires_in), refreshToken: refresh_token, ...(storeIDToken && { idToken: id_token }) }, diff --git a/src/session/touch-session.ts b/src/session/touch-session.ts index 42a90e649..06192b05c 100644 --- a/src/session/touch-session.ts +++ b/src/session/touch-session.ts @@ -1,7 +1,7 @@ import { IncomingMessage, ServerResponse } from 'http'; import { NextApiRequest, NextApiResponse } from 'next'; -import { SessionCache } from '../session'; -import { assertReqRes } from '../utils/assert'; +import { NextRequest, NextResponse } from 'next/server'; +import { get, set, SessionCache } from '../session'; /** * Touch the session object. If rolling sessions are enabled and autoSave is disabled, you will need @@ -21,20 +21,18 @@ import { assertReqRes } from '../utils/assert'; * @category Server */ export type TouchSession = ( - req: IncomingMessage | NextApiRequest, - res: ServerResponse | NextApiResponse + ...args: [IncomingMessage, ServerResponse] | [NextApiRequest, NextApiResponse] | [NextRequest, NextResponse] | [] ) => Promise; /** * @ignore */ export default function touchSessionFactory(sessionCache: SessionCache): TouchSession { - return async (req, res) => { - assertReqRes(req, res); - const session = await sessionCache.get(req, res); + return async (req?, res?) => { + const [session, iat] = await get({ sessionCache, req, res }); if (!session) { return; } - await sessionCache.save(req, res); + await set({ req, res, session, sessionCache, iat }); }; } diff --git a/src/session/update-session.ts b/src/session/update-session.ts index 3158171f2..99d19b0ad 100644 --- a/src/session/update-session.ts +++ b/src/session/update-session.ts @@ -1,7 +1,7 @@ import { IncomingMessage, ServerResponse } from 'http'; import { NextApiRequest, NextApiResponse } from 'next'; -import { Session, SessionCache } from '../session'; -import { assertReqRes } from '../utils/assert'; +import { NextRequest, NextResponse } from 'next/server'; +import { get, set, Session, SessionCache } from '../session'; /** * Update the session object. The provided `session` object will replace the existing session. @@ -25,21 +25,25 @@ import { assertReqRes } from '../utils/assert'; * @category Server */ export type UpdateSession = ( - req: IncomingMessage | NextApiRequest, - res: ServerResponse | NextApiResponse, - user: Session + ...args: + | [IncomingMessage, ServerResponse, Session] + | [NextApiRequest, NextApiResponse, Session] + | [NextRequest, NextResponse, Session] + | [Session] ) => Promise; /** * @ignore */ export default function updateSessionFactory(sessionCache: SessionCache): UpdateSession { - return async (req, res, newSession) => { - assertReqRes(req, res); - const session = await sessionCache.get(req, res); - if (!session || !newSession || !newSession.user) { + return async (reqOrSession, res?, newSession?) => { + const session = (res ? newSession : reqOrSession) as Session | undefined; + const req = (res ? reqOrSession : undefined) as IncomingMessage | NextApiRequest | NextRequest | undefined; + + const [prevSession, iat] = await get({ sessionCache, req, res }); + if (!prevSession || !session || !session.user) { return; } - await sessionCache.set(req, res, newSession); + await set({ req, res, session, sessionCache, iat }); }; } diff --git a/src/shared.ts b/src/shared.ts new file mode 100644 index 000000000..2bf7aa76a --- /dev/null +++ b/src/shared.ts @@ -0,0 +1,160 @@ +import { SessionStore as GenericSessionStore, SessionPayload } from './auth0-session'; +import { HandleAuth, HandleLogin, HandleProfile, HandleLogout, HandleCallback } from './handlers'; +import { SessionCache, GetSession, GetAccessToken, Session, TouchSession, UpdateSession } from './session/'; +import { WithApiAuthRequired, WithPageAuthRequired } from './helpers'; +import { ConfigParameters } from './config'; +import { WithMiddlewareAuthRequired } from './helpers/with-middleware-auth-required'; +import version from './version'; + +export const telemetry = { name: 'nextjs-auth0', version }; + +/** + * The SDK server instance. + * + * This is created for you when you use the named exports, or you can create your own using {@link InitAuth0}. + * + * See {@link ConfigParameters} for more info. + * + * @category Server + */ +export interface Auth0Server { + /** + * Session getter. + */ + getSession: GetSession; + + /** + * Update the expiry of a rolling session when autoSave is disabled. + */ + touchSession: TouchSession; + + /** + * Append properties to the user. + */ + updateSession: UpdateSession; + + /** + * Access token getter. + */ + getAccessToken: GetAccessToken; + + /** + * Login handler which will redirect the user to Auth0. + */ + handleLogin: HandleLogin; + + /** + * Callback handler which will complete the transaction and create a local session. + */ + handleCallback: HandleCallback; + + /** + * Logout handler which will clear the local session and the Auth0 session. + */ + handleLogout: HandleLogout; + + /** + * Profile handler which return profile information about the user. + */ + handleProfile: HandleProfile; + + /** + * Helper that adds auth to an API route. + */ + withApiAuthRequired: WithApiAuthRequired; + + /** + * Helper that adds auth to a Page route. + */ + withPageAuthRequired: WithPageAuthRequired; + + /** + * Create the main handlers for your api routes. + */ + handleAuth: HandleAuth; + + /** + * Add auth to your middleware functions. + */ + withMiddlewareAuthRequired: WithMiddlewareAuthRequired; +} + +export { + AuthError, + AccessTokenErrorCode, + AccessTokenError, + HandlerError, + CallbackHandlerError, + LoginHandlerError, + LogoutHandlerError, + ProfileHandlerError +} from './utils/errors'; + +export { + Handlers, + LoginOptions, + LogoutOptions, + GetLoginState, + GetLoginStatePageRoute, + GetLoginStateAppRoute, + ProfileOptions, + CallbackOptions, + AfterCallback, + AfterCallbackPageRoute, + AfterCallbackAppRoute, + AfterRefetch, + AfterRefetchPageRoute, + AfterRefetchAppRoute, + AppRouterOnError, + PageRouterOnError +} from './handlers'; + +export { + AppRouterPageRouteOpts, + AppRouterPageRoute, + WithPageAuthRequiredPageRouter, + WithPageAuthRequiredAppRouter, + GetServerSidePropsResultWithSession, + WithPageAuthRequiredPageRouterOptions, + WithPageAuthRequiredAppRouterOptions, + PageRoute, + AppRouteHandlerFn, + WithApiAuthRequiredAppRoute, + WithApiAuthRequiredPageRoute +} from './helpers'; + +export { + AccessTokenRequest, + GetAccessTokenResult, + Claims, + AfterRefresh, + AfterRefreshPageRoute, + AfterRefreshAppRoute +} from './session'; + +export { + MissingStateCookieError, + MissingStateParamError, + IdentityProviderError, + ApplicationError +} from './auth0-session'; + +export { + ConfigParameters, + HandleAuth, + HandleLogin, + HandleProfile, + HandleLogout, + HandleCallback, + WithApiAuthRequired, + WithPageAuthRequired, + SessionCache, + GetSession, + TouchSession, + UpdateSession, + GetAccessToken, + Session +}; + +export type SessionStore = GenericSessionStore; +export type SessionStorePayload = SessionPayload; diff --git a/src/utils/errors.ts b/src/utils/errors.ts index fda7b003b..fd2bc53ab 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -1,5 +1,3 @@ -import { HttpError } from 'http-errors'; - /** * @ignore */ @@ -48,7 +46,7 @@ export abstract class AuthError extends Error { /** * The underlying error, if any. * - * **IMPORTANT** When this error is from the Identity Provider ({@Link IdentityProviderError}) it can contain user + * **IMPORTANT** When this error is from the Identity Provider ({@link IdentityProviderError}) it can contain user * input and is only escaped using basic escaping for putting untrusted data directly into the HTML body. * * You should **not** render this error without using a templating engine that will properly escape it for other @@ -107,6 +105,14 @@ export class AccessTokenError extends AuthError { } } +/** + * @ignore + */ +interface HttpError extends Error { + status: number; + statusCode: number; +} + /** * @ignore */ @@ -129,7 +135,7 @@ type HandlerErrorOptions = { * without using a templating engine that will properly escape it for other HTML contexts first. * * @see the {@link AuthError.cause | cause property} contains the underlying error. - * **IMPORTANT** When this error is from the Identity Provider ({@Link IdentityProviderError}) it can contain user + * **IMPORTANT** When this error is from the Identity Provider ({@link IdentityProviderError}) it can contain user * input and is only escaped using basic escaping for putting untrusted data directly into the HTML body. * You should **not** render this error without using a templating engine that will properly escape it for other * HTML contexts first. @@ -158,7 +164,7 @@ export class HandlerError extends AuthError { * without using a templating engine that will properly escape it for other HTML contexts first. * * @see the {@link AuthError.cause | cause property} contains the underlying error. - * **IMPORTANT** When this error is from the Identity Provider ({@Link IdentityProviderError}) it can contain user + * **IMPORTANT** When this error is from the Identity Provider ({@link IdentityProviderError}) it can contain user * input and is only escaped using basic escaping for putting untrusted data directly into the HTML body. * You should **not** render this error without using a templating engine that will properly escape it for other * HTML contexts first. diff --git a/src/utils/middleware-cookies.ts b/src/utils/middleware-cookies.ts deleted file mode 100644 index 5390f587d..000000000 --- a/src/utils/middleware-cookies.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Cookies } from '../auth0-session/utils/cookies'; -import { NextRequest, NextResponse } from 'next/server'; - -export default class MiddlewareCookies extends Cookies { - protected getSetCookieHeader(res: NextResponse): string[] { - const value = res.headers.get('set-cookie'); - return splitCookiesString(value as string); - } - - protected setSetCookieHeader(res: NextResponse, cookies: string[]): void { - res.headers.delete('set-cookie'); - for (const cookie of cookies) { - res.headers.append('set-cookie', cookie); - } - } - - getAll(req: NextRequest): Record { - const { cookies } = req; - if (typeof cookies.getAll === 'function') { - return req.cookies.getAll().reduce((memo, { name, value }) => ({ ...memo, [name]: value }), {}); - } - // Edge cookies before Next 13.0.1 have no `getAll` and extend `Map`. - const legacyCookies = cookies as unknown as Map; - return Array.from(legacyCookies.keys()).reduce((memo: { [key: string]: string }, key) => { - memo[key] = legacyCookies.get(key) as string; - return memo; - }, {}); - } -} - -/* eslint-disable max-len */ -/** - * Handle cookies with commas, eg `foo=; Expires=Thu, 01 Jan 1970 00:00:00 GMT` - * @source https://github.com/vercel/edge-runtime/blob/90160abc42e6139c41494c5d2e98f09e9a5fa514/packages/cookies/src/response-cookies.ts#L128 - */ -export function splitCookiesString(cookiesString: string) { - if (!cookiesString) return []; - const cookiesStrings = []; - let pos = 0; - let start; - let ch; - let lastComma; - let nextStart; - let cookiesSeparatorFound; - - function skipWhitespace() { - while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) { - pos += 1; - } - return pos < cookiesString.length; - } - - function notSpecialChar() { - ch = cookiesString.charAt(pos); - - return ch !== '=' && ch !== ';' && ch !== ','; - } - - while (pos < cookiesString.length) { - start = pos; - cookiesSeparatorFound = false; - - while (skipWhitespace()) { - ch = cookiesString.charAt(pos); - if (ch === ',') { - // ',' is a cookie separator if we have later first '=', not ';' or ',' - lastComma = pos; - pos += 1; - - skipWhitespace(); - nextStart = pos; - - while (pos < cookiesString.length && notSpecialChar()) { - pos += 1; - } - - // currently special character - if (pos < cookiesString.length && cookiesString.charAt(pos) === '=') { - // we found cookies separator - cookiesSeparatorFound = true; - // pos is inside the next cookie, so back up and return it. - pos = nextStart; - cookiesStrings.push(cookiesString.substring(start, lastComma)); - start = pos; - /* c8 ignore next 5 */ - } else { - // in param ',' or param separator ';', - // we continue from that comma - pos = lastComma + 1; - } - } else { - pos += 1; - } - } - - if (!cookiesSeparatorFound || pos >= cookiesString.length) { - cookiesStrings.push(cookiesString.substring(start, cookiesString.length)); - } - } - - return cookiesStrings; -} diff --git a/src/utils/req-helpers.ts b/src/utils/req-helpers.ts new file mode 100644 index 000000000..c5b55c8dc --- /dev/null +++ b/src/utils/req-helpers.ts @@ -0,0 +1,13 @@ +import type { IncomingMessage } from 'http'; +import { NextApiRequest } from 'next'; +import { NextRequest } from 'next/server'; + +type Req = IncomingMessage | NextApiRequest | NextRequest | Request | Record; + +export const isRequest = (req: Req): boolean => { + return req instanceof Request || req.headers instanceof Headers || typeof (req as Request).bodyUsed === 'boolean'; +}; + +export const isNextApiRequest = (req: Req) => { + return !isRequest(req) && 'query' in req; +}; diff --git a/src/version.ts b/src/version.ts index 0910a6c54..a9f333347 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export default '2.7.0'; +export default '3.0.0-beta.3'; diff --git a/tests/auth0-session/client/edge-client.test.ts b/tests/auth0-session/client/edge-client.test.ts new file mode 100644 index 000000000..48b7805c9 --- /dev/null +++ b/tests/auth0-session/client/edge-client.test.ts @@ -0,0 +1,391 @@ +/** + * @jest-environment @edge-runtime/jest-environment + */ +import nock from 'nock'; +import * as jose from 'jose'; +import { getConfig, ConfigParameters } from '../../../src/auth0-session'; +import { jwks, makeIdToken } from '../fixtures/cert'; +import pkg from '../../../package.json'; +import wellKnown from '../fixtures/well-known.json'; +import version from '../../../src/version'; +import { EdgeClient } from '../../../src/auth0-session/client/edge-client'; +import { mockFetch } from '../../fixtures/app-router-helpers'; +import { Auth0Request } from '../../../src/auth0-session/http'; +import { readFileSync } from 'fs'; +import { join } from 'path'; +import { UserInfoError } from '../../../src/auth0-session/utils/errors'; + +class TestReq extends Auth0Request { + constructor() { + super(null); + } + getBody() { + return { state: 'foo', code: 'bar' }; + } + getCookies() { + return {}; + } + getMethod() { + return 'POST'; + } + getUrl() { + return ''; + } +} + +const defaultConfig: ConfigParameters = { + secret: '__test_session_secret__', + clientID: '__test_client_id__', + clientSecret: '__test_client_secret__', + issuerBaseURL: 'https://op.example.com', + baseURL: 'https://example.org', + routes: { + callback: '/callback' + }, + authorizationParams: { + response_type: 'code', + scope: 'openid profile read:customer', + audience: 'https://api.acme.com' + } +}; + +const getClient = async (params: ConfigParameters = {}): Promise => { + return new EdgeClient(getConfig({ ...defaultConfig, ...params }), { + name: 'nextjs-auth0', + version + }); +}; + +describe('edge client', function () { + let headersSpy = jest.fn(); + + beforeEach(() => { + mockFetch(); + if (!nock.isActive()) { + nock.activate(); + } + nock('https://op.example.com').get('/.well-known/openid-configuration').reply(200, wellKnown); + nock('https://op.example.com').get('/.well-known/jwks.json').reply(200, jwks); + nock('https://op.example.com') + .get('/userinfo') + .reply(200, function () { + headersSpy(this.req.headers); + return { sub: 'foo' }; + }); + nock('https://op.example.com') + .post('/oauth/token', (body) => { + return !body.error; + }) + .reply(200, async function () { + return { + access_token: '__test_access_token__', + refresh_token: '__test_refresh_token__', + id_token: await makeIdToken({}), + token_type: 'Bearer', + expires_in: 86400 + }; + }); + }); + + afterEach(() => { + nock.restore(); + nock.cleanAll(); + }); + + it('should send the correct default headers', async function () { + const client = await getClient(); + const userinfo = await client.userinfo('__test_token__'); + const headers = headersSpy.mock.calls[0][0]; + const headerProps = Object.getOwnPropertyNames(headers); + + expect(headerProps).toContain('auth0-client'); + + const decodedTelemetry = JSON.parse(jose.base64url.decode(headers['auth0-client'][0]).toString()); + + expect(decodedTelemetry.name).toEqual('nextjs-auth0'); + expect(decodedTelemetry.version).toEqual(pkg.version); + expect(decodedTelemetry.env.edge).toEqual(true); + + expect(headerProps).toContain('user-agent'); + expect(headers['user-agent'][0]).toEqual(`nextjs-auth0/${pkg.version}`); + expect(userinfo.sub).toBe('foo'); + }); + + it('should disable telemetry', async function () { + const client = await getClient({ enableTelemetry: false }); + const userinfo = await client.userinfo('__test_token__'); + const headers = headersSpy.mock.calls[0][0]; + const headerProps = Object.getOwnPropertyNames(headers); + + expect(headerProps).not.toContain('auth0-client'); + expect(userinfo.sub).toBe('foo'); + }); + + it('should not strip new headers', async function () { + const client = await getClient(); + const userinfo = await client.userinfo('__test_token__'); + const headers = headersSpy.mock.calls[0][0]; + const headerProps = Object.getOwnPropertyNames(headers); + + expect(headerProps).toContain('authorization'); + expect(userinfo.sub).toBe('foo'); + }); + + it('should prefer user configuration regardless of idP discovery', async function () { + nock('https://op2.example.com') + .get('/.well-known/openid-configuration') + .reply( + 200, + Object.assign({}, wellKnown, { + issuer: 'https://op2.example.com', + id_token_signing_alg_values_supported: ['none'] + }) + ); + + const client = await getClient({ + issuerBaseURL: 'https://op2.example.com', + idTokenSigningAlg: 'RS256' + }); + // @ts-ignore + expect((await client.getClient())[1].id_token_signed_response_alg).toEqual('RS256'); + }); + + it('should use discovered logout endpoint by default', async function () { + const client = await getClient({ ...defaultConfig, idpLogout: true }); + await expect(client.endSessionUrl({} as any)).resolves.toEqual( + 'https://op.example.com/session/end?client_id=__test_client_id__' + ); + }); + + it('should use auth0 logout endpoint if configured', async function () { + const client = await getClient({ ...defaultConfig, idpLogout: true, auth0Logout: true }); + await expect(client.endSessionUrl({} as any)).resolves.toEqual( + 'https://op.example.com/v2/logout?client_id=__test_client_id__' + ); + }); + + it('should use auth0 logout endpoint if domain is auth0.com', async function () { + nock('https://foo.auth0.com') + .get('/.well-known/openid-configuration') + .reply(200, { ...wellKnown, issuer: 'https://foo.auth0.com/' }); + const client = await getClient({ ...defaultConfig, idpLogout: true, issuerBaseURL: 'https://foo.auth0.com' }); + await expect(client.endSessionUrl({ post_logout_redirect_uri: '' })).resolves.toEqual( + 'https://foo.auth0.com/v2/logout?client_id=__test_client_id__' + ); + }); + + it('should use auth0 logout endpoint if domain is auth0.com and configured', async function () { + nock('https://foo.auth0.com') + .get('/.well-known/openid-configuration') + .reply(200, { ...wellKnown, issuer: 'https://foo.auth0.com/' }); + const client = await getClient({ + ...defaultConfig, + issuerBaseURL: 'https://foo.auth0.com', + idpLogout: true, + auth0Logout: true + }); + await expect(client.endSessionUrl({ post_logout_redirect_uri: '' })).resolves.toEqual( + 'https://foo.auth0.com/v2/logout?client_id=__test_client_id__' + ); + }); + + it('should use discovered logout endpoint if domain is auth0.com but configured with auth0logout false', async function () { + nock('https://foo.auth0.com') + .get('/.well-known/openid-configuration') + .reply(200, { + ...wellKnown, + issuer: 'https://foo.auth0.com/', + end_session_endpoint: 'https://foo.auth0.com/oidc/logout' + }); + const client = await getClient({ + ...defaultConfig, + issuerBaseURL: 'https://foo.auth0.com', + idpLogout: true, + auth0Logout: false + }); + await expect(client.endSessionUrl({} as any)).resolves.toEqual( + 'https://foo.auth0.com/oidc/logout?client_id=__test_client_id__' + ); + }); + + it('should create client with no end_session_endpoint', async function () { + nock('https://op2.example.com') + .get('/.well-known/openid-configuration') + .reply(200, { + ...wellKnown, + issuer: 'https://op2.example.com', + end_session_endpoint: undefined + }); + const client = await getClient({ ...defaultConfig, issuerBaseURL: 'https://op2.example.com' }); + await expect(client.endSessionUrl({ post_logout_redirect_uri: '' })).rejects.toThrowError(); + }); + + it('should create custom logout for auth0', async function () { + nock('https://test.eu.auth0.com') + .get('/.well-known/openid-configuration') + .reply(200, { ...wellKnown, issuer: 'https://test.eu.auth0.com/', end_session_endpoint: undefined }); + nock('https://test.eu.auth0.com').get('/.well-known/jwks.json').reply(200, jwks); + + const client = await getClient({ + issuerBaseURL: 'https://test.eu.auth0.com', + idpLogout: true + }); + await expect(client.endSessionUrl({ post_logout_redirect_uri: 'foo' })).resolves.toEqual( + 'https://test.eu.auth0.com/v2/logout?returnTo=foo&client_id=__test_client_id__' + ); + }); + + it('should remove null params from oidc logout endpoint', async function () { + const client = await getClient({ ...defaultConfig, idpLogout: true }); + await expect(client.endSessionUrl({ foo: null } as any)).resolves.toEqual( + 'https://op.example.com/session/end?client_id=__test_client_id__' + ); + }); + + it('should remove null params from auth0 logout endpoint', async function () { + const client = await getClient({ ...defaultConfig, idpLogout: true, auth0Logout: true }); + await expect(client.endSessionUrl({ foo: null } as any)).resolves.toEqual( + 'https://op.example.com/v2/logout?client_id=__test_client_id__' + ); + }); + + it('should handle limited openid-configuration', async function () { + nock('https://op2.example.com') + .get('/.well-known/openid-configuration') + .reply( + 200, + Object.assign({}, wellKnown, { + issuer: 'https://op2.example.com', + id_token_signing_alg_values_supported: undefined, + response_types_supported: undefined, + response_modes_supported: 'foo', + end_session_endpoint: undefined + }) + ); + + await expect( + ( + await getClient({ + issuerBaseURL: 'https://op2.example.com', + idpLogout: true + }) + ) + // @ts-ignore + .getClient() + ).resolves.not.toThrow(); + }); + + it('should throw DiscoveryError when discovery fails', async () => { + nock.cleanAll(); + nock('https://op.example.com').get('/.well-known/oauth-authorization-server').reply(500); + nock('https://op.example.com').get('/.well-known/openid-configuration').reply(500); + await expect((await getClient()).userinfo('token')).rejects.toThrow( + /Discovery requests failing for https:\/\/op.example.com/ + ); + }); + + it('should throw UserInfoError when userinfo fails', async () => { + nock.cleanAll(); + nock('https://op.example.com').get('/.well-known/openid-configuration').reply(200, wellKnown); + nock('https://op.example.com').get('/userinfo').reply(500, {}); + const client = await getClient(); + await expect(client.userinfo('__test_token__')).rejects.toThrow(UserInfoError); + }); + + it('should only support code flow', async () => { + await expect(getClient({ authorizationParams: { response_type: 'id_token' } })).rejects.toThrow( + 'This SDK only supports `response_type=code` when used in an Edge runtime.' + ); + }); + + it('should strip empty parameters from login url', async () => { + const client = await getClient(); + await expect(client.authorizationUrl({ foo: null })).resolves.toBe( + 'https://op.example.com/authorize?client_id=__test_client_id__' + ); + }); + + it('should get callback params from req body', async () => { + const client = await getClient(); + await expect(client.callbackParams(new TestReq(), 'foo')).resolves.toBeInstanceOf(URLSearchParams); + }); + + it('should support private key jwt', async () => { + const privateKey = readFileSync(join(__dirname, '..', 'fixtures', 'private-key.pem'), 'utf-8'); + + function pemToArrayBuffer(pem: string) { + const b64 = pem + .replace('\n', '') + .replace('-----BEGIN PRIVATE KEY-----', '') + .replace('-----END PRIVATE KEY-----', ''); + + const byteString = atob(b64); + const byteArray = new Uint8Array(byteString.length); + for (let i = 0; i < byteString.length; i++) { + byteArray[i] = byteString.charCodeAt(i); + } + return byteArray; + } + + const key = await crypto.subtle.importKey( + 'pkcs8', + pemToArrayBuffer(privateKey), + { + name: 'RSASSA-PKCS1-v1_5', + hash: { name: 'SHA-256' } // or SHA-512 + }, + true, + ['sign'] + ); + + const client = await getClient({ clientAssertionSigningKey: key as any }); + const params = await client.callbackParams(new TestReq(), 'foo'); + const res = await client.callback( + 'https://example.org/callback', + params, + { response_type: 'code', code_verifier: 'bar', nonce: '__test_nonce__' }, + {} + ); + expect(res.access_token).toBe('__test_access_token__'); + }); + + it('should handle oauth errors from code exchange', async () => { + nock('https://op.example.com') + .post('/oauth/token', (body) => { + return !!body.error; + }) + .reply(400, async function () { + return { + error: 'foo', + error_description: 'bar' + }; + }); + const client = await getClient(); + const params = await client.callbackParams(new TestReq(), 'foo'); + await expect( + client.callback( + 'https://example.org/callback', + params, + { response_type: 'code', code_verifier: 'bar', nonce: '__test_nonce__' }, + { exchangeBody: { error: '1' } } + ) + ).rejects.toThrowError(expect.objectContaining({ error: 'foo', errorDescription: 'bar' })); + }); + + it('should handle oauth errors from token refresh', async () => { + nock('https://op.example.com') + .post('/oauth/token', (body) => { + return !!body.error; + }) + .reply(400, async function () { + return { + error: 'foo', + error_description: 'bar' + }; + }); + const client = await getClient(); + await expect(client.refresh('foo', { exchangeBody: { error: '1' } })).rejects.toThrow( + 'The request to refresh the access token failed. CAUSE: bar' + ); + }); +}); diff --git a/tests/auth0-session/client.test.ts b/tests/auth0-session/client/node-client.test.ts similarity index 70% rename from tests/auth0-session/client.test.ts rename to tests/auth0-session/client/node-client.test.ts index cf925fd4d..1aa983d51 100644 --- a/tests/auth0-session/client.test.ts +++ b/tests/auth0-session/client/node-client.test.ts @@ -1,10 +1,11 @@ import nock from 'nock'; -import { Client } from 'openid-client'; -import { getConfig, ConfigParameters } from '../../src/auth0-session'; -import { jwks } from './fixtures/cert'; -import pkg from '../../package.json'; -import wellKnown from './fixtures/well-known.json'; -import version from '../../src/version'; +import { getConfig, ConfigParameters } from '../../../src/auth0-session'; +import { jwks } from '../fixtures/cert'; +import pkg from '../../../package.json'; +import wellKnown from '../fixtures/well-known.json'; +import version from '../../../src/version'; +import { NodeClient } from '../../../src/auth0-session/client/node-client'; +import { UserInfoError } from '../../../src/auth0-session/utils/errors'; const defaultConfig = { secret: '__test_session_secret__', @@ -17,15 +18,14 @@ const defaultConfig = { } }; -const getClient = async (params: ConfigParameters = {}): Promise => { - const { default: clientFactory } = await import('../../src/auth0-session/client'); - return clientFactory(getConfig({ ...defaultConfig, ...params }), { +const getClient = async (params: ConfigParameters = {}): Promise => { + return new NodeClient(getConfig({ ...defaultConfig, ...params }), { name: 'nextjs-auth0', version - })(); + }); }; -describe('clientFactory', function () { +describe('node client', function () { beforeEach(() => { if (!nock.isActive()) { nock.activate(); @@ -33,7 +33,7 @@ describe('clientFactory', function () { nock('https://op.example.com').get('/.well-known/openid-configuration').reply(200, wellKnown); nock('https://op.example.com').get('/.well-known/jwks.json').reply(200, jwks); nock('https://op.example.com') - .post('/introspection') + .get('/userinfo') .reply(200, function () { return this.req.headers; }); @@ -44,15 +44,9 @@ describe('clientFactory', function () { nock.cleanAll(); }); - it('should save the passed values', async function () { - const client = await getClient(); - expect(client.client_id).toEqual('__test_client_id__'); - expect(client.client_secret).toEqual('__test_client_secret__'); - }); - it('should send the correct default headers', async function () { const client = await getClient(); - const headers = await client.introspect('__test_token__', '__test_hint__'); + const headers = await client.userinfo('__test_token__'); const headerProps = Object.getOwnPropertyNames(headers); expect(headerProps).toContain('auth0-client'); @@ -69,7 +63,7 @@ describe('clientFactory', function () { it('should disable telemetry', async function () { const client = await getClient({ enableTelemetry: false }); - const headers = await client.introspect('__test_token__', '__test_hint__'); + const headers = await client.userinfo('__test_token__'); const headerProps = Object.getOwnPropertyNames(headers); expect(headerProps).not.toContain('auth0-client'); @@ -77,13 +71,8 @@ describe('clientFactory', function () { it('should not strip new headers', async function () { const client = await getClient(); - const response = await client.requestResource('https://op.example.com/introspection', 'token', { - method: 'POST', - headers: { - Authorization: 'Bearer foo' - } - }); - const headerProps = Object.getOwnPropertyNames(JSON.parse((response.body as Buffer).toString())); + const response = await client.userinfo('__test_token__'); + const headerProps = Object.getOwnPropertyNames(response); expect(headerProps).toContain('authorization'); }); @@ -102,17 +91,22 @@ describe('clientFactory', function () { issuerBaseURL: 'https://op2.example.com', idTokenSigningAlg: 'RS256' }); - expect(client.id_token_signed_response_alg).toEqual('RS256'); + // @ts-ignore + expect((await client.getClient()).id_token_signed_response_alg).toEqual('RS256'); }); it('should use discovered logout endpoint by default', async function () { const client = await getClient({ ...defaultConfig, idpLogout: true }); - expect(client.endSessionUrl({})).toEqual('https://op.example.com/session/end?client_id=__test_client_id__'); + await expect(client.endSessionUrl({})).resolves.toEqual( + 'https://op.example.com/session/end?client_id=__test_client_id__' + ); }); it('should use auth0 logout endpoint if configured', async function () { const client = await getClient({ ...defaultConfig, idpLogout: true, auth0Logout: true }); - expect(client.endSessionUrl({})).toEqual('https://op.example.com/v2/logout?client_id=__test_client_id__'); + await expect(client.endSessionUrl({})).resolves.toEqual( + 'https://op.example.com/v2/logout?client_id=__test_client_id__' + ); }); it('should use auth0 logout endpoint if domain is auth0.com', async function () { @@ -120,7 +114,9 @@ describe('clientFactory', function () { .get('/.well-known/openid-configuration') .reply(200, { ...wellKnown, issuer: 'https://foo.auth0.com/' }); const client = await getClient({ ...defaultConfig, idpLogout: true, issuerBaseURL: 'https://foo.auth0.com' }); - expect(client.endSessionUrl({})).toEqual('https://foo.auth0.com/v2/logout?client_id=__test_client_id__'); + await expect(client.endSessionUrl({})).resolves.toEqual( + 'https://foo.auth0.com/v2/logout?client_id=__test_client_id__' + ); }); it('should use auth0 logout endpoint if domain is auth0.com and configured', async function () { @@ -133,7 +129,9 @@ describe('clientFactory', function () { idpLogout: true, auth0Logout: true }); - expect(client.endSessionUrl({})).toEqual('https://foo.auth0.com/v2/logout?client_id=__test_client_id__'); + await expect(client.endSessionUrl({})).resolves.toEqual( + 'https://foo.auth0.com/v2/logout?client_id=__test_client_id__' + ); }); it('should use discovered logout endpoint if domain is auth0.com but configured with auth0logout false', async function () { @@ -150,7 +148,9 @@ describe('clientFactory', function () { idpLogout: true, auth0Logout: false }); - expect(client.endSessionUrl({})).toEqual('https://foo.auth0.com/oidc/logout?client_id=__test_client_id__'); + await expect(client.endSessionUrl({})).resolves.toEqual( + 'https://foo.auth0.com/oidc/logout?client_id=__test_client_id__' + ); }); it('should create client with no end_session_endpoint', async function () { @@ -162,7 +162,7 @@ describe('clientFactory', function () { end_session_endpoint: undefined }); const client = await getClient({ ...defaultConfig, issuerBaseURL: 'https://op2.example.com' }); - expect(() => client.endSessionUrl({})).toThrowError(); + await expect(client.endSessionUrl({})).rejects.toThrowError(); }); it('should create custom logout for auth0', async function () { @@ -175,8 +175,8 @@ describe('clientFactory', function () { issuerBaseURL: 'https://test.eu.auth0.com', idpLogout: true }); - expect(client.endSessionUrl({ post_logout_redirect_uri: 'foo' })).toEqual( - 'https://test.eu.auth0.com/v2/logout?returnTo=foo&client_id=__test_client_id__' + await expect(client.endSessionUrl({ post_logout_redirect_uri: 'foo' })).resolves.toEqual( + 'https://test.eu.auth0.com/v2/logout?client_id=__test_client_id__&returnTo=foo' ); }); @@ -194,10 +194,14 @@ describe('clientFactory', function () { ); await expect( - getClient({ - issuerBaseURL: 'https://op2.example.com', - idpLogout: true - }) + ( + await getClient({ + issuerBaseURL: 'https://op2.example.com', + idpLogout: true + }) + ) + // @ts-ignore + .getClient() ).resolves.not.toThrow(); }); @@ -205,8 +209,15 @@ describe('clientFactory', function () { nock.cleanAll(); nock('https://op.example.com').get('/.well-known/oauth-authorization-server').reply(500); nock('https://op.example.com').get('/.well-known/openid-configuration').reply(500); - await expect(getClient()).rejects.toThrow( + await expect((await getClient()).userinfo('token')).rejects.toThrow( 'Discovery requests failing for https://op.example.com, expected 200 OK, got: 500 Internal Server Error' ); }); + + it('should throw UserInfoError when userinfo fails', async () => { + nock.cleanAll(); + nock('https://op.example.com').get('/.well-known/openid-configuration').reply(200, wellKnown); + nock('https://op.example.com').get('/userinfo').reply(500, {}); + await expect((await getClient()).userinfo('token')).rejects.toThrow(UserInfoError); + }); }); diff --git a/tests/auth0-session/fixtures/helpers.ts b/tests/auth0-session/fixtures/helpers.ts index 34b0bd21c..e448eeef8 100644 --- a/tests/auth0-session/fixtures/helpers.ts +++ b/tests/auth0-session/fixtures/helpers.ts @@ -1,8 +1,8 @@ +import { IncomingMessage, request as nodeHttpRequest } from 'http'; +import { request as nodeHttpsRequest } from 'https'; import { Cookie, CookieJar } from 'tough-cookie'; import { signing } from '../../../src/auth0-session/utils/hkdf'; import { generateCookieValue } from '../../../src/auth0-session/utils/signed-cookies'; -import { IncomingMessage, request as nodeHttpRequest } from 'http'; -import { request as nodeHttpsRequest } from 'https'; import { ConfigParameters } from '../../../src/auth0-session'; import { base64url } from 'jose'; @@ -17,11 +17,15 @@ export const defaultConfig: Omit = { } }; +export const signCookie = async (key: string, value: string) => { + const signingKey = await signing(secret); + return generateCookieValue(key, value, signingKey); +}; + export const toSignedCookieJar = async (cookies: { [key: string]: string }, url: string): Promise => { const cookieJar = new CookieJar(); - const signingKey = await signing(secret); for (const [key, value] of Object.entries(cookies)) { - cookieJar.setCookieSync(`${key}=${await generateCookieValue(key, value, signingKey)}`, url); + cookieJar.setCookieSync(`${key}=${await signCookie(key, value)}`, url); } return cookieJar; }; diff --git a/tests/auth0-session/fixtures/server.ts b/tests/auth0-session/fixtures/server.ts index c1aadd807..5277bb960 100644 --- a/tests/auth0-session/fixtures/server.ts +++ b/tests/auth0-session/fixtures/server.ts @@ -6,11 +6,9 @@ import nock from 'nock'; import { TokenSet, TokenSetParameters } from 'openid-client'; import bodyParser from 'body-parser'; import { - NodeCookies as Cookies, loginHandler, getConfig, ConfigParameters, - clientFactory, TransientStore, StatelessSession, SessionCache, @@ -27,59 +25,69 @@ import { jwks } from './cert'; import { cert, key } from './https'; import { Claims } from '../../../src/session'; import version from '../../../src/version'; +import { NodeRequest, NodeResponse } from '../../../src/auth0-session/http'; +import { NodeClient } from '../../../src/auth0-session/client/node-client'; export type SessionResponse = TokenSetParameters & { claims: Claims }; -class TestSessionCache implements SessionCache { - constructor(private cookieStore: AbstractSession) {} +interface NodeCallbackOptions extends Omit { + afterCallback?: ( + req: IncomingMessage, + res: ServerResponse, + session: any, + state?: Record + ) => Promise | any | undefined; +} + +class TestSessionCache implements SessionCache { + constructor(private cookieStore: AbstractSession) {} async create(req: IncomingMessage, res: ServerResponse, tokenSet: TokenSet): Promise { - await this.cookieStore.save(req, res, tokenSet); + await this.cookieStore.save(new NodeRequest(req), new NodeResponse(res), tokenSet); } async delete(req: IncomingMessage, res: ServerResponse): Promise { - await this.cookieStore.save(req, res, null); + await this.cookieStore.save(new NodeRequest(req), new NodeResponse(res), null); } async isAuthenticated(req: IncomingMessage): Promise { - const [session] = await this.cookieStore.read(req); + const [session] = await this.cookieStore.read(new NodeRequest(req)); return !!session?.id_token; } async getIdToken(req: IncomingMessage): Promise { - const [session] = await this.cookieStore.read(req); + const [session] = await this.cookieStore.read(new NodeRequest(req)); return session?.id_token; } - fromTokenSet(tokenSet: TokenSet): { [p: string]: any } { + fromTokenEndpointResponse(tokenSet: TokenSet): { [p: string]: any } { return tokenSet; } } type Handlers = { - handleLogin: (req: IncomingMessage, res: ServerResponse, opts?: LoginOptions) => Promise; - handleLogout: (req: IncomingMessage, res: ServerResponse, opts?: LogoutOptions) => Promise; - handleCallback: (req: IncomingMessage, res: ServerResponse, opts?: CallbackOptions) => Promise; + handleLogin: (req: NodeRequest, res: NodeResponse, opts?: LoginOptions) => Promise; + handleLogout: (req: NodeRequest, res: NodeResponse, opts?: LogoutOptions) => Promise; + handleCallback: (req: NodeRequest, res: NodeResponse, opts?: CallbackOptions) => Promise; handleSession: (req: IncomingMessage, res: ServerResponse) => Promise; }; const createHandlers = (params: ConfigParameters): Handlers => { const config = getConfig(params); - const getClient = clientFactory(config, { name: 'nextjs-auth0', version }); + const client = new NodeClient(config, { name: 'nextjs-auth0', version }); const transientStore = new TransientStore(config); - const cookieStore = params.session?.store - ? new StatefulSession(config, Cookies) - : new StatelessSession(config, Cookies); + const cookieStore = params.session?.store ? new StatefulSession(config) : new StatelessSession(config); const sessionCache = new TestSessionCache(cookieStore); return { - handleLogin: loginHandler(config, getClient, transientStore), - handleLogout: logoutHandler(config, getClient, sessionCache), - handleCallback: callbackHandler(config, getClient, sessionCache, transientStore), + handleLogin: loginHandler(config, client, transientStore), + handleLogout: logoutHandler(config, client, sessionCache), + handleCallback: callbackHandler(config, client, sessionCache, transientStore), handleSession: async (req: IncomingMessage, res: ServerResponse) => { - const [json, iat] = await cookieStore.read(req); + const nodeReq = new NodeRequest(req); + const [json, iat] = await cookieStore.read(nodeReq); if (!json?.id_token) { res.writeHead(401); res.end(); return; } const session = new TokenSet(json); - await cookieStore.save(req, res, session, iat); + await cookieStore.save(nodeReq, new NodeResponse(res), session, iat); res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ ...session, claims: session?.claims() } as SessionResponse)); } @@ -102,25 +110,39 @@ const requestListener = ( handlers: Handlers, { - callbackOptions, + callbackOptions: nodeCallbackOptions, loginOptions, logoutOptions - }: { callbackOptions?: CallbackOptions; loginOptions?: LoginOptions; logoutOptions?: LogoutOptions } + }: { callbackOptions?: NodeCallbackOptions; loginOptions?: LoginOptions; logoutOptions?: LogoutOptions } ) => async (req: IncomingMessage, res: ServerResponse): Promise => { const { pathname } = url.parse(req.url as string, true); const parsedReq = await parseJson(req, res); + const nodeReq = new NodeRequest(parsedReq); + const nodeRes = new NodeResponse(res); + let callbackOptions: CallbackOptions | undefined = undefined; + if (nodeCallbackOptions?.afterCallback) { + const fn = nodeCallbackOptions.afterCallback; + callbackOptions = { + ...nodeCallbackOptions, + afterCallback: (...args) => fn(req, res, ...args) + }; + } try { switch (pathname) { case '/login': - return await handlers.handleLogin(parsedReq, res, loginOptions); + return await handlers.handleLogin(nodeReq, nodeRes, loginOptions); case '/logout': - return await handlers.handleLogout(parsedReq, res, logoutOptions); + return await handlers.handleLogout(nodeReq, nodeRes, logoutOptions); case '/callback': - return await handlers.handleCallback(parsedReq, res, callbackOptions); + return await handlers.handleCallback( + nodeReq, + nodeRes, + (callbackOptions || nodeCallbackOptions) as CallbackOptions + ); case '/session': - return await handlers.handleSession(parsedReq, res); + return await handlers.handleSession(req, res); default: res.writeHead(404); res.end(); @@ -143,7 +165,7 @@ export const setup = async ( https }: { https?: boolean; - callbackOptions?: CallbackOptions; + callbackOptions?: NodeCallbackOptions; loginOptions?: LoginOptions; logoutOptions?: LogoutOptions; customListener?: (req: IncomingMessage, res: ServerResponse) => void; diff --git a/tests/auth0-session/handlers/callback.test.ts b/tests/auth0-session/handlers/callback.test.ts index e17779e1e..dc685355f 100644 --- a/tests/auth0-session/handlers/callback.test.ts +++ b/tests/auth0-session/handlers/callback.test.ts @@ -6,7 +6,7 @@ import { encodeState } from '../../../src/auth0-session/utils/encoding'; import { SessionResponse, setup, teardown } from '../fixtures/server'; import { makeIdToken } from '../fixtures/cert'; import { toSignedCookieJar, get, post, defaultConfig, decodeJWT } from '../fixtures/helpers'; -import { ServerResponse } from 'http'; +import { IncomingMessage, ServerResponse } from 'http'; import { readFileSync } from 'fs'; import { join } from 'path'; import * as qs from 'querystring'; @@ -436,60 +436,6 @@ describe('callback', () => { expect(header.alg).toEqual('RS256'); }); - it('should use client secret jwt on token endpoint', async () => { - const idToken = await makeIdToken({ - c_hash: '77QmUPtjPfzWtF2AnpK9RQ' - }); - - const baseURL = await setup({ - ...defaultConfig, - authorizationParams: { - response_type: 'code' - }, - clientSecret: 'foo', - clientAuthMethod: 'client_secret_jwt' - }); - - let body: qs.ParsedUrlQuery = {}; - nock('https://op.example.com') - .post('/oauth/token') - .reply(200, function (_uri, requestBody) { - body = qs.parse(requestBody as string); - return { - access_token: '__test_access_token__', - refresh_token: '__test_refresh_token__', - id_token: idToken, - token_type: 'Bearer', - expires_in: 86400 - }; - }); - - const cookieJar = await toSignedCookieJar( - { - state: expectedDefaultState, - nonce: '__test_nonce__' - }, - baseURL - ); - - await post(baseURL, '/callback', { - body: { - state: expectedDefaultState, - id_token: idToken, - code: 'jHkWEdUXMU1BwAsC4vtUsZwnNvTIxEl0z9K3vx5KF0Y' - }, - cookieJar, - fullResponse: true - }); - - expect(body.client_assertion).not.toBeUndefined(); - expect(body.client_assertion_type).toEqual('urn:ietf:params:oauth:client-assertion-type:jwt-bearer'); - - const { header } = decodeJWT(body.client_assertion as string); - - expect(header.alg).toEqual('HS256'); - }); - it('should redirect to default base url', async () => { const baseURL = await setup(defaultConfig); @@ -536,7 +482,7 @@ describe('callback', () => { it('should not overwrite location header if set in after callback', async () => { const baseURL = await setup(defaultConfig, { callbackOptions: { - afterCallback(_req, res: ServerResponse, session) { + afterCallback(_req: IncomingMessage, res: ServerResponse, session: any) { res.setHeader('Location', '/foo'); return session; } diff --git a/tests/auth0-session/handlers/login.test.ts b/tests/auth0-session/handlers/login.test.ts index dd13ad555..31f7fbd80 100644 --- a/tests/auth0-session/handlers/login.test.ts +++ b/tests/auth0-session/handlers/login.test.ts @@ -4,7 +4,6 @@ import { setup, teardown } from '../fixtures/server'; import { defaultConfig, fromCookieJar, get, getCookie } from '../fixtures/helpers'; import { decodeState, encodeState } from '../../../src/auth0-session/utils/encoding'; import { LoginOptions } from '../../../src/auth0-session'; -import { IncomingMessage } from 'http'; describe('login', () => { afterEach(teardown); @@ -181,7 +180,7 @@ describe('login', () => { it('should use a custom state builder', async () => { const baseURL = await setup({ ...defaultConfig, - getLoginState: (_req: IncomingMessage, opts: LoginOptions) => { + getLoginState: (opts: LoginOptions) => { return { returnTo: opts.returnTo + '/custom-page', customProp: '__test_custom_prop__' diff --git a/tests/auth0-session/http/node-request.test.ts b/tests/auth0-session/http/node-request.test.ts new file mode 100644 index 000000000..99b87b4b4 --- /dev/null +++ b/tests/auth0-session/http/node-request.test.ts @@ -0,0 +1,38 @@ +import { AddressInfo } from 'net'; +import { createServer, get as getRequest, IncomingMessage, ServerResponse } from 'http'; +import { NodeRequest } from '../../../src/auth0-session/http'; + +const setup = (): Promise<[IncomingMessage, ServerResponse, Function]> => + new Promise((resolve) => { + const server = createServer((req, res) => { + resolve([ + req, + res, + (): Promise => + new Promise((resolve) => { + res.end(); + server.close(resolve as (err?: Error) => void); + }) + ]); + }); + server.listen(0, () => { + const url = `http://localhost:${(server.address() as AddressInfo).port}`; + getRequest(url); + }); + }); + +describe('NodeRequest', () => { + it('should get all cookies', async () => { + const [req, , teardown] = await setup(); + req.headers.cookie = 'foo=bar; bar=baz;'; + expect(new NodeRequest(req).getCookies()).toMatchObject({ foo: 'bar', bar: 'baz' }); + await teardown(); + }); + + it('should get a cookie by name', async () => { + const [req, , teardown] = await setup(); + req.headers.cookie = 'foo=bar; bar=baz;'; + expect(new NodeRequest(req).getCookies()['foo']).toEqual('bar'); + await teardown(); + }); +}); diff --git a/tests/auth0-session/http/node-response.test.ts b/tests/auth0-session/http/node-response.test.ts new file mode 100644 index 000000000..9837a73cd --- /dev/null +++ b/tests/auth0-session/http/node-response.test.ts @@ -0,0 +1,57 @@ +import { AddressInfo } from 'net'; +import { createServer, get as getRequest, IncomingMessage, ServerResponse } from 'http'; +import { NodeResponse } from '../../../src/auth0-session/http'; + +const setup = (): Promise<[IncomingMessage, ServerResponse, Function]> => + new Promise((resolve) => { + const server = createServer((req, res) => { + resolve([ + req, + res, + (): Promise => + new Promise((resolve) => { + res.end(); + server.close(resolve as (err?: Error) => void); + }) + ]); + }); + server.listen(0, () => { + const url = `http://localhost:${(server.address() as AddressInfo).port}`; + getRequest(url); + }); + }); + +describe('NodeResponse', () => { + it('should set a cookie', async () => { + const [, res, teardown] = await setup(); + const setter = new NodeResponse(res); + setter.setCookie('foo', 'bar'); + expect(res.getHeader('Set-Cookie')).toEqual(['foo=bar']); + await teardown(); + }); + + it('should set a cookie with opts', async () => { + const [, res, teardown] = await setup(); + const setter = new NodeResponse(res); + setter.setCookie('foo', 'bar', { httpOnly: true, sameSite: 'strict' }); + expect(res.getHeader('Set-Cookie')).toEqual(['foo=bar; HttpOnly; SameSite=Strict']); + await teardown(); + }); + + it('should not overwrite existing set cookie', async () => { + const [, res, teardown] = await setup(); + res.setHeader('Set-Cookie', 'foo=bar'); + const setter = new NodeResponse(res); + setter.setCookie('baz', 'qux'); + expect(res.getHeader('Set-Cookie')).toEqual(['foo=bar', 'baz=qux']); + await teardown(); + }); + + it('should clear cookies', async () => { + const [, res, teardown] = await setup(); + const setter = new NodeResponse(res); + setter.clearCookie('foo'); + expect(res.getHeader('Set-Cookie')).toEqual(['foo=; Max-Age=0']); + await teardown(); + }); +}); diff --git a/tests/auth0-session/session/stateless-session.test.ts b/tests/auth0-session/session/stateless-session.test.ts index 8ffb2c7a8..7eb01c336 100644 --- a/tests/auth0-session/session/stateless-session.test.ts +++ b/tests/auth0-session/session/stateless-session.test.ts @@ -285,7 +285,7 @@ describe('StatelessSession', () => { }); it('should expire after 1 day of inactivity by default', async () => { - const clock = jest.useFakeTimers('modern'); + const clock = jest.useFakeTimers(); const baseURL = await setup(defaultConfig); const appSession = await encrypted(); @@ -298,7 +298,7 @@ describe('StatelessSession', () => { }); it('should expire after 7 days regardless of activity by default', async () => { - const clock = jest.useFakeTimers('modern'); + const clock = jest.useFakeTimers(); let days = 7; const baseURL = await setup(defaultConfig); @@ -315,7 +315,7 @@ describe('StatelessSession', () => { }); it('should expire only after custom absoluteDuration', async () => { - const clock = jest.useFakeTimers('modern'); + const clock = jest.useFakeTimers(); const baseURL = await setup({ ...defaultConfig, @@ -336,7 +336,7 @@ describe('StatelessSession', () => { }); it('should expire only after defined rollingDuration period of inactivty', async () => { - const clock = jest.useFakeTimers('modern'); + const clock = jest.useFakeTimers(); const baseURL = await setup({ ...defaultConfig, session: { diff --git a/tests/auth0-session/transient-store.test.ts b/tests/auth0-session/transient-store.test.ts index a50162781..426371aeb 100644 --- a/tests/auth0-session/transient-store.test.ts +++ b/tests/auth0-session/transient-store.test.ts @@ -1,10 +1,10 @@ -import { IncomingMessage, ServerResponse } from 'http'; import * as jose from 'jose'; import { CookieJar } from 'tough-cookie'; import { getConfig, TransientStore } from '../../src/auth0-session/'; import { signing } from '../../src/auth0-session/utils/hkdf'; import { defaultConfig, fromCookieJar, get, getCookie, toSignedCookieJar } from './fixtures/helpers'; import { setup as createServer, teardown } from './fixtures/server'; +import { NodeRequest, NodeResponse } from '../../src/auth0-session/http'; const generateSignature = async (cookie: string, value: string): Promise => { const key = await signing(defaultConfig.secret as string); @@ -14,10 +14,14 @@ const generateSignature = async (cookie: string, value: string): Promise return signature; }; -const setup = async (params = defaultConfig, cb: Function, https = true): Promise => +const setup = async ( + params = defaultConfig, + cb: (req: NodeRequest, res: NodeResponse) => Promise, + https = true +): Promise => createServer(params, { customListener: async (req, res) => { - const value = await cb(req, res); + const value = await cb(new NodeRequest(req), new NodeResponse(res)); res.end(JSON.stringify({ value })); }, https @@ -27,12 +31,10 @@ describe('TransientStore', () => { afterEach(teardown); it('should use the passed-in key to set the cookies', async () => { - const baseURL = await setup( - defaultConfig, - async (req: IncomingMessage, res: ServerResponse) => - await transientStore.save('test_key', req, res, { value: 'foo' }) + const baseURL: string = await setup(defaultConfig, async (req: NodeRequest, res: NodeResponse) => + transientStore.save('test_key', req, res, { value: 'foo' }) ); - const transientStore = new TransientStore(getConfig({ ...defaultConfig, baseURL })); + const transientStore: TransientStore = new TransientStore(getConfig({ ...defaultConfig, baseURL })); const cookieJar = new CookieJar(); const { value } = await get(baseURL, '/', { cookieJar }); const cookies = fromCookieJar(cookieJar, baseURL); @@ -43,7 +45,7 @@ describe('TransientStore', () => { it('should accept list of secrets', async () => { const config = { ...defaultConfig, secret: ['__old_secret__', defaultConfig.secret as string] }; - const baseURL = await setup(config, (req: IncomingMessage, res: ServerResponse) => + const baseURL: string = await setup(config, (req: NodeRequest, res: NodeResponse) => transientStore.save('test_key', req, res, { value: 'foo' }) ); const transientStore = new TransientStore(getConfig({ ...config, baseURL })); @@ -56,7 +58,7 @@ describe('TransientStore', () => { }); it('should set cookie to secure by default when baseURL protocol is https', async () => { - const baseURL = await setup(defaultConfig, (req: IncomingMessage, res: ServerResponse) => + const baseURL: string = await setup(defaultConfig, (req: NodeRequest, res: NodeResponse) => transientStore.save('test_key', req, res, { value: 'foo' }) ); const transientStore = new TransientStore(getConfig({ ...defaultConfig, baseURL })); @@ -68,9 +70,9 @@ describe('TransientStore', () => { }); it('should set cookie to not secure when baseURL protocol is http and SameSite=Lax', async () => { - const baseURL = await setup( + const baseURL: string = await setup( defaultConfig, - (req: IncomingMessage, res: ServerResponse) => transientStore.save('test_key', req, res, { value: 'foo' }), + (req: NodeRequest, res: NodeResponse) => transientStore.save('test_key', req, res, { value: 'foo' }), false ); const transientStore = new TransientStore(getConfig({ ...defaultConfig, baseURL })); @@ -81,10 +83,12 @@ describe('TransientStore', () => { expect(cookie?.secure).toBeFalsy(); }); - it('should set SameSite=None, Secure=False for fallback cookie by default for http', async () => { - const baseURL = await setup( + it('should not set SameSite and set Secure=False for fallback cookie by default for http', async () => { + const baseURL: string = await setup( defaultConfig, - (req: IncomingMessage, res: ServerResponse) => transientStore.save('test_key', req, res, { value: 'foo' }), + (req: NodeRequest, res: NodeResponse) => { + return transientStore.save('test_key', req, res, { value: 'foo' }); + }, false ); const transientStore = new TransientStore(getConfig({ ...defaultConfig, baseURL })); @@ -93,16 +97,16 @@ describe('TransientStore', () => { const fallbackCookie = getCookie('_test_key', cookieJar, baseURL); expect(value).toEqual(expect.any(String)); expect(fallbackCookie).toMatchObject({ - sameSite: 'none', + sameSite: undefined, secure: false, httpOnly: true }); }); it('should turn off fallback', async () => { - const baseURL = await setup( + const baseURL: string = await setup( { ...defaultConfig, legacySameSiteCookie: false }, - (req: IncomingMessage, res: ServerResponse) => transientStore.save('test_key', req, res, { value: 'foo' }) + (req: NodeRequest, res: NodeResponse) => transientStore.save('test_key', req, res, { value: 'foo' }) ); const transientStore = new TransientStore(getConfig({ ...defaultConfig, baseURL, legacySameSiteCookie: false })); const cookieJar = new CookieJar(); @@ -113,7 +117,7 @@ describe('TransientStore', () => { }); it('should set custom SameSite with no fallback', async () => { - const baseURL = await setup(defaultConfig, (req: IncomingMessage, res: ServerResponse) => + const baseURL: string = await setup(defaultConfig, (req: NodeRequest, res: NodeResponse) => transientStore.save('test_key', req, res, { sameSite: 'lax', value: 'foo' }) ); const transientStore = new TransientStore(getConfig({ ...defaultConfig, baseURL })); @@ -132,7 +136,7 @@ describe('TransientStore', () => { }); it('should return undefined if there are no cookies', async () => { - const baseURL = await setup(defaultConfig, (req: IncomingMessage, res: ServerResponse) => + const baseURL: string = await setup(defaultConfig, (req: NodeRequest, res: NodeResponse) => transientStore.read('test_key', req, res) ); const transientStore = new TransientStore(getConfig({ ...defaultConfig, baseURL })); @@ -141,7 +145,7 @@ describe('TransientStore', () => { }); it('should return main value and delete both cookies by default', async () => { - const baseURL = await setup(defaultConfig, (req: IncomingMessage, res: ServerResponse) => + const baseURL: string = await setup(defaultConfig, (req: NodeRequest, res: NodeResponse) => transientStore.read('test_key', req, res) ); const transientStore = new TransientStore(getConfig({ ...defaultConfig, baseURL })); @@ -161,7 +165,7 @@ describe('TransientStore', () => { }); it('should return fallback value and delete both cookies if main value not present', async () => { - const baseURL = await setup(defaultConfig, (req: IncomingMessage, res: ServerResponse) => + const baseURL: string = await setup(defaultConfig, (req: NodeRequest, res: NodeResponse) => transientStore.read('test_key', req, res) ); const transientStore = new TransientStore(getConfig({ ...defaultConfig, baseURL })); @@ -179,9 +183,9 @@ describe('TransientStore', () => { }); it('should not check fallback value when legacySameSiteCookie is false', async () => { - const baseURL = await setup( + const baseURL: string = await setup( { ...defaultConfig, legacySameSiteCookie: false }, - (req: IncomingMessage, res: ServerResponse) => transientStore.read('test_key', req, res) + (req: NodeRequest, res: NodeResponse) => transientStore.read('test_key', req, res) ); const transientStore = new TransientStore(getConfig({ ...defaultConfig, baseURL, legacySameSiteCookie: false })); const cookieJar = await toSignedCookieJar( @@ -197,9 +201,7 @@ describe('TransientStore', () => { }); it("should not throw when it can't verify the signature", async () => { - const baseURL = await setup(defaultConfig, (req: IncomingMessage, res: ServerResponse) => - transientStore.read('test_key', req, res) - ); + const baseURL: string = await setup(defaultConfig, (req, res) => transientStore.read('test_key', req, res)); const transientStore = new TransientStore(getConfig({ ...defaultConfig, baseURL })); const cookieJar = await toSignedCookieJar( { @@ -212,10 +214,4 @@ describe('TransientStore', () => { const { value } = await get(baseURL, '/', { cookieJar }); expect(value).toBeUndefined(); }); - - it('should generate a code verifier and challenge', async () => { - const transientStore = new TransientStore(getConfig({ ...defaultConfig, baseURL: 'http://example.com' })); - expect(transientStore.generateCodeVerifier()).toEqual(expect.any(String)); - expect(transientStore.calculateCodeChallenge('foo')).toEqual(expect.any(String)); - }); }); diff --git a/tests/auth0-session/utils/cookie.test.ts b/tests/auth0-session/utils/cookie.test.ts deleted file mode 100644 index 04b1e1c7d..000000000 --- a/tests/auth0-session/utils/cookie.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { AddressInfo } from 'net'; -import { createServer, get as getRequest, IncomingMessage, ServerResponse } from 'http'; -import NodeCookies from '../../../src/auth0-session/utils/cookies'; - -const setup = (): Promise<[IncomingMessage, ServerResponse, Function]> => - new Promise((resolve) => { - const server = createServer((req, res) => { - resolve([ - req, - res, - (): Promise => - new Promise((resolve) => { - res.end(); - server.close(resolve as (err?: Error) => void); - }) - ]); - }); - server.listen(0, () => { - const url = `http://localhost:${(server.address() as AddressInfo).port}`; - getRequest(url); - }); - }); - -describe('cookie', () => { - it('should get all cookies', async () => { - const [req, , teardown] = await setup(); - req.headers.cookie = 'foo=bar; bar=baz;'; - expect(new NodeCookies().getAll(req)).toMatchObject({ foo: 'bar', bar: 'baz' }); - await teardown(); - }); - - it('should get a cookie by name', async () => { - const [req, , teardown] = await setup(); - req.headers.cookie = 'foo=bar; bar=baz;'; - expect(new NodeCookies().getAll(req)['foo']).toEqual('bar'); - await teardown(); - }); - - it('should set a cookie', async () => { - const [, res, teardown] = await setup(); - const setter = new NodeCookies(); - setter.set('foo', 'bar'); - setter.commit(res); - expect(res.getHeader('Set-Cookie')).toEqual(['foo=bar']); - await teardown(); - }); - - it('should set a cookie with opts', async () => { - const [, res, teardown] = await setup(); - const setter = new NodeCookies(); - setter.set('foo', 'bar', { httpOnly: true, sameSite: 'strict' }); - setter.commit(res); - expect(res.getHeader('Set-Cookie')).toEqual(['foo=bar; HttpOnly; SameSite=Strict']); - await teardown(); - }); - - it('should not overwrite existing set cookie', async () => { - const [, res, teardown] = await setup(); - res.setHeader('Set-Cookie', 'foo=bar'); - const setter = new NodeCookies(); - setter.set('baz', 'qux'); - setter.commit(res); - expect(res.getHeader('Set-Cookie')).toEqual(['foo=bar', 'baz=qux']); - await teardown(); - }); - - it('should override existing cookies that equal name', async () => { - const [, res, teardown] = await setup(); - res.setHeader('Set-Cookie', ['foo=bar', 'baz=qux']); - const setter = new NodeCookies(); - setter.set('foo', 'qux'); - setter.commit(res, 'foo'); - expect(res.getHeader('Set-Cookie')).toEqual(['baz=qux', 'foo=qux']); - await teardown(); - }); - - it('should override existing cookies that match name', async () => { - const [, res, teardown] = await setup(); - res.setHeader('Set-Cookie', ['foo.1=bar', 'foo.2=baz']); - const setter = new NodeCookies(); - setter.set('foo', 'qux'); - setter.commit(res, 'foo'); - expect(res.getHeader('Set-Cookie')).toEqual(['foo=qux']); - await teardown(); - }); - - it('should clear cookies', async () => { - const [, res, teardown] = await setup(); - const setter = new NodeCookies(); - setter.clear('foo'); - setter.commit(res); - expect(res.getHeader('Set-Cookie')).toEqual(['foo=; Max-Age=0']); - await teardown(); - }); -}); diff --git a/tests/auth0-session/utils/errors.test.ts b/tests/auth0-session/utils/errors.test.ts index 8981fc809..53e39aa88 100644 --- a/tests/auth0-session/utils/errors.test.ts +++ b/tests/auth0-session/utils/errors.test.ts @@ -1,9 +1,8 @@ -import { IdentityProviderError } from '../../../src'; +import { IdentityProviderError } from '../../../src/auth0-session'; describe('IdentityProviderError', () => { test('should escape error fields', () => { const error = new IdentityProviderError({ - name: 'RPError', message: "", error: "", error_description: "" diff --git a/tests/config.test.ts b/tests/config.test.ts index ba8b6657b..041cdac31 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -99,8 +99,7 @@ describe('config params', () => { routes: { login: '/api/auth/login', callback: '/api/auth/callback', - postLogoutRedirect: '', - unauthorized: '/api/auth/401' + postLogoutRedirect: '' }, organization: undefined, session: { diff --git a/tests/fixtures/app-router-helpers.ts b/tests/fixtures/app-router-helpers.ts new file mode 100644 index 000000000..ffa3c29c2 --- /dev/null +++ b/tests/fixtures/app-router-helpers.ts @@ -0,0 +1,134 @@ +import nock from 'nock'; +import { default as nodeFetch } from 'node-fetch'; +import { NextRequest, NextResponse } from 'next/server'; +import { + Auth0Server, + CallbackOptions, + Claims, + ConfigParameters, + initAuth0 as nodeInitAuth0, + LoginOptions, + LogoutOptions, + ProfileOptions +} from '../../src'; +import { initAuth0 as edgeInitAuth0 } from '../../src/edge'; +import { withApi } from './default-settings'; +import { setupNock } from './setup'; +import { StatelessSession } from '../../src/auth0-session'; +import { getConfig } from '../../src/config'; +import { Auth0NextRequest } from '../../src/http'; +import { encodeState } from '../../src/auth0-session/utils/encoding'; +import { signCookie } from '../auth0-session/fixtures/helpers'; + +const isEdgeRuntime = + // @ts-ignore + typeof EdgeRuntime !== 'undefined'; + +export const initAuth0 = (config: ConfigParameters) => { + if (isEdgeRuntime) { + return edgeInitAuth0(config); + } + return nodeInitAuth0(config); +}; + +export const mockFetch = () => { + if (isEdgeRuntime) { + jest.spyOn(globalThis, 'fetch').mockImplementation((...args: any[]) => + (nodeFetch as any)(...args).then(async (res: any) => { + const res2 = new Response(await res.text(), { + headers: Object.fromEntries(res.headers.entries()), + status: res.status + }); + Object.defineProperty(res2, 'url', { value: args[0] }); + return res2; + }) + ); + } +}; + +export type GetResponseOpts = { + url: string; + config?: ConfigParameters; + cookies?: { [key: string]: string }; + idTokenClaims?: Claims; + discoveryOptions?: Record; + userInfoPayload?: Record; + userInfoToken?: string; + callbackOpts?: CallbackOptions; + loginOpts?: LoginOptions; + logoutOpts?: LogoutOptions; + profileOpts?: ProfileOptions; + extraHandlers?: any; + clearNock?: boolean; + auth0Instance?: Auth0Server; +}; + +export type LoginOpts = Omit; + +export const getResponse = async ({ + url, + config, + cookies, + idTokenClaims, + discoveryOptions, + userInfoPayload, + userInfoToken, + callbackOpts, + loginOpts, + logoutOpts, + profileOpts, + extraHandlers, + clearNock = true, + auth0Instance +}: GetResponseOpts) => { + const opts = { ...withApi, ...config }; + clearNock && nock.cleanAll(); + await setupNock(opts, { idTokenClaims, discoveryOptions, userInfoPayload, userInfoToken }); + const auth0 = url.split('?')[0].split('/').slice(3); + const instance = auth0Instance || initAuth0(opts); + const handleAuth = instance.handleAuth({ + ...(callbackOpts && { callback: instance.handleCallback(callbackOpts) }), + ...(loginOpts && { login: instance.handleLogin(loginOpts) }), + ...(logoutOpts && { logout: instance.handleLogout(logoutOpts) }), + ...(profileOpts && { profile: instance.handleProfile(profileOpts) }), + onError(_req: any, error: any) { + return new Response(null, { status: error.status || 500, statusText: error.message }); + }, + ...extraHandlers + }); + let headers = new Headers(); + if (cookies) { + headers.set( + 'Cookie', + Object.entries(cookies) + .map(([k, v]) => `${k}=${v}`) + .join('; ') + ); + } + return handleAuth(new NextRequest(new URL(url, opts.baseURL), { headers }), { params: { auth0 } }); +}; + +export const getSession = async (config: any, res: NextResponse) => { + const req = new NextRequest('https://example.com'); + res.cookies + .getAll() + .forEach(({ name, value }: { name: string; value: string }) => value && req.cookies.set(name, value)); + + const store = new StatelessSession(getConfig(config).baseConfig); + const [session] = await store.read(new Auth0NextRequest(req)); + return session; +}; + +export const login = async (opts: LoginOpts = {}) => { + const state = encodeState({ returnTo: '/' }); + return await getResponse({ + ...opts, + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + ...opts.cookies, + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') + } + }); +}; diff --git a/tests/fixtures/oidc-nocks.ts b/tests/fixtures/oidc-nocks.ts index 843a24544..6b8d12b99 100644 --- a/tests/fixtures/oidc-nocks.ts +++ b/tests/fixtures/oidc-nocks.ts @@ -81,12 +81,13 @@ export function jwksEndpoint(params: ConfigParameters, keyset: JSONWebKeySet): n export function codeExchange(params: ConfigParameters, idToken: string, code = 'code'): nock.Scope { return nock(`${params.issuerBaseURL}`) - .post( - '/oauth/token', - `grant_type=authorization_code&code=${code}&redirect_uri=${encodeURIComponent( - `${params.baseURL}api/auth/callback` - )}` - ) + .post('/oauth/token', (body: any) => { + return ( + body.grant_type === 'authorization_code' && + body.code === code && + body.redirect_uri === `${params.baseURL}api/auth/callback` + ); + }) .reply(200, { access_token: 'eyJz93a...k4laUWw', expires_in: 750, @@ -110,7 +111,9 @@ export async function refreshTokenExchange( }); return nock(`${params.issuerBaseURL}`) - .post('/oauth/token', `grant_type=refresh_token&refresh_token=${refreshToken}`) + .post('/oauth/token', (body) => { + return body.grant_type === 'refresh_token' && body.refresh_token === refreshToken; + }) .reply(200, { access_token: newToken || 'eyJz93a...k4laUWw', id_token: idToken, @@ -145,7 +148,9 @@ export async function refreshTokenRotationExchange( }); return nock(`${params.issuerBaseURL}`) - .post('/oauth/token', `grant_type=refresh_token&refresh_token=${refreshToken}`) + .post('/oauth/token', (body) => { + return body.grant_type === 'refresh_token' && body.refresh_token === refreshToken; + }) .reply(200, { access_token: newToken || 'eyJz93a...k4laUWw', refresh_token: newrefreshToken || 'GEbRxBN...edjnXbL', diff --git a/tests/fixtures/setup.ts b/tests/fixtures/setup.ts index 7802eb0fb..1ab24897d 100644 --- a/tests/fixtures/setup.ts +++ b/tests/fixtures/setup.ts @@ -8,19 +8,21 @@ import { LoginOptions, LogoutOptions, ProfileOptions, - WithPageAuthRequiredOptions, + WithPageAuthRequiredPageRouterOptions, initAuth0, AccessTokenRequest, Claims, - OnError, - Handlers + PageRouterOnError, + HandleLogin, + HandleLogout, + HandleCallback, + HandleProfile } from '../../src'; import { codeExchange, discovery, jwksEndpoint, userInfo } from './oidc-nocks'; import { jwks, makeIdToken } from '../auth0-session/fixtures/cert'; import { start, stop } from './server'; import { encodeState } from '../../src/auth0-session/utils/encoding'; import { post, toSignedCookieJar } from '../auth0-session/fixtures/helpers'; -import { HandleLogin, HandleLogout, HandleCallback, HandleProfile } from '../../src'; export type SetupOptions = { idTokenClaims?: Claims; @@ -32,20 +34,35 @@ export type SetupOptions = { logoutOptions?: LogoutOptions; profileHandler?: HandleProfile; profileOptions?: ProfileOptions; - withPageAuthRequiredOptions?: WithPageAuthRequiredOptions; + withPageAuthRequiredOptions?: WithPageAuthRequiredPageRouterOptions; getAccessTokenOptions?: AccessTokenRequest; - onError?: OnError; - discoveryOptions?: Record; + onError?: PageRouterOnError; + discoveryOptions?: Record; userInfoPayload?: Record; userInfoToken?: string; asyncProps?: boolean; }; -export const defaultOnError: OnError = (_req, res, error) => { +export const defaultOnError: PageRouterOnError = (_req, res, error) => { res.statusMessage = error.message; res.status(error.status || 500).end(error.message); }; +export const setupNock = async ( + config: ConfigParameters, + { + idTokenClaims, + discoveryOptions, + userInfoPayload = {}, + userInfoToken = 'eyJz93a...k4laUWw' + }: Pick = {} +) => { + discovery(config, discoveryOptions); + jwksEndpoint(config, jwks); + codeExchange(config, await makeIdToken({ iss: 'https://acme.auth0.local/', ...idTokenClaims })); + userInfo(config, userInfoToken, userInfoPayload); +}; + export const setup = async ( config: ConfigParameters, { @@ -67,10 +84,7 @@ export const setup = async ( asyncProps }: SetupOptions = {} ): Promise => { - discovery(config, discoveryOptions); - jwksEndpoint(config, jwks); - codeExchange(config, await makeIdToken({ iss: 'https://acme.auth0.local/', ...idTokenClaims })); - userInfo(config, userInfoToken, userInfoPayload); + await setupNock(config, { idTokenClaims, discoveryOptions, userInfoPayload, userInfoToken }); const { handleAuth, handleCallback, @@ -88,7 +102,7 @@ export const setup = async ( const login: NextApiHandler = (...args) => (loginHandler || handleLogin)(...args, loginOptions); const logout: NextApiHandler = (...args) => (logoutHandler || handleLogout)(...args, logoutOptions); const profile: NextApiHandler = (...args) => (profileHandler || handleProfile)(...args, profileOptions); - const handlers: Handlers = { onError, callback, login, logout, profile }; + const handlers: { [key: string]: NextApiHandler } = { onError: onError as any, callback, login, logout, profile }; global.handleAuth = handleAuth.bind(null, handlers); global.getSession = getSession; global.touchSession = touchSession; diff --git a/tests/frontend/with-page-auth-required.test.tsx b/tests/frontend/with-page-auth-required.test.tsx index eda022aa7..9a613f492 100644 --- a/tests/frontend/with-page-auth-required.test.tsx +++ b/tests/frontend/with-page-auth-required.test.tsx @@ -8,21 +8,17 @@ import { render, screen, waitFor } from '@testing-library/react'; import { fetchUserErrorMock, withUserProvider, user } from '../fixtures/frontend'; import { withPageAuthRequired } from '../../src/client'; -const windowLocation = window.location; - describe('with-page-auth-required csr', () => { beforeAll(() => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore window.location is non-optional - delete window.location; - window.location = { - ...windowLocation, - assign: jest.fn(), - toString: jest.fn(() => 'https://example.com') - }; + Object.defineProperty(window, 'location', { + writable: true, + value: { + assign: jest.fn(), + toString: () => 'https://example.com' + } + }); }); afterEach(() => delete (global as any).fetch); - afterAll(() => (window.location = windowLocation)); it('should deny access to a CSR page when not authenticated', async () => { (global as any).fetch = fetchUserErrorMock; diff --git a/tests/handlers/auth-page-router.test.ts b/tests/handlers/auth-page-router.test.ts new file mode 100644 index 000000000..dcaffb593 --- /dev/null +++ b/tests/handlers/auth-page-router.test.ts @@ -0,0 +1,291 @@ +import { IncomingMessage, ServerResponse } from 'http'; +import { NextApiHandler } from 'next'; +import { withoutApi } from '../fixtures/default-settings'; +import { login, setup, teardown } from '../fixtures/setup'; +import { get } from '../auth0-session/fixtures/helpers'; +import { initAuth0 } from '../../src'; +import { LoginOptions, LogoutOptions, CallbackOptions, ProfileOptions } from '../../src/handlers'; +import * as baseLoginHandler from '../../src/auth0-session/handlers/login'; +import * as baseLogoutHandler from '../../src/auth0-session/handlers/logout'; +import * as baseCallbackHandler from '../../src/auth0-session/handlers/callback'; +import { Handler } from '../../src/handlers/router-helpers'; + +const handlerError = () => + expect.objectContaining({ + status: 400, + code: 'ERR_CALLBACK_HANDLER_FAILURE' + }); + +describe('auth handler (page router)', () => { + afterEach(teardown); + + test('return 500 for unexpected error', async () => { + const baseUrl = await setup(withoutApi); + global.handleAuth = initAuth0(withoutApi).handleAuth; + delete global.onError; + jest.spyOn(console, 'error').mockImplementation((error) => { + delete error.status; + }); + await expect(get(baseUrl, '/api/auth/callback?error=foo&error_description=bar&state=foo')).rejects.toThrow( + 'Internal Server Error' + ); + }); + + test('return 404 for unknown routes', async () => { + const baseUrl = await setup(withoutApi); + global.handleAuth = initAuth0(withoutApi).handleAuth; + await expect(get(baseUrl, '/api/auth/foo')).rejects.toThrow('Not Found'); + }); + + test('return 404 for unknown routes including builtin props', async () => { + const baseUrl = await setup(withoutApi); + global.handleAuth = initAuth0(withoutApi).handleAuth; + await expect(get(baseUrl, '/api/auth/__proto__')).rejects.toThrow('Not Found'); + }); + + test('return 404 when routes have extra parts', async () => { + const baseUrl = await setup(withoutApi); + global.handleAuth = initAuth0(withoutApi).handleAuth; + await expect(get(baseUrl, '/api/auth/me.css')).rejects.toThrow('Not Found'); + await expect(get(baseUrl, '/api/auth/me/foo.css')).rejects.toThrow('Not Found'); + await expect(get(baseUrl, '/api/auth/me/foo/bar.css')).rejects.toThrow('Not Found'); + }); + + test('accept custom error handler', async () => { + const onError = jest.fn((_req, res) => res.end()); + const baseUrl = await setup(withoutApi, { onError }); + await get(baseUrl, '/api/auth/callback?error=foo&error_description=bar&state=foo'); + expect(onError).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse), handlerError()); + }); + + test('use default error handler', async () => { + const baseUrl = await setup(withoutApi); + global.handleAuth = initAuth0(withoutApi).handleAuth; + delete global.onError; + // eslint-disable-next-line @typescript-eslint/no-empty-function + jest.spyOn(console, 'error').mockImplementation(() => {}); + await expect(get(baseUrl, '/api/auth/callback?error=foo&error_description=bar&state=foo')).rejects.toThrow( + 'Bad Request' + ); + expect(console.error).toHaveBeenCalledWith(handlerError()); + }); + + test('finish response if custom error does not', async () => { + const onError = jest.fn(); + const baseUrl = await setup(withoutApi); + global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { onError }); + await expect( + get(baseUrl, '/api/auth/callback?error=foo&error_description=bar&state=foo', { fullResponse: true }) + ).rejects.toThrow('Internal Server Error'); + expect(onError).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse), handlerError()); + }); + + test('finish response with custom error status', async () => { + const onError = jest.fn((_req, res) => res.status(418)); + const baseUrl = await setup(withoutApi); + global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { onError }); + await expect( + get(baseUrl, '/api/auth/callback?error=foo&error_description=bar&state=foo', { fullResponse: true }) + ).rejects.toThrow("I'm a Teapot"); + expect(onError).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse), handlerError()); + }); + + test('accept custom login handler', async () => { + const login = jest.fn(async (_req, res) => { + res.end(); + }) as NextApiHandler as Handler; + const baseUrl = await setup(withoutApi); + global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { login }); + await get(baseUrl, '/api/auth/login'); + expect(login).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse)); + }); + + test('accept custom logout handler', async () => { + const logout = jest.fn(async (_req, res) => { + res.end(); + }) as NextApiHandler as Handler; + const baseUrl = await setup(withoutApi); + global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { logout }); + await get(baseUrl, '/api/auth/logout'); + expect(logout).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse)); + }); + + test('accept custom callback handler', async () => { + const callback = jest.fn(async (_req, res) => { + res.end(); + }) as NextApiHandler as Handler; + const baseUrl = await setup(withoutApi); + global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { callback }); + await get(baseUrl, '/api/auth/callback'); + expect(callback).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse)); + }); + + test('accept custom profile handler', async () => { + const profile = jest.fn(async (_req, res) => { + res.end(); + }) as NextApiHandler as Handler; + const baseUrl = await setup(withoutApi); + global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { profile }); + await get(baseUrl, '/api/auth/me'); + expect(profile).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse)); + }); + + test('accept custom arbitrary handler', async () => { + const signup = jest.fn(async (_req, res) => { + res.end(); + }) as NextApiHandler as Handler; + const baseUrl = await setup(withoutApi); + global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { signup }); + await get(baseUrl, '/api/auth/signup'); + expect(signup).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse)); + }); + + test('accept custom login options', async () => { + const loginHandler = jest.fn(async (_req: any, res: any) => { + res.res.end(); + }); + jest.spyOn(baseLoginHandler, 'default').mockImplementation(() => loginHandler); + const options: LoginOptions = { authorizationParams: { scope: 'openid' } }; + const baseUrl = await setup(withoutApi); + const { handleLogin, handleAuth } = initAuth0(withoutApi); + global.handleAuth = handleAuth.bind(null, { + login: handleLogin(options) + }); + await get(baseUrl, '/api/auth/login'); + expect(loginHandler).toHaveBeenCalledWith( + expect.objectContaining({ req: expect.any(IncomingMessage) }), + expect.objectContaining({ res: expect.any(ServerResponse) }), + options + ); + }); + + test('accept custom logout options', async () => { + const logoutHandler = jest.fn(async (_req: any, res: any) => { + res.res.end(); + }); + jest.spyOn(baseLogoutHandler, 'default').mockImplementation(() => logoutHandler); + const options: LogoutOptions = { returnTo: '/foo' }; + const baseUrl = await setup(withoutApi); + const { handleLogout, handleAuth } = initAuth0(withoutApi); + global.handleAuth = handleAuth.bind(null, { + logout: handleLogout(options) + }); + await get(baseUrl, '/api/auth/logout'); + expect(logoutHandler).toHaveBeenCalledWith( + expect.objectContaining({ req: expect.any(IncomingMessage) }), + expect.objectContaining({ res: expect.any(ServerResponse) }), + options + ); + }); + + test('accept custom callback options', async () => { + const callbackHandler = jest.fn(async (_req: any, res: any) => { + res.res.end(); + }); + jest.spyOn(baseCallbackHandler, 'default').mockImplementation(() => callbackHandler); + const options: CallbackOptions = { redirectUri: '/foo' }; + const baseUrl = await setup(withoutApi); + const { handleCallback, handleAuth } = initAuth0(withoutApi); + global.handleAuth = handleAuth.bind(null, { + callback: handleCallback(options) + }); + await get(baseUrl, '/api/auth/callback'); + expect(callbackHandler).toHaveBeenCalledWith( + expect.objectContaining({ req: expect.any(IncomingMessage) }), + expect.objectContaining({ res: expect.any(ServerResponse) }), + expect.objectContaining(options) + ); + }); + + test('accept custom profile options', async () => { + const afterRefetch = jest.fn(async (_req, _res, session) => session); + const options: ProfileOptions = { refetch: true, afterRefetch }; + const baseUrl = await setup(withoutApi); + const { handleProfile, handleAuth } = initAuth0(withoutApi); + global.handleAuth = handleAuth.bind(null, { + profile: handleProfile(options) + }); + const cookieJar = await login(baseUrl); + await get(baseUrl, '/api/auth/me', { cookieJar }); + expect(afterRefetch).toHaveBeenCalled(); + }); + + test('accept custom login options provider', async () => { + const loginHandler = jest.fn(async (_req: any, res: any) => { + res.res.end(); + }); + jest.spyOn(baseLoginHandler, 'default').mockImplementation(() => loginHandler); + const options = { authorizationParams: { scope: 'openid' } }; + const optionsProvider = jest.fn(() => options); + const baseUrl = await setup(withoutApi); + const { handleLogin, handleAuth } = initAuth0(withoutApi); + + global.handleAuth = handleAuth.bind(null, { + login: handleLogin(optionsProvider) + }); + await get(baseUrl, '/api/auth/login'); + expect(optionsProvider).toHaveBeenCalled(); + expect(loginHandler).toHaveBeenCalledWith( + expect.objectContaining({ req: expect.any(IncomingMessage) }), + expect.objectContaining({ res: expect.any(ServerResponse) }), + options + ); + }); + + test('accept custom logout options provider', async () => { + const logoutHandler = jest.fn(async (_req: any, res: any) => { + res.res.end(); + }); + jest.spyOn(baseLogoutHandler, 'default').mockImplementation(() => logoutHandler); + const options: LogoutOptions = { returnTo: '/foo' }; + const optionsProvider = jest.fn(() => options); + const baseUrl = await setup(withoutApi); + const { handleLogout, handleAuth } = initAuth0(withoutApi); + global.handleAuth = handleAuth.bind(null, { + logout: handleLogout(optionsProvider) + }); + await get(baseUrl, '/api/auth/logout'); + expect(optionsProvider).toHaveBeenCalled(); + expect(logoutHandler).toHaveBeenCalledWith( + expect.objectContaining({ req: expect.any(IncomingMessage) }), + expect.objectContaining({ res: expect.any(ServerResponse) }), + options + ); + }); + + test('accept custom callback options provider', async () => { + const callbackHandler = jest.fn(async (_req: any, res: any) => { + res.res.end(); + }); + jest.spyOn(baseCallbackHandler, 'default').mockImplementation(() => callbackHandler); + const options: CallbackOptions = { redirectUri: '/foo' }; + const optionsProvider = jest.fn(() => options); + const baseUrl = await setup(withoutApi); + const { handleCallback, handleAuth } = initAuth0(withoutApi); + global.handleAuth = handleAuth.bind(null, { + callback: handleCallback(optionsProvider) + }); + await get(baseUrl, '/api/auth/callback'); + expect(optionsProvider).toHaveBeenCalled(); + expect(callbackHandler).toHaveBeenCalledWith( + expect.objectContaining({ req: expect.any(IncomingMessage) }), + expect.objectContaining({ res: expect.any(ServerResponse) }), + expect.objectContaining(options) + ); + }); + + test('accept custom profile options provider', async () => { + const afterRefetch = jest.fn(async (_req, _res, session) => session); + const options: ProfileOptions = { refetch: true, afterRefetch }; + const optionsProvider = jest.fn(() => options); + const baseUrl = await setup(withoutApi); + const { handleProfile, handleAuth } = initAuth0(withoutApi); + global.handleAuth = handleAuth.bind(null, { + profile: handleProfile(optionsProvider) + }); + const cookieJar = await login(baseUrl); + await get(baseUrl, '/api/auth/me', { cookieJar }); + expect(optionsProvider).toHaveBeenCalled(); + expect(afterRefetch).toHaveBeenCalled(); + }); +}); diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 7827bb9ca..a1da472e8 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -1,299 +1,70 @@ -import { IncomingMessage, ServerResponse } from 'http'; -import { ArgumentsOf } from 'ts-jest'; -import { withoutApi } from '../fixtures/default-settings'; -import { login, setup, teardown } from '../fixtures/setup'; -import { get } from '../auth0-session/fixtures/helpers'; -import { initAuth0, OnError, Session } from '../../src'; -import { LoginHandler, LoginOptions } from '../../src/handlers/login'; -import { LogoutHandler, LogoutOptions } from '../../src/handlers/logout'; -import { CallbackHandler, CallbackOptions } from '../../src/handlers/callback'; -import { ProfileHandler, ProfileOptions } from '../../src/handlers/profile'; -import * as baseLoginHandler from '../../src/auth0-session/handlers/login'; -import * as baseLogoutHandler from '../../src/auth0-session/handlers/logout'; -import * as baseCallbackHandler from '../../src/auth0-session/handlers/callback'; - -const handlerError = () => - expect.objectContaining({ - status: 400, - code: 'ERR_CALLBACK_HANDLER_FAILURE' - }); - -describe('auth handler', () => { - afterEach(teardown); +/** + * **REMOVE-TO-TEST-ON-EDGE**@jest-environment @edge-runtime/jest-environment + */ +import { getResponse } from '../fixtures/app-router-helpers'; +describe('auth handler (app router)', () => { test('return 500 for unexpected error', async () => { - const baseUrl = await setup(withoutApi); - global.handleAuth = initAuth0(withoutApi).handleAuth; - delete global.onError; - jest.spyOn(console, 'error').mockImplementation((error) => { - delete error.status; - }); - await expect(get(baseUrl, '/api/auth/callback?error=foo&error_description=bar&state=foo')).rejects.toThrow( - 'Internal Server Error' - ); + await expect( + getResponse({ + url: '/api/auth/foo', + extraHandlers: { + foo: () => { + throw new Error(); + } + } + }) + ).resolves.toMatchObject({ status: 500 }); }); test('return 404 for unknown routes', async () => { - const baseUrl = await setup(withoutApi); - global.handleAuth = initAuth0(withoutApi).handleAuth; - await expect(get(baseUrl, '/api/auth/foo')).rejects.toThrow('Not Found'); + await expect(getResponse({ url: '/api/auth/foo' })).resolves.toMatchObject({ status: 404 }); }); test('return 404 for unknown routes including builtin props', async () => { - const baseUrl = await setup(withoutApi); - global.handleAuth = initAuth0(withoutApi).handleAuth; - await expect(get(baseUrl, '/api/auth/__proto__')).rejects.toThrow('Not Found'); - }); - - test('return unauthorized for /401 route', async () => { - const baseUrl = await setup(withoutApi); - global.handleAuth = initAuth0(withoutApi).handleAuth; - await expect(get(baseUrl, '/api/auth/401')).rejects.toThrow('Unauthorized'); + await expect(getResponse({ url: '/api/auth/__proto__' })).resolves.toMatchObject({ status: 404 }); }); test('return 404 when routes have extra parts', async () => { - const baseUrl = await setup(withoutApi); - global.handleAuth = initAuth0(withoutApi).handleAuth; - await expect(get(baseUrl, '/api/auth/me.css')).rejects.toThrow('Not Found'); - await expect(get(baseUrl, '/api/auth/me/foo.css')).rejects.toThrow('Not Found'); - await expect(get(baseUrl, '/api/auth/me/foo/bar.css')).rejects.toThrow('Not Found'); - }); -}); - -describe('custom error handler', () => { - afterEach(teardown); - - test('accept custom error handler', async () => { - const onError = jest.fn>((_req, res) => res.end()); - const baseUrl = await setup(withoutApi, { onError }); - await get(baseUrl, '/api/auth/callback?error=foo&error_description=bar&state=foo'); - expect(onError).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse), handlerError()); + await expect(getResponse({ url: '/api/auth/me/foo.css' })).resolves.toMatchObject({ status: 404 }); }); test('use default error handler', async () => { - const baseUrl = await setup(withoutApi); - global.handleAuth = initAuth0(withoutApi).handleAuth; - delete global.onError; - // eslint-disable-next-line @typescript-eslint/no-empty-function jest.spyOn(console, 'error').mockImplementation(() => {}); - await expect(get(baseUrl, '/api/auth/callback?error=foo&error_description=bar&state=foo')).rejects.toThrow( - 'Bad Request' - ); - expect(console.error).toHaveBeenCalledWith(handlerError()); + await expect( + getResponse({ + url: '/api/auth/foo', + extraHandlers: { foo: jest.fn().mockRejectedValue(new Error()), onError: undefined } + }) + ).resolves.toMatchObject({ status: 500 }); + expect(console.error).toHaveBeenCalledWith(expect.any(Error)); }); - test('finish response if custom error does not', async () => { + test('accept custom error handler', async () => { const onError = jest.fn(); - const baseUrl = await setup(withoutApi); - global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { onError }); await expect( - get(baseUrl, '/api/auth/callback?error=foo&error_description=bar&state=foo', { fullResponse: true }) - ).rejects.toThrow('Internal Server Error'); - expect(onError).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse), handlerError()); - }); - - test('finish response with custom error status', async () => { - const onError = jest.fn>((_req, res) => res.status(418)); - const baseUrl = await setup(withoutApi); - global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { onError }); + getResponse({ + url: '/api/auth/foo', + extraHandlers: { + foo: jest.fn().mockRejectedValue(new Error()), + onError + } + }) + ).resolves.toMatchObject({ status: 500 }); + expect(onError).toHaveBeenCalledWith(expect.any(Request), expect.any(Error)); + }); + + test('accept custom error handler response', async () => { + const onError = jest.fn().mockReturnValue(new Response(null, { status: 418 })); await expect( - get(baseUrl, '/api/auth/callback?error=foo&error_description=bar&state=foo', { fullResponse: true }) - ).rejects.toThrow("I'm a Teapot"); - expect(onError).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse), handlerError()); - }); -}); - -describe('custom handlers', () => { - afterEach(teardown); - - test('accept custom login handler', async () => { - const login = jest.fn, ArgumentsOf>(async (_req, res) => { - res.end(); - }); - const baseUrl = await setup(withoutApi); - global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { login }); - await get(baseUrl, '/api/auth/login'); - expect(login).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse)); - }); - - test('accept custom logout handler', async () => { - const logout = jest.fn, ArgumentsOf>(async (_req, res) => { - res.end(); - }); - const baseUrl = await setup(withoutApi); - global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { logout }); - await get(baseUrl, '/api/auth/logout'); - expect(logout).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse)); - }); - - test('accept custom callback handler', async () => { - const callback = jest.fn, ArgumentsOf>(async (_req, res) => { - res.end(); - }); - const baseUrl = await setup(withoutApi); - global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { callback }); - await get(baseUrl, '/api/auth/callback'); - expect(callback).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse)); - }); - - test('accept custom profile handler', async () => { - const profile = jest.fn, ArgumentsOf>(async (_req, res) => { - res.end(); - }); - const baseUrl = await setup(withoutApi); - global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { profile }); - await get(baseUrl, '/api/auth/me'); - expect(profile).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse)); - }); - - test('accept custom arbitrary handler', async () => { - const signup = jest.fn, ArgumentsOf>(async (_req, res) => { - res.end(); - }); - const baseUrl = await setup(withoutApi); - global.handleAuth = initAuth0(withoutApi).handleAuth.bind(null, { signup }); - await get(baseUrl, '/api/auth/signup'); - expect(signup).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse)); - }); -}); - -describe('custom options', () => { - afterEach(teardown); - - test('accept custom login options', async () => { - const loginHandler = jest.fn(async (_req: IncomingMessage, res: ServerResponse) => { - res.end(); - }); - jest.spyOn(baseLoginHandler, 'default').mockImplementation(() => loginHandler); - const options: LoginOptions = { authorizationParams: { scope: 'openid' } }; - const baseUrl = await setup(withoutApi); - const { handleLogin, handleAuth } = initAuth0(withoutApi); - global.handleAuth = handleAuth.bind(null, { - login: handleLogin(options) - }); - await get(baseUrl, '/api/auth/login'); - expect(loginHandler).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse), options); - }); - - test('accept custom logout options', async () => { - const logoutHandler = jest.fn(async (_req: IncomingMessage, res: ServerResponse) => { - res.end(); - }); - jest.spyOn(baseLogoutHandler, 'default').mockImplementation(() => logoutHandler); - const options: LogoutOptions = { returnTo: '/foo' }; - const baseUrl = await setup(withoutApi); - const { handleLogout, handleAuth } = initAuth0(withoutApi); - global.handleAuth = handleAuth.bind(null, { - logout: handleLogout(options) - }); - await get(baseUrl, '/api/auth/logout'); - expect(logoutHandler).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse), options); - }); - - test('accept custom callback options', async () => { - const callbackHandler = jest.fn(async (_req: IncomingMessage, res: ServerResponse) => { - res.end(); - }); - jest.spyOn(baseCallbackHandler, 'default').mockImplementation(() => callbackHandler); - const options: CallbackOptions = { redirectUri: '/foo' }; - const baseUrl = await setup(withoutApi); - const { handleCallback, handleAuth } = initAuth0(withoutApi); - global.handleAuth = handleAuth.bind(null, { - callback: handleCallback(options) - }); - await get(baseUrl, '/api/auth/callback'); - expect(callbackHandler).toHaveBeenCalledWith( - expect.any(IncomingMessage), - expect.any(ServerResponse), - expect.objectContaining(options) - ); - }); - - test('accept custom profile options', async () => { - const afterRefetch = jest.fn(async (_req: IncomingMessage, _res: ServerResponse, session: Session) => session); - const options: ProfileOptions = { refetch: true, afterRefetch }; - const baseUrl = await setup(withoutApi); - const { handleProfile, handleAuth } = initAuth0(withoutApi); - global.handleAuth = handleAuth.bind(null, { - profile: handleProfile(options) - }); - const cookieJar = await login(baseUrl); - await get(baseUrl, '/api/auth/me', { cookieJar }); - expect(afterRefetch).toHaveBeenCalled(); - }); -}); - -describe('custom options providers', () => { - afterEach(teardown); - - test('accept custom login options provider', async () => { - const loginHandler = jest.fn(async (_req: IncomingMessage, res: ServerResponse) => { - res.end(); - }); - jest.spyOn(baseLoginHandler, 'default').mockImplementation(() => loginHandler); - const options = { authorizationParams: { scope: 'openid' } }; - const optionsProvider = jest.fn(() => options); - const baseUrl = await setup(withoutApi); - const { handleLogin, handleAuth } = initAuth0(withoutApi); - - global.handleAuth = handleAuth.bind(null, { - login: handleLogin(optionsProvider) - }); - await get(baseUrl, '/api/auth/login'); - expect(optionsProvider).toHaveBeenCalled(); - expect(loginHandler).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse), options); - }); - - test('accept custom logout options provider', async () => { - const logoutHandler = jest.fn(async (_req: IncomingMessage, res: ServerResponse) => { - res.end(); - }); - jest.spyOn(baseLogoutHandler, 'default').mockImplementation(() => logoutHandler); - const options: LogoutOptions = { returnTo: '/foo' }; - const optionsProvider = jest.fn(() => options); - const baseUrl = await setup(withoutApi); - const { handleLogout, handleAuth } = initAuth0(withoutApi); - global.handleAuth = handleAuth.bind(null, { - logout: handleLogout(optionsProvider) - }); - await get(baseUrl, '/api/auth/logout'); - expect(optionsProvider).toHaveBeenCalled(); - expect(logoutHandler).toHaveBeenCalledWith(expect.any(IncomingMessage), expect.any(ServerResponse), options); - }); - - test('accept custom callback options provider', async () => { - const callbackHandler = jest.fn(async (_req: IncomingMessage, res: ServerResponse) => { - res.end(); - }); - jest.spyOn(baseCallbackHandler, 'default').mockImplementation(() => callbackHandler); - const options: CallbackOptions = { redirectUri: '/foo' }; - const optionsProvider = jest.fn(() => options); - const baseUrl = await setup(withoutApi); - const { handleCallback, handleAuth } = initAuth0(withoutApi); - global.handleAuth = handleAuth.bind(null, { - callback: handleCallback(optionsProvider) - }); - await get(baseUrl, '/api/auth/callback'); - expect(optionsProvider).toHaveBeenCalled(); - expect(callbackHandler).toHaveBeenCalledWith( - expect.any(IncomingMessage), - expect.any(ServerResponse), - expect.objectContaining(options) - ); - }); - - test('accept custom profile options provider', async () => { - const afterRefetch = jest.fn(async (_req: IncomingMessage, _res: ServerResponse, session: Session) => session); - const options: ProfileOptions = { refetch: true, afterRefetch }; - const optionsProvider = jest.fn(() => options); - const baseUrl = await setup(withoutApi); - const { handleProfile, handleAuth } = initAuth0(withoutApi); - global.handleAuth = handleAuth.bind(null, { - profile: handleProfile(optionsProvider) - }); - const cookieJar = await login(baseUrl); - await get(baseUrl, '/api/auth/me', { cookieJar }); - expect(optionsProvider).toHaveBeenCalled(); - expect(afterRefetch).toHaveBeenCalled(); + getResponse({ + url: '/api/auth/foo', + extraHandlers: { + foo: jest.fn().mockRejectedValue(new Error()), + onError + } + }) + ).resolves.toMatchObject({ status: 418 }); + expect(onError).toHaveBeenCalled(); }); }); diff --git a/tests/handlers/callback-page-router.test.ts b/tests/handlers/callback-page-router.test.ts new file mode 100644 index 000000000..ccd7ed3d0 --- /dev/null +++ b/tests/handlers/callback-page-router.test.ts @@ -0,0 +1,462 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import { CookieJar } from 'tough-cookie'; +import * as jose from 'jose'; +import timekeeper from 'timekeeper'; +import { withApi, withoutApi } from '../fixtures/default-settings'; +import { makeIdToken } from '../auth0-session/fixtures/cert'; +import { defaultConfig, get, post, toSignedCookieJar } from '../auth0-session/fixtures/helpers'; +import { encodeState } from '../../src/auth0-session/utils/encoding'; +import { defaultOnError, setup, teardown } from '../fixtures/setup'; +import { Session, AfterCallbackPageRoute, MissingStateCookieError } from '../../src'; +import nock from 'nock'; +import { signing } from '../../src/auth0-session/utils/hkdf'; + +const callback = (baseUrl: string, body: any, cookieJar?: CookieJar): Promise => + post(baseUrl, `/api/auth/callback`, { + body, + cookieJar, + fullResponse: true + }); + +const generateSignature = async (cookie: string, value: string): Promise => { + const key = await signing(defaultConfig.secret as string); + const { signature } = await new jose.FlattenedSign(new TextEncoder().encode(`${cookie}=${value}`)) + .setProtectedHeader({ alg: 'HS256', b64: false, crit: ['b64'] }) + .sign(key); + return signature; +}; + +describe('callback handler (page router)', () => { + afterEach(teardown); + + test('should require a state', async () => { + expect.assertions(2); + const baseUrl = await setup(withoutApi, { + onError(req, res, err) { + expect(err.cause).toBeInstanceOf(MissingStateCookieError); + defaultOnError(req, res, err); + } + }); + await expect( + callback(baseUrl, { + state: '__test_state__' + }) + ).rejects.toThrow( + 'Callback handler failed. CAUSE: Missing state cookie from login request (check login URL, callback URL and cookie config).' + ); + }); + + test('should validate the state', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = await toSignedCookieJar( + { + state: '__other_state__' + }, + baseUrl + ); + await expect( + callback( + baseUrl, + { + state: '__test_state__' + }, + cookieJar + ) + ).rejects.toThrow('state mismatch, expected __other_state__, got: __test_state__'); + }); + + test('should validate the audience', async () => { + const baseUrl = await setup(withoutApi, { idTokenClaims: { aud: 'bar' } }); + const state = encodeState({ returnTo: baseUrl }); + const cookieJar = await toSignedCookieJar( + { + state, + nonce: '__test_nonce__' + }, + baseUrl + ); + await expect( + callback( + baseUrl, + { + state, + code: 'code' + }, + cookieJar + ) + ).rejects.toThrow('aud mismatch, expected __test_client_id__, got: bar'); + }); + + test('should validate the issuer', async () => { + const baseUrl = await setup(withoutApi, { idTokenClaims: { aud: 'bar', iss: 'other-issuer' } }); + const state = encodeState({ returnTo: baseUrl }); + const cookieJar = await toSignedCookieJar( + { + state, + nonce: '__test_nonce__' + }, + baseUrl + ); + await expect( + callback( + baseUrl, + { + state, + code: 'code' + }, + cookieJar + ) + ).rejects.toThrow('unexpected iss value, expected https://acme.auth0.local/, got: other-issuer'); + }); + + test('should escape html in error qp', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = await toSignedCookieJar( + { + state: `foo.${await generateSignature('state', 'foo')}` + }, + baseUrl + ); + await expect( + get(baseUrl, `/api/auth/callback?error=%3Cscript%3Ealert(%27xss%27)%3C%2Fscript%3E&state=foo`, { cookieJar }) + ).rejects.toThrow('<script>alert('xss')</script>'); + }); + + test('should create the session without OIDC claims', async () => { + const baseUrl = await setup(withoutApi); + const state = encodeState({ returnTo: baseUrl }); + const cookieJar = await toSignedCookieJar( + { + state, + nonce: '__test_nonce__' + }, + baseUrl + ); + const { res } = await callback( + baseUrl, + { + state, + code: 'code' + }, + cookieJar + ); + expect(res.statusCode).toBe(302); + const body = await get(baseUrl, `/api/session`, { cookieJar }); + expect(body.user).toStrictEqual({ + nickname: '__test_nickname__', + sub: '__test_sub__' + }); + }); + + test('should set the correct expiration', async () => { + timekeeper.freeze(0); + const baseUrl = await setup(withoutApi); + const state = encodeState({ returnTo: baseUrl }); + const cookieJar = await toSignedCookieJar( + { + state, + nonce: '__test_nonce__' + }, + baseUrl + ); + const { res } = await post(baseUrl, `/api/auth/callback`, { + fullResponse: true, + cookieJar, + body: { + state, + code: 'code' + } + }); + expect(res.statusCode).toBe(302); + + const [sessionCookie] = cookieJar.getCookiesSync(baseUrl); + const expiryInHrs = new Date(sessionCookie.expires).getTime() / 1000 / 60 / 60; + expect(expiryInHrs).toBe(24); + timekeeper.reset(); + }); + + test('should create the session without OIDC claims with api config', async () => { + timekeeper.freeze(0); + const baseUrl = await setup(withApi); + const state = encodeState({ returnTo: baseUrl }); + const cookieJar = await toSignedCookieJar( + { + state, + nonce: '__test_nonce__' + }, + baseUrl + ); + const { res } = await callback( + baseUrl, + { + state, + code: 'code' + }, + cookieJar + ); + expect(res.statusCode).toBe(302); + const session = await get(baseUrl, `/api/session`, { cookieJar }); + + expect(session).toStrictEqual({ + accessToken: 'eyJz93a...k4laUWw', + accessTokenExpiresAt: 750, + accessTokenScope: 'read:foo delete:foo', + token_type: 'Bearer', + refreshToken: 'GEbRxBN...edjnXbL', + idToken: await makeIdToken({ iss: 'https://acme.auth0.local/' }), + user: { + nickname: '__test_nickname__', + sub: '__test_sub__' + } + }); + timekeeper.reset(); + }); + + test('remove properties from session with afterCallback hook', async () => { + timekeeper.freeze(0); + const afterCallback: AfterCallbackPageRoute = ( + _req: NextApiRequest, + _res: NextApiResponse, + session: Session + ): Session => { + delete session.accessToken; + delete session.refreshToken; + return session; + }; + const baseUrl = await setup(withApi, { callbackOptions: { afterCallback } }); + const state = encodeState({ returnTo: baseUrl }); + const cookieJar = await toSignedCookieJar( + { + state, + nonce: '__test_nonce__' + }, + baseUrl + ); + const { res } = await callback( + baseUrl, + { + state, + code: 'code' + }, + cookieJar + ); + expect(res.statusCode).toBe(302); + const session = await get(baseUrl, `/api/session`, { cookieJar }); + + expect(session).toStrictEqual({ + accessTokenExpiresAt: 750, + accessTokenScope: 'read:foo delete:foo', + idToken: await makeIdToken({ iss: 'https://acme.auth0.local/' }), + token_type: 'Bearer', + user: { + nickname: '__test_nickname__', + sub: '__test_sub__' + } + }); + timekeeper.reset(); + }); + + test('add properties to session with afterCallback hook', async () => { + timekeeper.freeze(0); + const afterCallback: AfterCallbackPageRoute = (_req, _res, session: Session): Session => { + session.foo = 'bar'; + return session; + }; + const baseUrl = await setup(withApi, { callbackOptions: { afterCallback } }); + const state = encodeState({ returnTo: baseUrl }); + const cookieJar = await toSignedCookieJar( + { + state, + nonce: '__test_nonce__' + }, + baseUrl + ); + const { res } = await callback( + baseUrl, + { + state, + code: 'code' + }, + cookieJar + ); + expect(res.statusCode).toBe(302); + const session = await get(baseUrl, '/api/session', { cookieJar }); + + expect(session).toMatchObject({ + foo: 'bar', + user: { + nickname: '__test_nickname__', + sub: '__test_sub__' + } + }); + timekeeper.reset(); + }); + + test('throws from afterCallback hook', async () => { + const afterCallback = (): Session => { + throw new Error('some validation error.'); + }; + const baseUrl = await setup(withApi, { callbackOptions: { afterCallback } }); + const state = encodeState({ returnTo: baseUrl }); + const cookieJar = await toSignedCookieJar( + { + state, + nonce: '__test_nonce__' + }, + baseUrl + ); + await expect( + callback( + baseUrl, + { + state, + code: 'code' + }, + cookieJar + ) + ).rejects.toThrow('some validation error.'); + }); + + test('throws for missing org_id claim', async () => { + const baseUrl = await setup({ ...withApi, organization: 'org_foo' }); + const state = encodeState({ returnTo: baseUrl }); + const cookieJar = await toSignedCookieJar( + { + state, + nonce: '__test_nonce__' + }, + baseUrl + ); + await expect( + callback( + baseUrl, + { + state, + code: 'code' + }, + cookieJar + ) + ).rejects.toThrow(/Organization Id \(org_id\) claim must be a string present in the ID token/); + }); + + test('throws for org_id claim mismatch', async () => { + const baseUrl = await setup({ ...withApi, organization: 'org_foo' }, { idTokenClaims: { org_id: 'org_bar' } }); + const state = encodeState({ returnTo: baseUrl }); + const cookieJar = await toSignedCookieJar( + { + state, + nonce: '__test_nonce__' + }, + baseUrl + ); + await expect( + callback( + baseUrl, + { + state, + code: 'code' + }, + cookieJar + ) + ).rejects.toThrow( + /Organization Id \(org_id\) claim value mismatch in the ID token; expected "org_foo", found "org_bar"/ + ); + }); + + test('accepts a valid organization', async () => { + const baseUrl = await setup(withApi, { + idTokenClaims: { org_id: 'org_foo' }, + callbackOptions: { organization: 'org_foo' } + }); + const state = encodeState({ returnTo: baseUrl }); + const cookieJar = await toSignedCookieJar( + { + state, + nonce: '__test_nonce__' + }, + baseUrl + ); + await expect( + callback( + baseUrl, + { + state, + code: 'code' + }, + cookieJar + ) + ).resolves.not.toThrow(); + const session = await get(baseUrl, '/api/session', { cookieJar }); + + expect(session.user.org_id).toEqual('org_foo'); + }); + + test('should pass custom params to the token exchange', async () => { + const baseUrl = await setup(withoutApi, { + callbackOptions: { + authorizationParams: { foo: 'bar' } + } + }); + const state = encodeState({ returnTo: baseUrl }); + const cookieJar = await toSignedCookieJar( + { + state, + nonce: '__test_nonce__' + }, + baseUrl + ); + const spy = jest.fn(); + + nock(`${withoutApi.issuerBaseURL}`) + .post('/oauth/token', /grant_type=authorization_code/) + .reply(200, async (_, body) => { + spy(body); + return { + access_token: 'eyJz93a...k4laUWw', + expires_in: 750, + scope: 'read:foo delete:foo', + refresh_token: 'GEbRxBN...edjnXbL', + id_token: await makeIdToken({ iss: `${withoutApi.issuerBaseURL}/` }), + token_type: 'Bearer' + }; + }); + + const { res } = await callback( + baseUrl, + { + state, + code: 'foobar' + }, + cookieJar + ); + expect(res.statusCode).toBe(302); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('foo=bar')); + }); + + test('redirects from afterCallback hook', async () => { + const afterCallback = (_req: NextApiRequest, res: NextApiResponse): undefined => { + res.writeHead(302, '', { location: '/foo' }); + res.end(); + return; + }; + const baseUrl = await setup(withApi, { callbackOptions: { afterCallback } }); + const state = encodeState({ returnTo: baseUrl }); + const cookieJar = await toSignedCookieJar( + { + state, + nonce: '__test_nonce__' + }, + baseUrl + ); + const { res } = await callback( + baseUrl, + { + state, + code: 'code' + }, + cookieJar + ); + expect(res.statusCode).toBe(302); + expect(res.headers.location).toBe('/foo'); + }); +}); diff --git a/tests/handlers/callback.test.ts b/tests/handlers/callback.test.ts index c3d9437d4..3d8983ac6 100644 --- a/tests/handlers/callback.test.ts +++ b/tests/handlers/callback.test.ts @@ -1,500 +1,323 @@ -import { CookieJar } from 'tough-cookie'; -import * as jose from 'jose'; -import timekeeper from 'timekeeper'; -import { withApi, withoutApi } from '../fixtures/default-settings'; -import { makeIdToken } from '../auth0-session/fixtures/cert'; -import { defaultConfig, get, post, toSignedCookieJar } from '../auth0-session/fixtures/helpers'; +/** + * **REMOVE-TO-TEST-ON-EDGE**@jest-environment @edge-runtime/jest-environment + */ +import { NextRequest, NextResponse } from 'next/server'; +import { withApi } from '../fixtures/default-settings'; +import { signCookie } from '../auth0-session/fixtures/helpers'; import { encodeState } from '../../src/auth0-session/utils/encoding'; -import { defaultOnError, setup, teardown } from '../fixtures/setup'; -import { Session, AfterCallback, MissingStateCookieError } from '../../src'; -import nock from 'nock'; -import { signing } from '../../src/auth0-session/utils/hkdf'; +import type { Session } from '../../src'; +import { getResponse, mockFetch, getSession as getSessionFromRes } from '../fixtures/app-router-helpers'; -const callback = (baseUrl: string, body: any, cookieJar?: CookieJar): Promise => - post(baseUrl, `/api/auth/callback`, { - body, - cookieJar, - fullResponse: true - }); - -const generateSignature = async (cookie: string, value: string): Promise => { - const key = await signing(defaultConfig.secret as string); - const { signature } = await new jose.FlattenedSign(new TextEncoder().encode(`${cookie}=${value}`)) - .setProtectedHeader({ alg: 'HS256', b64: false, crit: ['b64'] }) - .sign(key); - return signature; -}; +describe('callback handler (app router)', () => { + beforeEach(mockFetch); -describe('callback handler', () => { - afterEach(teardown); - - test('should require a state', async () => { - expect.assertions(2); - const baseUrl = await setup(withoutApi, { - onError(req, res, err) { - expect(err.cause).toBeInstanceOf(MissingStateCookieError); - defaultOnError(req, res, err); + test('should require a state parameter', async () => { + const res = await getResponse({ + url: '/api/auth/callback', + cookies: { + state: await signCookie('state', 'foo') } }); - await expect( - callback(baseUrl, { - state: '__test_state__' - }) - ).rejects.toThrow( - 'Callback handler failed. CAUSE: Missing state cookie from login request (check login URL, callback URL and cookie config).' - ); + expect(res.status).toBe(400); + expect(res.statusText).toMatch(/Missing state parameter|response parameter "state" missing/); + }); + + test('should require a state cookie', async () => { + const res = await getResponse({ + url: '/api/auth/callback?state=__test_state__' + }); + expect(res.status).toBe(400); + expect(res.statusText).toMatch(/Missing state cookie from login request/); }); test('should validate the state', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = await toSignedCookieJar( - { - state: '__other_state__' - }, - baseUrl + const res = await getResponse({ + url: '/api/auth/callback?state=__test_state__', + cookies: { + state: await signCookie('state', 'other_state') + } + }); + expect(res.status).toBe(400); + expect(res.statusText).toMatch( + /state mismatch, expected other_state, got: __test_state__|unexpected "state" response parameter value/ ); - await expect( - callback( - baseUrl, - { - state: '__test_state__' - }, - cookieJar - ) - ).rejects.toThrow('state mismatch, expected __other_state__, got: __test_state__'); }); test('should validate the audience', async () => { - const baseUrl = await setup(withoutApi, { idTokenClaims: { aud: 'bar' } }); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' + const state = encodeState({ returnTo: 'https://example.com' }); + const res = await getResponse({ + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') }, - baseUrl + idTokenClaims: { aud: 'bar' } + }); + expect(res.status).toBe(400); + expect(res.statusText).toMatch( + /aud mismatch, expected __test_client_id__, got: bar|unexpected JWT "aud" \(audience\) claim value/ ); - await expect( - callback( - baseUrl, - { - state, - code: 'code' - }, - cookieJar - ) - ).rejects.toThrow('aud mismatch, expected __test_client_id__, got: bar'); }); test('should validate the issuer', async () => { - const baseUrl = await setup(withoutApi, { idTokenClaims: { aud: 'bar', iss: 'other-issuer' } }); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' - }, - baseUrl - ); - await expect( - callback( - baseUrl, - { - state, - code: 'code' - }, - cookieJar - ) - ).rejects.toThrow('unexpected iss value, expected https://acme.auth0.local/, got: other-issuer'); - }); - - it('should escape html in error qp', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = await toSignedCookieJar( - { - state: `foo.${await generateSignature('state', 'foo')}` - }, - baseUrl - ); - await expect( - get(baseUrl, `/api/auth/callback?error=%3Cscript%3Ealert(%27xss%27)%3C%2Fscript%3E&state=foo`, { cookieJar }) - ).rejects.toThrow('<script>alert('xss')</script>'); - }); - - test('should create the session without OIDC claims', async () => { - const baseUrl = await setup(withoutApi); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' + const state = encodeState({ returnTo: 'https://example.com' }); + const res = await getResponse({ + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') }, - baseUrl - ); - const { res } = await callback( - baseUrl, - { - state, - code: 'code' - }, - cookieJar - ); - expect(res.statusCode).toBe(302); - const body = await get(baseUrl, `/api/session`, { cookieJar }); - expect(body.user).toStrictEqual({ - nickname: '__test_nickname__', - sub: '__test_sub__' + idTokenClaims: { iss: 'other-issuer' } }); + expect(res.status).toBe(400); + expect(res.statusText).toMatch( + /unexpected iss value, expected https:\/\/acme.auth0.local\/, got: other-issuer|unexpected JWT "iss" \(issuer\) claim value/ + ); }); - test('should set the correct expiration', async () => { - timekeeper.freeze(0); - const baseUrl = await setup(withoutApi); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' - }, - baseUrl - ); - const { res } = await post(baseUrl, `/api/auth/callback`, { - fullResponse: true, - cookieJar, - body: { - state, - code: 'code' + test('should escape html in error qp', async () => { + const res = await getResponse({ + url: '/api/auth/callback?error=%3Cscript%3Ealert(%27xss%27)%3C%2Fscript%3E&state=foo', + cookies: { + state: await signCookie('state', 'foo') } }); - expect(res.statusCode).toBe(302); - - const [sessionCookie] = cookieJar.getCookiesSync(baseUrl); - const expiryInHrs = new Date(sessionCookie.expires).getTime() / 1000 / 60 / 60; - expect(expiryInHrs).toBe(24); - timekeeper.reset(); + expect(res.status).toBe(400); + expect(res.statusText).toMatch(/<script>alert\('xss'\)<\/script>/); }); - test('should create the session without OIDC claims with api config', async () => { - timekeeper.freeze(0); - const baseUrl = await setup(withApi); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' - }, - baseUrl - ); - const { res } = await callback( - baseUrl, - { - state, - code: 'code' - }, - cookieJar - ); - expect(res.statusCode).toBe(302); - const session = await get(baseUrl, `/api/session`, { cookieJar }); - - expect(session).toStrictEqual({ - accessToken: 'eyJz93a...k4laUWw', - accessTokenExpiresAt: 750, - accessTokenScope: 'read:foo delete:foo', - token_type: 'Bearer', - refreshToken: 'GEbRxBN...edjnXbL', - idToken: await makeIdToken({ iss: 'https://acme.auth0.local/' }), - user: { - nickname: '__test_nickname__', - sub: '__test_sub__' + test('should create session and strip OIDC claims', async () => { + const state = encodeState({ returnTo: 'https://example.com/foo' }); + const res = await getResponse({ + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') } }); - timekeeper.reset(); + expect(res.status).toEqual(302); + expect(res.headers.get('location')).toEqual('https://example.com/foo'); + const session = await getSessionFromRes(withApi, res); + expect(session).toMatchObject({ + user: { sub: '__test_sub__' }, + accessToken: expect.any(String) + }); + expect(session?.user).not.toHaveProperty('iss'); }); test('remove properties from session with afterCallback hook', async () => { - timekeeper.freeze(0); - const afterCallback: AfterCallback = (_req, _res, session: Session): Session => { - delete session.accessToken; - delete session.refreshToken; - return session; - }; - const baseUrl = await setup(withApi, { callbackOptions: { afterCallback } }); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' + const state = encodeState({ returnTo: 'https://example.com/foo' }); + const res = await getResponse({ + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') }, - baseUrl - ); - const { res } = await callback( - baseUrl, - { - state, - code: 'code' - }, - cookieJar - ); - expect(res.statusCode).toBe(302); - const session = await get(baseUrl, `/api/session`, { cookieJar }); - - expect(session).toStrictEqual({ - accessTokenExpiresAt: 750, - accessTokenScope: 'read:foo delete:foo', - idToken: await makeIdToken({ iss: 'https://acme.auth0.local/' }), - token_type: 'Bearer', - user: { - nickname: '__test_nickname__', - sub: '__test_sub__' + callbackOpts: { + afterCallback(_req: NextRequest, session: Session) { + delete session.accessToken; + return session; + } } }); - timekeeper.reset(); + expect(res.status).toEqual(302); + expect(res.headers.get('location')).toEqual('https://example.com/foo'); + const session = await getSessionFromRes(withApi, res); + expect(session).toMatchObject({ + user: { sub: '__test_sub__' } + }); + expect(session).not.toHaveProperty('accessToken'); }); test('add properties to session with afterCallback hook', async () => { - timekeeper.freeze(0); - const afterCallback: AfterCallback = (_req, _res, session: Session): Session => { - session.foo = 'bar'; - return session; - }; - const baseUrl = await setup(withApi, { callbackOptions: { afterCallback } }); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' - }, - baseUrl - ); - const { res } = await callback( - baseUrl, - { - state, - code: 'code' + const state = encodeState({ returnTo: 'https://example.com/foo' }); + const res = await getResponse({ + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') }, - cookieJar - ); - expect(res.statusCode).toBe(302); - const session = await get(baseUrl, '/api/session', { cookieJar }); - - expect(session).toMatchObject({ - foo: 'bar', - user: { - nickname: '__test_nickname__', - sub: '__test_sub__' + callbackOpts: { + afterCallback(_req: NextRequest, session: Session) { + return { ...session, foo: 'bar' }; + } } }); - timekeeper.reset(); + expect(res.status).toEqual(302); + expect(res.headers.get('location')).toEqual('https://example.com/foo'); + const session = await getSessionFromRes(withApi, res); + expect(session).toMatchObject({ + user: { sub: '__test_sub__' }, + foo: 'bar' + }); }); test('throws from afterCallback hook', async () => { - const afterCallback = (): Session => { - throw new Error('some validation error.'); - }; - const baseUrl = await setup(withApi, { callbackOptions: { afterCallback } }); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' - }, - baseUrl - ); + const state = encodeState({ returnTo: 'https://example.com/foo' }); await expect( - callback( - baseUrl, - { - state, - code: 'code' + getResponse({ + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') }, - cookieJar - ) - ).rejects.toThrow('some validation error.'); + callbackOpts: { + afterCallback() { + throw new Error('some validation error.'); + } + } + }) + ).resolves.toMatchObject({ status: 500, statusText: expect.stringMatching(/some validation error/) }); }); - test('throws for missing org_id claim', async () => { - const baseUrl = await setup({ ...withApi, organization: 'org_foo' }); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' + test('redirect from afterCallback hook', async () => { + const state = encodeState({ returnTo: 'https://example.com/foo' }); + const res = await getResponse({ + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') }, - baseUrl - ); + callbackOpts: { + afterCallback() { + return NextResponse.redirect('https://example.com/foo'); + } + } + }); + expect(res.status).toBe(302); + expect(res.headers.get('location')).toBe('https://example.com/foo'); + }); + + test('throws for missing org_id claim', async () => { + const state = encodeState({ returnTo: 'https://example.com/foo' }); await expect( - callback( - baseUrl, - { - state, - code: 'code' + getResponse({ + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') }, - cookieJar - ) - ).rejects.toThrow('Organization Id (org_id) claim must be a string present in the ID token'); + callbackOpts: { + organization: 'org_foo' + } + }) + ).resolves.toMatchObject({ + status: 500, + statusText: expect.stringMatching(/Organization Id \(org_id\) claim must be a string present in the ID token/) + }); }); test('throws for missing org_name claim', async () => { - const baseUrl = await setup({ ...withApi, organization: 'foo' }); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' - }, - baseUrl - ); + const state = encodeState({ returnTo: 'https://example.com/foo' }); await expect( - callback( - baseUrl, - { - state, - code: 'code' + getResponse({ + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') }, - cookieJar - ) - ).rejects.toThrow('Organization Name (org_name) claim must be a string present in the ID token'); + callbackOpts: { + organization: 'foo' + } + }) + ).resolves.toMatchObject({ + status: 500, + statusText: expect.stringMatching(/Organization Name \(org_name\) claim must be a string present in the ID token/) + }); }); test('throws for org_id claim mismatch', async () => { - const baseUrl = await setup({ ...withApi, organization: 'org_foo' }, { idTokenClaims: { org_id: 'org_bar' } }); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' - }, - baseUrl - ); + const state = encodeState({ returnTo: 'https://example.com/foo' }); await expect( - callback( - baseUrl, - { - state, - code: 'code' + getResponse({ + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') }, - cookieJar + callbackOpts: { + organization: 'org_foo' + }, + idTokenClaims: { org_id: 'org_bar' } + }) + ).resolves.toMatchObject({ + status: 500, + statusText: expect.stringMatching( + /Organization Id \(org_id\) claim value mismatch in the ID token; expected "org_foo", found "org_bar"/ ) - ).rejects.toThrow('Organization Id (org_id) claim value mismatch in the ID token; expected "org_foo", found "org_bar"'); + }); }); test('throws for org_name claim mismatch', async () => { - const baseUrl = await setup({ ...withApi, organization: 'foo' }, { idTokenClaims: { org_name: 'bar' } }); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' - }, - baseUrl - ); + const state = encodeState({ returnTo: 'https://example.com/foo' }); await expect( - callback( - baseUrl, - { - state, - code: 'code' + getResponse({ + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') }, - cookieJar + callbackOpts: { + organization: 'foo' + }, + idTokenClaims: { org_name: 'bar' } + }) + ).resolves.toMatchObject({ + status: 500, + statusText: expect.stringMatching( + /Organization Name \(org_name\) claim value mismatch in the ID token; expected "foo", found "bar"/ ) - ).rejects.toThrow('Organization Name (org_name) claim value mismatch in the ID token; expected "foo", found "bar"'); + }); }); test('accepts a valid organization id', async () => { - const baseUrl = await setup(withApi, { - idTokenClaims: { org_id: 'org_foo' }, - callbackOptions: { organization: 'org_foo' } - }); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' - }, - baseUrl - ); + const state = encodeState({ returnTo: 'https://example.com/foo' }); await expect( - callback( - baseUrl, - { - state, - code: 'code' + getResponse({ + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') }, - cookieJar - ) - ).resolves.not.toThrow(); - const session = await get(baseUrl, '/api/session', { cookieJar }); - - expect(session.user.org_id).toEqual('org_foo'); + callbackOpts: { + organization: 'org_foo' + }, + idTokenClaims: { org_id: 'org_foo' } + }) + ).resolves.toMatchObject({ + status: 302 + }); }); test('accepts a valid organization name', async () => { - const baseUrl = await setup(withApi, { - idTokenClaims: { org_name: 'foo' }, - callbackOptions: { organization: 'foo' } - }); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' - }, - baseUrl - ); + const state = encodeState({ returnTo: 'https://example.com/foo' }); await expect( - callback( - baseUrl, - { - state, - code: 'code' + getResponse({ + url: `/api/auth/callback?state=${state}&code=code`, + cookies: { + state: await signCookie('state', state), + nonce: await signCookie('nonce', '__test_nonce__'), + code_verifier: await signCookie('code_verifier', '__test_code_verifier__') }, - cookieJar - ) - ).resolves.not.toThrow(); - const session = await get(baseUrl, '/api/session', { cookieJar }); - - expect(session.user.org_name).toEqual('foo'); - }); - - test('should pass custom params to the token exchange', async () => { - const baseUrl = await setup(withoutApi, { - callbackOptions: { - authorizationParams: { foo: 'bar' } - } + callbackOpts: { + organization: 'foo' + }, + idTokenClaims: { org_name: 'foo' } + }) + ).resolves.toMatchObject({ + status: 302 }); - const state = encodeState({ returnTo: baseUrl }); - const cookieJar = await toSignedCookieJar( - { - state, - nonce: '__test_nonce__' - }, - baseUrl - ); - const spy = jest.fn(); - - nock(`${withoutApi.issuerBaseURL}`) - .post('/oauth/token', /grant_type=authorization_code/) - .reply(200, async (_, body) => { - spy(body); - return { - access_token: 'eyJz93a...k4laUWw', - expires_in: 750, - scope: 'read:foo delete:foo', - refresh_token: 'GEbRxBN...edjnXbL', - id_token: await makeIdToken({ iss: `${withoutApi.issuerBaseURL}/` }), - token_type: 'Bearer' - }; - }); - - const { res } = await callback( - baseUrl, - { - state, - code: 'foobar' - }, - cookieJar - ); - expect(res.statusCode).toBe(302); - expect(spy).toHaveBeenCalledWith(expect.stringContaining('foo=bar')); }); }); diff --git a/tests/handlers/login-page-router.test.ts b/tests/handlers/login-page-router.test.ts new file mode 100644 index 000000000..340b84ddf --- /dev/null +++ b/tests/handlers/login-page-router.test.ts @@ -0,0 +1,331 @@ +import { parse as urlParse } from 'url'; +import { withoutApi, withApi } from '../fixtures/default-settings'; +import { decodeState } from '../../src/auth0-session/utils/encoding'; +import { setup, teardown } from '../fixtures/setup'; +import { get, getCookie } from '../auth0-session/fixtures/helpers'; +import { Cookie, CookieJar } from 'tough-cookie'; + +describe('login handler (page router)', () => { + afterEach(teardown); + + test('should create a state, nonce, and code verifier', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = new CookieJar(); + await get(baseUrl, '/api/auth/login', { cookieJar }); + + expect(cookieJar.getCookiesSync(baseUrl)).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: 'nonce', + value: expect.any(String), + path: '/', + sameSite: 'lax' + }), + expect.objectContaining({ + key: 'state', + value: expect.any(String), + path: '/', + sameSite: 'lax' + }), + expect.objectContaining({ + key: 'code_verifier', + value: expect.any(String), + path: '/', + sameSite: 'lax' + }) + ]) + ); + }); + + test('should add returnTo to the state', async () => { + const baseUrl = await setup(withoutApi, { loginOptions: { returnTo: '/custom-url' } }); + const cookieJar = new CookieJar(); + await get(baseUrl, '/api/auth/login', { cookieJar }); + + const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + expect(state).toBeTruthy(); + + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState?.returnTo).toEqual('/custom-url'); + }); + + test('should redirect to the identity provider', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = new CookieJar(); + const { + res: { statusCode, headers } + } = await get(baseUrl, '/api/auth/login', { cookieJar, fullResponse: true }); + + expect(statusCode).toBe(302); + + const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + expect(urlParse(headers.location, true)).toMatchObject({ + protocol: 'https:', + host: 'acme.auth0.local', + hash: null, + query: { + client_id: '__test_client_id__', + scope: 'openid profile email', + response_type: 'code', + redirect_uri: 'http://www.acme.com/api/auth/callback', + nonce: expect.any(String), + state: state.split('.')[0], + code_challenge: expect.any(String), + code_challenge_method: 'S256' + }, + pathname: '/authorize' + }); + }); + + test('should allow sending custom parameters to the authorization server', async () => { + const loginOptions = { + authorizationParams: { + max_age: 123, + login_hint: 'foo@acme.com', + ui_locales: 'nl', + scope: 'some other scope openid', + foo: 'bar', + organization: 'foo', + invitation: 'bar' + } + }; + const baseUrl = await setup(withoutApi, { loginOptions }); + const cookieJar = new CookieJar(); + const { + res: { statusCode, headers } + } = await get(baseUrl, '/api/auth/login', { cookieJar, fullResponse: true }); + + expect(statusCode).toBe(302); + expect(urlParse(headers.location, true)).toMatchObject({ + query: { + ...loginOptions.authorizationParams, + max_age: '123' + } + }); + }); + + test('should pass organization config to the authorization server', async () => { + const baseUrl = await setup({ ...withoutApi, organization: 'foo' }); + const cookieJar = new CookieJar(); + const { + res: { statusCode, headers } + } = await get(baseUrl, '/api/auth/login', { cookieJar, fullResponse: true }); + + expect(statusCode).toBe(302); + expect(urlParse(headers.location, true)).toMatchObject({ + query: { + organization: 'foo' + } + }); + }); + + test('should prefer organization auth param to config', async () => { + const baseUrl = await setup( + { ...withoutApi, organization: 'foo' }, + { loginOptions: { authorizationParams: { organization: 'bar' } } } + ); + const cookieJar = new CookieJar(); + const { + res: { statusCode, headers } + } = await get(baseUrl, '/api/auth/login', { cookieJar, fullResponse: true }); + + expect(statusCode).toBe(302); + expect(urlParse(headers.location, true)).toMatchObject({ + query: { + organization: 'bar' + } + }); + }); + + test('should allow adding custom data to the state', async () => { + const loginOptions = { + getLoginState: (): Record => { + return { + foo: 'bar' + }; + } + }; + const baseUrl = await setup(withoutApi, { loginOptions }); + const cookieJar = new CookieJar(); + await get(baseUrl, '/api/auth/login', { cookieJar }); + + const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState).toEqual({ + foo: 'bar', + returnTo: 'http://www.acme.com/' + }); + }); + + test('should merge returnTo and state', async () => { + const loginOptions = { + returnTo: '/profile', + getLoginState: (): Record => { + return { + foo: 'bar' + }; + } + }; + const baseUrl = await setup(withoutApi, { loginOptions }); + const cookieJar = new CookieJar(); + await get(baseUrl, '/api/auth/login', { cookieJar }); + + const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState).toEqual({ + foo: 'bar', + returnTo: '/profile' + }); + }); + + test('should allow the getState method to overwrite returnTo', async () => { + const loginOptions = { + returnTo: '/profile', + getLoginState: (): Record => { + return { + foo: 'bar', + returnTo: '/foo' + }; + } + }; + const baseUrl = await setup(withoutApi, { loginOptions }); + const cookieJar = new CookieJar(); + await get(baseUrl, '/api/auth/login', { cookieJar }); + + const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState).toEqual({ + foo: 'bar', + returnTo: '/foo' + }); + }); + + test('should allow the returnTo url to be provided in the querystring', async () => { + const loginOptions = { + returnTo: '/profile' + }; + const baseUrl = await setup(withoutApi, { loginOptions }); + const cookieJar = new CookieJar(); + await get(baseUrl, '/api/auth/login?returnTo=/foo', { cookieJar }); + const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState).toEqual({ + returnTo: new URL('/foo', withoutApi.baseURL).toString() + }); + }); + + test('should take the first returnTo url provided in the querystring', async () => { + const loginOptions = { + returnTo: '/profile' + }; + const baseUrl = await setup(withoutApi, { loginOptions }); + const cookieJar = new CookieJar(); + await get(baseUrl, '/api/auth/login?returnTo=/foo&returnTo=/bar', { cookieJar }); + const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState).toEqual({ + returnTo: new URL('/foo', withoutApi.baseURL).toString() + }); + }); + + test('should not allow absolute urls to be provided in the querystring', async () => { + const loginOptions = { + returnTo: '/default-redirect' + }; + const baseUrl = await setup(withoutApi, { loginOptions }); + + const cookieJar = new CookieJar(); + await get(baseUrl, '/api/auth/login?returnTo=https://www.google.com', { cookieJar }); + const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState).toEqual({}); + }); + + test('should allow absolute urls in params of returnTo urls', async () => { + const loginOptions = { + returnTo: '/default-redirect' + }; + const baseUrl = await setup(withoutApi, { loginOptions }); + + const cookieJar = new CookieJar(); + await get(baseUrl, '/api/auth/login?returnTo=/foo?url=https://www.google.com', { cookieJar }); + const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState).toEqual({ + returnTo: new URL('/foo?url=https://www.google.com', withoutApi.baseURL).toString() + }); + }); + + test('should redirect relative to the redirect_uri over the base url', async () => { + const loginOptions = { + returnTo: '/default-redirect', + authorizationParams: { + redirect_uri: 'https://other-org.acme.com/api/auth/callback' + } + }; + const baseUrl = await setup(withoutApi, { loginOptions }); + + const cookieJar = new CookieJar(); + await get(baseUrl, '/api/auth/login?returnTo=/foo', { cookieJar }); + const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState).toEqual({ + returnTo: 'https://other-org.acme.com/foo' + }); + }); + + test('should allow the returnTo to be be overwritten by getState() when provided in the querystring', async () => { + const loginOptions = { + returnTo: '/profile', + getLoginState: (): Record => { + return { + returnTo: '/foo' + }; + } + }; + const baseUrl = await setup(withoutApi, { loginOptions }); + const cookieJar = new CookieJar(); + await get(baseUrl, '/api/auth/login', { cookieJar }); + const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState).toEqual({ + returnTo: '/foo' + }); + }); + + test('should redirect to the identity provider with scope and audience', async () => { + const baseUrl = await setup(withApi); + const { + res: { statusCode, headers } + } = await get(baseUrl, '/api/auth/login', { fullResponse: true }); + + expect(statusCode).toBe(302); + + expect(urlParse(headers.location, true).query).toMatchObject({ + scope: 'openid profile read:customer', + audience: 'https://api.acme.com' + }); + }); + + test('should handle login errors', async () => { + const baseUrl = await setup(withApi, { + loginOptions: { + getLoginState() { + return 1 as any; + } + } + }); + await expect(get(baseUrl, '/api/auth/login', { fullResponse: true })).rejects.toThrowError( + /Login handler failed. CAUSE: Custom state value must be an object/ + ); + }); +}); diff --git a/tests/handlers/login.test.ts b/tests/handlers/login.test.ts index 81ecfb3ce..1a77e6d74 100644 --- a/tests/handlers/login.test.ts +++ b/tests/handlers/login.test.ts @@ -1,71 +1,56 @@ +/** + * **REMOVE-TO-TEST-ON-EDGE**@jest-environment @edge-runtime/jest-environment + */ import { parse as urlParse } from 'url'; -import { withoutApi, withApi } from '../fixtures/default-settings'; import { decodeState } from '../../src/auth0-session/utils/encoding'; -import { setup, teardown } from '../fixtures/setup'; -import { get, getCookie } from '../auth0-session/fixtures/helpers'; -import { Cookie, CookieJar } from 'tough-cookie'; +import { getResponse, mockFetch } from '../fixtures/app-router-helpers'; -describe('login handler', () => { - afterEach(teardown); +describe('login handler (app router)', () => { + beforeEach(mockFetch); test('should create a state', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = new CookieJar(); - await get(baseUrl, '/api/auth/login', { cookieJar }); - - expect(cookieJar.getCookiesSync(baseUrl)).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - key: 'nonce', - value: expect.any(String), - path: '/', - sameSite: 'lax' - }), - expect.objectContaining({ - key: 'state', - value: expect.any(String), - path: '/', - sameSite: 'lax' - }), - expect.objectContaining({ - key: 'code_verifier', - value: expect.any(String), - path: '/', - sameSite: 'lax' - }) - ]) - ); + const res = await getResponse({ + url: '/api/auth/login' + }); + expect(res.cookies.get('nonce')).toMatchObject({ + value: expect.any(String), + path: '/', + sameSite: 'lax' + }); + expect(res.cookies.get('state')).toMatchObject({ + value: expect.any(String), + path: '/', + sameSite: 'lax' + }); + expect(res.cookies.get('code_verifier')).toMatchObject({ + value: expect.any(String), + path: '/', + sameSite: 'lax' + }); }); test('should add returnTo to the state', async () => { - const baseUrl = await setup(withoutApi, { loginOptions: { returnTo: '/custom-url' } }); - const cookieJar = new CookieJar(); - await get(baseUrl, '/api/auth/login', { cookieJar }); - - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; - expect(state).toBeTruthy(); - + const res = await getResponse({ + url: '/api/auth/login', + loginOpts: { returnTo: '/custom-url' } + }); + const { value: state } = res.cookies.get('state'); const decodedState = decodeState(state.split('.')[0]); expect(decodedState?.returnTo).toEqual('/custom-url'); }); test('should redirect to the identity provider', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = new CookieJar(); - const { - res: { statusCode, headers } - } = await get(baseUrl, '/api/auth/login', { cookieJar, fullResponse: true }); - - expect(statusCode).toBe(302); - - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; - expect(urlParse(headers.location, true)).toMatchObject({ + const res = await getResponse({ + url: '/api/auth/login' + }); + const { value: state } = res.cookies.get('state'); + expect(urlParse(res.headers.get('location'), true)).toMatchObject({ protocol: 'https:', host: 'acme.auth0.local', hash: null, query: { client_id: '__test_client_id__', - scope: 'openid profile email', + scope: 'openid profile read:customer', response_type: 'code', redirect_uri: 'http://www.acme.com/api/auth/callback', nonce: expect.any(String), @@ -78,7 +63,7 @@ describe('login handler', () => { }); test('should allow sending custom parameters to the authorization server', async () => { - const loginOptions = { + const loginOpts = { authorizationParams: { max_age: 123, login_hint: 'foo@acme.com', @@ -89,68 +74,52 @@ describe('login handler', () => { invitation: 'bar' } }; - const baseUrl = await setup(withoutApi, { loginOptions }); - const cookieJar = new CookieJar(); - const { - res: { statusCode, headers } - } = await get(baseUrl, '/api/auth/login', { cookieJar, fullResponse: true }); - - expect(statusCode).toBe(302); - expect(urlParse(headers.location, true)).toMatchObject({ + const res = await getResponse({ + url: '/api/auth/login', + loginOpts + }); + expect(res.status).toBe(302); + expect(urlParse(res.headers.get('location'), true)).toMatchObject({ query: { - ...loginOptions.authorizationParams, + ...loginOpts.authorizationParams, max_age: '123' } }); }); test('should pass organization config to the authorization server', async () => { - const baseUrl = await setup({ ...withoutApi, organization: 'foo' }); - const cookieJar = new CookieJar(); - const { - res: { statusCode, headers } - } = await get(baseUrl, '/api/auth/login', { cookieJar, fullResponse: true }); - - expect(statusCode).toBe(302); - expect(urlParse(headers.location, true)).toMatchObject({ - query: { - organization: 'foo' - } + const res = await getResponse({ + url: '/api/auth/login', + config: { organization: 'foo' } + }); + expect(res.status).toBe(302); + expect(urlParse(res.headers.get('location'), true).query).toMatchObject({ + organization: 'foo' }); }); test('should prefer organization auth param to config', async () => { - const baseUrl = await setup( - { ...withoutApi, organization: 'foo' }, - { loginOptions: { authorizationParams: { organization: 'bar' } } } - ); - const cookieJar = new CookieJar(); - const { - res: { statusCode, headers } - } = await get(baseUrl, '/api/auth/login', { cookieJar, fullResponse: true }); - - expect(statusCode).toBe(302); - expect(urlParse(headers.location, true)).toMatchObject({ - query: { - organization: 'bar' - } + const res = await getResponse({ + url: '/api/auth/login', + config: { organization: 'foo' }, + loginOpts: { authorizationParams: { organization: 'bar' } } + }); + expect(res.status).toBe(302); + expect(urlParse(res.headers.get('location'), true).query).toMatchObject({ + organization: 'bar' }); }); test('should allow adding custom data to the state', async () => { - const loginOptions = { - getLoginState: (): Record => { - return { - foo: 'bar' - }; + const res = await getResponse({ + url: '/api/auth/login', + loginOpts: { + getLoginState() { + return { foo: 'bar' }; + } } - }; - const baseUrl = await setup(withoutApi, { loginOptions }); - const cookieJar = new CookieJar(); - await get(baseUrl, '/api/auth/login', { cookieJar }); - - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; - + }); + const { value: state } = res.cookies.get('state'); const decodedState = decodeState(state.split('.')[0]); expect(decodedState).toEqual({ foo: 'bar', @@ -159,20 +128,16 @@ describe('login handler', () => { }); test('should merge returnTo and state', async () => { - const loginOptions = { - returnTo: '/profile', - getLoginState: (): Record => { - return { - foo: 'bar' - }; + const res = await getResponse({ + url: '/api/auth/login', + loginOpts: { + returnTo: '/profile', + getLoginState() { + return { foo: 'bar' }; + } } - }; - const baseUrl = await setup(withoutApi, { loginOptions }); - const cookieJar = new CookieJar(); - await get(baseUrl, '/api/auth/login', { cookieJar }); - - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; - + }); + const { value: state } = res.cookies.get('state'); const decodedState = decodeState(state.split('.')[0]); expect(decodedState).toEqual({ foo: 'bar', @@ -181,138 +146,111 @@ describe('login handler', () => { }); test('should allow the getState method to overwrite returnTo', async () => { - const loginOptions = { - returnTo: '/profile', - getLoginState: (): Record => { - return { - foo: 'bar', - returnTo: '/foo' - }; + const res = await getResponse({ + url: '/api/auth/login', + loginOpts: { + returnTo: '/profile', + getLoginState() { + return { foo: 'bar', returnTo: '/bar' }; + } } - }; - const baseUrl = await setup(withoutApi, { loginOptions }); - const cookieJar = new CookieJar(); - await get(baseUrl, '/api/auth/login', { cookieJar }); - - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; - + }); + const { value: state } = res.cookies.get('state'); const decodedState = decodeState(state.split('.')[0]); expect(decodedState).toEqual({ foo: 'bar', - returnTo: '/foo' + returnTo: '/bar' }); }); test('should allow the returnTo url to be provided in the querystring', async () => { - const loginOptions = { - returnTo: '/profile' - }; - const baseUrl = await setup(withoutApi, { loginOptions }); - const cookieJar = new CookieJar(); - await get(baseUrl, '/api/auth/login?returnTo=/foo', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; - - const decodedState = decodeState(state.split('.')[0]); - expect(decodedState).toEqual({ - returnTo: new URL('/foo', withoutApi.baseURL).toString() + const res = await getResponse({ + url: '/api/auth/login?returnTo=/from-query' }); + const { value: state } = res.cookies.get('state'); + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState?.returnTo).toEqual('http://www.acme.com/from-query'); }); test('should take the first returnTo url provided in the querystring', async () => { - const loginOptions = { - returnTo: '/profile' - }; - const baseUrl = await setup(withoutApi, { loginOptions }); - const cookieJar = new CookieJar(); - await get(baseUrl, '/api/auth/login?returnTo=/foo&returnTo=/bar', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; - - const decodedState = decodeState(state.split('.')[0]); - expect(decodedState).toEqual({ - returnTo: new URL('/foo', withoutApi.baseURL).toString() + const res = await getResponse({ + url: '/api/auth/login?returnTo=/foo&returnTo=bar' }); + const { value: state } = res.cookies.get('state'); + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState?.returnTo).toEqual('http://www.acme.com/foo'); }); test('should not allow absolute urls to be provided in the querystring', async () => { - const loginOptions = { - returnTo: '/default-redirect' - }; - const baseUrl = await setup(withoutApi, { loginOptions }); - - const cookieJar = new CookieJar(); - await get(baseUrl, '/api/auth/login?returnTo=https://www.google.com', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; - + const res = await getResponse({ + url: '/api/auth/login?returnTo=https://evil.com' + }); + const { value: state } = res.cookies.get('state'); const decodedState = decodeState(state.split('.')[0]); - expect(decodedState).toEqual({}); + expect(decodedState?.returnTo).toBeUndefined(); }); test('should allow absolute urls in params of returnTo urls', async () => { - const loginOptions = { - returnTo: '/default-redirect' - }; - const baseUrl = await setup(withoutApi, { loginOptions }); - - const cookieJar = new CookieJar(); - await get(baseUrl, '/api/auth/login?returnTo=/foo?url=https://www.google.com', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; - - const decodedState = decodeState(state.split('.')[0]); - expect(decodedState).toEqual({ - returnTo: new URL('/foo?url=https://www.google.com', withoutApi.baseURL).toString() + const res = await getResponse({ + url: '/api/auth/login', + loginOpts: { returnTo: 'https://google.com' } }); + const { value: state } = res.cookies.get('state'); + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState?.returnTo).toBe('https://google.com'); }); test('should redirect relative to the redirect_uri over the base url', async () => { - const loginOptions = { - returnTo: '/default-redirect', + const loginOpts = { authorizationParams: { redirect_uri: 'https://other-org.acme.com/api/auth/callback' } }; - const baseUrl = await setup(withoutApi, { loginOptions }); - - const cookieJar = new CookieJar(); - await get(baseUrl, '/api/auth/login?returnTo=/foo', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; - - const decodedState = decodeState(state.split('.')[0]); - expect(decodedState).toEqual({ - returnTo: 'https://other-org.acme.com/foo' + const res = await getResponse({ + url: '/api/auth/login?returnTo=/bar', + loginOpts }); + const { value: state } = res.cookies.get('state'); + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState?.returnTo).toBe('https://other-org.acme.com/bar'); }); test('should allow the returnTo to be be overwritten by getState() when provided in the querystring', async () => { - const loginOptions = { - returnTo: '/profile', - getLoginState: (): Record => { - return { - returnTo: '/foo' - }; + const res = await getResponse({ + url: '/api/auth/login?returnTo=/foo', + loginOpts: { + getLoginState() { + return { returnTo: '/bar' }; + } } - }; - const baseUrl = await setup(withoutApi, { loginOptions }); - const cookieJar = new CookieJar(); - await get(baseUrl, '/api/auth/login', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; - - const decodedState = decodeState(state.split('.')[0]); - expect(decodedState).toEqual({ - returnTo: '/foo' }); + const { value: state } = res.cookies.get('state'); + const decodedState = decodeState(state.split('.')[0]); + expect(decodedState?.returnTo).toBe('/bar'); }); test('should redirect to the identity provider with scope and audience', async () => { - const baseUrl = await setup(withApi); - const { - res: { statusCode, headers } - } = await get(baseUrl, '/api/auth/login', { fullResponse: true }); - - expect(statusCode).toBe(302); + const res = await getResponse({ + config: { authorizationParams: { scope: 'openid profile foobar', audience: 'https://api.acme.com/foo' } }, + url: '/api/auth/login' + }); + expect(res.status).toBe(302); + expect(urlParse(res.headers.get('location'), true).query).toMatchObject({ + scope: 'openid profile foobar', + audience: 'https://api.acme.com/foo' + }); + }); - expect(urlParse(headers.location, true).query).toMatchObject({ - scope: 'openid profile read:customer', - audience: 'https://api.acme.com' + test('should handle login errors', async () => { + const res = await getResponse({ + loginOpts: { + getLoginState() { + return 1 as any; + } + }, + url: '/api/auth/login' }); + expect(res.status).toBe(500); + expect(res.statusText).toMatch(/Login handler failed. CAUSE: Custom state value must be an object/); }); }); diff --git a/tests/handlers/logout-page-router.test.ts b/tests/handlers/logout-page-router.test.ts new file mode 100644 index 000000000..d595c72f5 --- /dev/null +++ b/tests/handlers/logout-page-router.test.ts @@ -0,0 +1,152 @@ +import { parse } from 'cookie'; +import { parse as parseUrl } from 'url'; +import { withoutApi } from '../fixtures/default-settings'; +import { get } from '../auth0-session/fixtures/helpers'; +import { setup, teardown, login } from '../fixtures/setup'; +import { ServerResponse } from 'http'; + +describe('logout handler (page router)', () => { + afterEach(teardown); + + test('should redirect to auth0', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = await login(baseUrl); + + const { + res: { statusCode, headers } + } = await get(baseUrl, '/api/auth/logout', { + cookieJar, + fullResponse: true + }); + + expect(statusCode).toBe(302); + expect(parseUrl(headers['location'], true)).toMatchObject({ + protocol: 'https:', + host: 'acme.auth0.local', + query: { + returnTo: 'http://www.acme.com', + client_id: '__test_client_id__' + }, + pathname: '/v2/logout' + }); + }); + + test('should pass logout params to auth0', async () => { + const baseUrl = await setup(withoutApi, { logoutOptions: { logoutParams: { foo: 'bar' } } }); + const cookieJar = await login(baseUrl); + + const { + res: { statusCode, headers } + } = await get(baseUrl, '/api/auth/logout', { + cookieJar, + fullResponse: true + }); + + expect(statusCode).toBe(302); + expect(parseUrl(headers['location'], true)).toMatchObject({ + protocol: 'https:', + host: 'acme.auth0.local', + query: { + returnTo: 'http://www.acme.com', + client_id: '__test_client_id__', + foo: 'bar' + }, + pathname: '/v2/logout' + }); + }); + + test('should return to the custom path', async () => { + const customReturnTo = 'https://www.foo.bar'; + const baseUrl = await setup(withoutApi, { + logoutOptions: { returnTo: customReturnTo } + }); + const cookieJar = await login(baseUrl); + + const { + res: { statusCode, headers } + } = await get(baseUrl, '/api/auth/logout', { + cookieJar, + fullResponse: true + }); + + expect(statusCode).toBe(302); + expect(parseUrl(headers['location'], true).query).toMatchObject({ + returnTo: 'https://www.foo.bar' + }); + }); + + test('should use end_session_endpoint when configured', async () => { + const baseUrl = await setup( + { ...withoutApi, auth0Logout: false }, + { + discoveryOptions: { end_session_endpoint: 'https://my-end-session-endpoint/logout' } + } + ); + const cookieJar = await login(baseUrl); + + const { + res: { statusCode, headers } + } = await get(baseUrl, '/api/auth/logout', { + cookieJar, + fullResponse: true + }); + + expect(statusCode).toBe(302); + expect(parseUrl(headers['location'])).toMatchObject({ + host: 'my-end-session-endpoint', + pathname: '/logout' + }); + }); + + test('should use auth0 logout by default even when end_session_endpoint is discovered', async () => { + const baseUrl = await setup(withoutApi, { + discoveryOptions: { end_session_endpoint: 'https://my-end-session-endpoint/logout' } + }); + const cookieJar = await login(baseUrl); + + const { + res: { statusCode, headers } + } = await get(baseUrl, '/api/auth/logout', { + cookieJar, + fullResponse: true + }); + + expect(statusCode).toBe(302); + expect(parseUrl(headers['location'])).toMatchObject({ + host: 'acme.auth0.local', + pathname: '/v2/logout' + }); + }); + + test('should delete the session', async () => { + const baseUrl = await setup(withoutApi, { + discoveryOptions: { end_session_endpoint: 'https://my-end-session-endpoint/logout' } + }); + const cookieJar = await login(baseUrl); + + const { + res: { headers } + } = await get(baseUrl, '/api/auth/logout', { + cookieJar, + fullResponse: true + }); + + expect(parse(headers['set-cookie'][0])).toMatchObject({ + appSession: '', + 'Max-Age': '0', + Path: '/' + }); + }); + + test('should delete tshe session', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = await login(baseUrl); + + jest.spyOn(ServerResponse.prototype, 'writeHead').mockImplementationOnce(() => { + throw new Error('write err'); + }); + await expect(get(baseUrl, '/api/auth/logout', { cookieJar })).rejects.toThrowError( + /Logout handler failed. CAUSE: write err/ + ); + }); +}); diff --git a/tests/handlers/logout.test.ts b/tests/handlers/logout.test.ts index b043e7dff..4949ac07d 100644 --- a/tests/handlers/logout.test.ts +++ b/tests/handlers/logout.test.ts @@ -1,25 +1,21 @@ -import { parse } from 'cookie'; +/** + * **REMOVE-TO-TEST-ON-EDGE**@jest-environment @edge-runtime/jest-environment + */ import { parse as parseUrl } from 'url'; -import { withoutApi } from '../fixtures/default-settings'; -import { get } from '../auth0-session/fixtures/helpers'; -import { setup, teardown, login } from '../fixtures/setup'; +import { withApi } from '../fixtures/default-settings'; +import { getResponse, login as appRouterLogin, mockFetch } from '../fixtures/app-router-helpers'; +import nock from 'nock'; +import { discovery } from '../fixtures/oidc-nocks'; -describe('logout handler', () => { - afterEach(teardown); +describe('logout handler (app router)', () => { + beforeEach(mockFetch); test('should redirect to auth0', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = await login(baseUrl); - - const { - res: { statusCode, headers } - } = await get(baseUrl, '/api/auth/logout', { - cookieJar, - fullResponse: true - }); - - expect(statusCode).toBe(302); - expect(parseUrl(headers['location'], true)).toMatchObject({ + const loginRes = await appRouterLogin(); + const cookies = { appSession: loginRes.cookies.get('appSession').value }; + const res = await getResponse({ url: '/api/auth/logout', cookies }); + expect(res.status).toBe(302); + expect(parseUrl(res.headers.get('location'), true)).toMatchObject({ protocol: 'https:', host: 'acme.auth0.local', query: { @@ -31,109 +27,82 @@ describe('logout handler', () => { }); test('should pass logout params to auth0', async () => { - const baseUrl = await setup(withoutApi, { logoutOptions: { logoutParams: { foo: 'bar' } } }); - const cookieJar = await login(baseUrl); - - const { - res: { statusCode, headers } - } = await get(baseUrl, '/api/auth/logout', { - cookieJar, - fullResponse: true + const loginRes = await appRouterLogin(); + const cookies = { appSession: loginRes.cookies.get('appSession').value }; + const res = await getResponse({ + url: '/api/auth/logout', + cookies, + logoutOpts: { logoutParams: { foo: 'bar' } } }); - - expect(statusCode).toBe(302); - expect(parseUrl(headers['location'], true)).toMatchObject({ - protocol: 'https:', - host: 'acme.auth0.local', - query: { - returnTo: 'http://www.acme.com', - client_id: '__test_client_id__', - foo: 'bar' - }, - pathname: '/v2/logout' + expect(res.status).toBe(302); + expect(parseUrl(res.headers.get('location'), true).query).toMatchObject({ + returnTo: 'http://www.acme.com', + client_id: '__test_client_id__', + foo: 'bar' }); }); test('should return to the custom path', async () => { - const customReturnTo = 'https://www.foo.bar'; - const baseUrl = await setup(withoutApi, { - logoutOptions: { returnTo: customReturnTo } + const loginRes = await appRouterLogin(); + const cookies = { appSession: loginRes.cookies.get('appSession').value }; + const res = await getResponse({ + url: '/api/auth/logout', + cookies, + logoutOpts: { returnTo: 'https://www.google.com' } }); - const cookieJar = await login(baseUrl); - - const { - res: { statusCode, headers } - } = await get(baseUrl, '/api/auth/logout', { - cookieJar, - fullResponse: true - }); - - expect(statusCode).toBe(302); - expect(parseUrl(headers['location'], true).query).toMatchObject({ - returnTo: 'https://www.foo.bar' + expect(res.status).toBe(302); + expect(parseUrl(res.headers.get('location'), true).query).toMatchObject({ + returnTo: 'https://www.google.com' }); }); test('should use end_session_endpoint when configured', async () => { - const baseUrl = await setup( - { ...withoutApi, auth0Logout: false }, - { - discoveryOptions: { end_session_endpoint: 'https://my-end-session-endpoint/logout' } - } - ); - const cookieJar = await login(baseUrl); - - const { - res: { statusCode, headers } - } = await get(baseUrl, '/api/auth/logout', { - cookieJar, - fullResponse: true + const loginRes = await appRouterLogin(); + const cookies = { appSession: loginRes.cookies.get('appSession').value }; + const res = await getResponse({ + url: '/api/auth/logout', + cookies, + config: { auth0Logout: false }, + discoveryOptions: { end_session_endpoint: 'https://my-end-session-endpoint/logout' } }); - - expect(statusCode).toBe(302); - expect(parseUrl(headers['location'])).toMatchObject({ + expect(res.status).toBe(302); + expect(parseUrl(res.headers.get('location'))).toMatchObject({ host: 'my-end-session-endpoint', pathname: '/logout' }); }); test('should use auth0 logout by default even when end_session_endpoint is discovered', async () => { - const baseUrl = await setup(withoutApi, { + const loginRes = await appRouterLogin(); + const cookies = { appSession: loginRes.cookies.get('appSession').value }; + const res = await getResponse({ + url: '/api/auth/logout', + cookies, discoveryOptions: { end_session_endpoint: 'https://my-end-session-endpoint/logout' } }); - const cookieJar = await login(baseUrl); - - const { - res: { statusCode, headers } - } = await get(baseUrl, '/api/auth/logout', { - cookieJar, - fullResponse: true - }); - - expect(statusCode).toBe(302); - expect(parseUrl(headers['location'])).toMatchObject({ + expect(res.status).toBe(302); + expect(parseUrl(res.headers.get('location'))).toMatchObject({ host: 'acme.auth0.local', pathname: '/v2/logout' }); }); test('should delete the session', async () => { - const baseUrl = await setup(withoutApi, { - discoveryOptions: { end_session_endpoint: 'https://my-end-session-endpoint/logout' } - }); - const cookieJar = await login(baseUrl); + const loginRes = await appRouterLogin(); + const cookies = { appSession: loginRes.cookies.get('appSession').value }; + const res = await getResponse({ url: '/api/auth/logout', cookies }); + expect(res.status).toBe(302); + expect(new Date(res.cookies.get('appSession').expires).getTime()).toBe(0); + }); - const { - res: { headers } - } = await get(baseUrl, '/api/auth/logout', { - cookieJar, - fullResponse: true - }); + test('should handle logout errors', async () => { + const loginRes = await appRouterLogin(); + const cookies = { appSession: loginRes.cookies.get('appSession').value }; + nock.cleanAll(); + discovery(withApi, { error: true }); - expect(parse(headers['set-cookie'][0])).toMatchObject({ - appSession: '', - 'Max-Age': '0', - Path: '/' - }); + const res = await getResponse({ url: '/api/auth/logout', cookies, clearNock: false }); + expect(res.status).toBe(500); + expect(res.statusText).toMatch(/Logout handler failed. CAUSE: Discovery requests failing/); }); }); diff --git a/tests/handlers/profile-page-router.test.ts b/tests/handlers/profile-page-router.test.ts new file mode 100644 index 000000000..4e7fdc10b --- /dev/null +++ b/tests/handlers/profile-page-router.test.ts @@ -0,0 +1,148 @@ +import nock from 'nock'; +import { withApi, withoutApi } from '../fixtures/default-settings'; +import { refreshTokenRotationExchange, userInfo } from '../fixtures/oidc-nocks'; +import { get } from '../auth0-session/fixtures/helpers'; +import { setup, teardown, login } from '../fixtures/setup'; +import { Session, AfterCallbackPageRoute } from '../../src'; +import { makeIdToken } from '../auth0-session/fixtures/cert'; + +describe('profile handler (page router)', () => { + afterEach(teardown); + + test('should throw an error when not logged in', async () => { + const baseUrl = await setup(withoutApi); + + await expect(get(baseUrl, '/api/auth/me')).resolves.toBe(''); + }); + + test('should return the profile when logged in', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = await login(baseUrl); + + const profile = await get(baseUrl, '/api/auth/me', { cookieJar }); + expect(profile).toStrictEqual({ nickname: '__test_nickname__', sub: '__test_sub__' }); + }); + + test('should not allow caching the profile response', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = await login(baseUrl); + + const { res } = await get(baseUrl, '/api/auth/me', { cookieJar, fullResponse: true }); + expect(res.headers['cache-control']).toEqual('no-store'); + }); + + test('should not allow caching the profile response when refetch is true', async () => { + const baseUrl = await setup(withoutApi, { profileOptions: { refetch: true } }); + const cookieJar = await login(baseUrl); + + const { res } = await get(baseUrl, '/api/auth/me', { cookieJar, fullResponse: true }); + expect(res.headers['cache-control']).toEqual('no-store'); + }); + + test('should throw if re-fetching with no access token', async () => { + const afterCallback: AfterCallbackPageRoute = (_req, _res, session: Session): Session => { + delete session.accessToken; + return session; + }; + const baseUrl = await setup(withoutApi, { + profileOptions: { refetch: true }, + callbackOptions: { afterCallback } + }); + const cookieJar = await login(baseUrl); + + await expect(get(baseUrl, '/api/auth/me', { cookieJar })).rejects.toThrow( + 'The user does not have a valid access token.' + ); + }); + + test('should refetch the user and update the session', async () => { + const baseUrl = await setup(withoutApi, { profileOptions: { refetch: true }, userInfoPayload: { foo: 'bar' } }); + const cookieJar = await login(baseUrl); + + const profile = await get(baseUrl, '/api/auth/me', { cookieJar }); + expect(profile).toMatchObject({ foo: 'bar', nickname: '__test_nickname__', sub: '__test_sub__' }); + // check that the session is saved + userInfo(withoutApi, 'eyJz93a...k4laUWw', {}); + const profile2 = await get(baseUrl, '/api/auth/me', { cookieJar }); + expect(profile2).toMatchObject({ foo: 'bar', nickname: '__test_nickname__', sub: '__test_sub__' }); + }); + + test("should refetch the user and fail if it can't get an access token", async () => { + const afterCallback: AfterCallbackPageRoute = (_req, _res, session: Session): Session => { + session.accessTokenExpiresAt = -60; + return session; + }; + const baseUrl = await setup(withoutApi, { + profileOptions: { refetch: true }, + userInfoPayload: { foo: 'bar' }, + callbackOptions: { + afterCallback + } + }); + const cookieJar = await login(baseUrl); + + nock(`${withoutApi.issuerBaseURL}`) + .post('/oauth/token', `grant_type=refresh_token&refresh_token=GEbRxBN...edjnXbL`) + .reply(200, { + id_token: await makeIdToken({ iss: 'https://acme.auth0.local/' }), + token_type: 'Bearer', + expires_in: 750, + scope: 'read:foo write:foo' + }); + await expect(get(baseUrl, '/api/auth/me', { cookieJar })).rejects.toThrow( + 'No access token available to refetch the profile' + ); + }); + + test('should refetch the user and preserve new tokens', async () => { + const afterCallback: AfterCallbackPageRoute = (_req, _res, session: Session): Session => { + session.accessTokenExpiresAt = -60; + return session; + }; + const baseUrl = await setup(withApi, { + profileOptions: { refetch: true }, + userInfoPayload: { foo: 'bar' }, + callbackOptions: { + afterCallback + }, + userInfoToken: 'new-access-token' + }); + await refreshTokenRotationExchange(withApi, 'GEbRxBN...edjnXbL', {}, 'new-access-token', 'new-refresh-token'); + const cookieJar = await login(baseUrl); + const profile = await get(baseUrl, '/api/auth/me', { cookieJar }); + expect(profile).toMatchObject({ foo: 'bar' }); + const session = await get(baseUrl, '/api/session', { cookieJar }); + expect(session.accessToken).toEqual('new-access-token'); + expect(session.refreshToken).toEqual('new-refresh-token'); + }); + + test('should update the session in the afterRefetch hook', async () => { + const baseUrl = await setup(withoutApi, { + profileOptions: { + refetch: true, + afterRefetch(_req, _res, session) { + session.user.foo = 'bar'; + return session; + } + } + }); + const cookieJar = await login(baseUrl); + + const user = await get(baseUrl, '/api/auth/me', { cookieJar }); + expect(user.foo).toEqual('bar'); + }); + + test('should throw from the afterRefetch hook', async () => { + const baseUrl = await setup(withoutApi, { + profileOptions: { + refetch: true, + afterRefetch() { + throw new Error('some validation error'); + } + } + }); + const cookieJar = await login(baseUrl); + + await expect(get(baseUrl, '/api/auth/me', { cookieJar })).rejects.toThrowError('some validation error'); + }); +}); diff --git a/tests/handlers/profile.test.ts b/tests/handlers/profile.test.ts index 90fad9e56..d253510a4 100644 --- a/tests/handlers/profile.test.ts +++ b/tests/handlers/profile.test.ts @@ -1,145 +1,181 @@ +/** + * **REMOVE-TO-TEST-ON-EDGE**@jest-environment @edge-runtime/jest-environment + */ import nock from 'nock'; -import { withApi, withoutApi } from '../fixtures/default-settings'; -import { refreshTokenRotationExchange, userInfo } from '../fixtures/oidc-nocks'; -import { get } from '../auth0-session/fixtures/helpers'; -import { setup, teardown, login } from '../fixtures/setup'; -import { Session, AfterCallback } from '../../src'; +import { withApi } from '../fixtures/default-settings'; +import { refreshTokenRotationExchange } from '../fixtures/oidc-nocks'; +import { Session } from '../../src'; import { makeIdToken } from '../auth0-session/fixtures/cert'; - -describe('profile handler', () => { - afterEach(teardown); - - test('should throw an error when not logged in', async () => { - const baseUrl = await setup(withoutApi); - - await expect(get(baseUrl, '/api/auth/me')).resolves.toBe(''); +import { + getResponse, + login as appRouterLogin, + getSession as appRouterGetSession, + mockFetch +} from '../fixtures/app-router-helpers'; +import { NextRequest } from 'next/server'; + +describe('profile handler (app router)', () => { + beforeEach(mockFetch); + + test('should be empty when not logged in', async () => { + await expect(getResponse({ url: '/api/auth/me' })).resolves.toMatchObject({ status: 204 }); }); test('should return the profile when logged in', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = await login(baseUrl); - - const profile = await get(baseUrl, '/api/auth/me', { cookieJar }); - expect(profile).toStrictEqual({ nickname: '__test_nickname__', sub: '__test_sub__' }); + const loginRes = await appRouterLogin(); + const res = await getResponse({ + url: '/api/auth/me', + cookies: { appSession: loginRes.cookies.get('appSession').value } + }); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ nickname: '__test_nickname__', sub: '__test_sub__' }); }); test('should not allow caching the profile response', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = await login(baseUrl); - - const { res } = await get(baseUrl, '/api/auth/me', { cookieJar, fullResponse: true }); - expect(res.headers['cache-control']).toEqual('no-store'); + const loginRes = await appRouterLogin(); + const res = await getResponse({ + url: '/api/auth/me', + cookies: { appSession: loginRes.cookies.get('appSession').value } + }); + expect(res.headers.get('cache-control')).toBe('no-store'); }); test('should not allow caching the profile response when refetch is true', async () => { - const baseUrl = await setup(withoutApi, { profileOptions: { refetch: true } }); - const cookieJar = await login(baseUrl); - - const { res } = await get(baseUrl, '/api/auth/me', { cookieJar, fullResponse: true }); - expect(res.headers['cache-control']).toEqual('no-store'); + const loginRes = await appRouterLogin(); + const res = await getResponse({ + url: '/api/auth/me', + cookies: { appSession: loginRes.cookies.get('appSession').value }, + profileOpts: { refetch: true }, + userInfoPayload: { sub: 'foo' } + }); + expect(res.headers.get('cache-control')).toBe('no-store'); }); test('should throw if re-fetching with no access token', async () => { - const afterCallback: AfterCallback = (_req, _res, session: Session): Session => { - delete session.accessToken; - return session; - }; - const baseUrl = await setup(withoutApi, { profileOptions: { refetch: true }, callbackOptions: { afterCallback } }); - const cookieJar = await login(baseUrl); - - await expect(get(baseUrl, '/api/auth/me', { cookieJar })).rejects.toThrow( - 'The user does not have a valid access token.' - ); + const loginRes = await appRouterLogin({ + callbackOpts: { + afterCallback(_req: any, session: any): Session { + delete session.accessToken; + return session; + } + } + }); + const res = await getResponse({ + url: '/api/auth/me', + cookies: { appSession: loginRes.cookies.get('appSession').value }, + profileOpts: { refetch: true } + }); + expect(res.status).toBe(500); + expect(res.statusText).toMatch(/The user does not have a valid access token/); }); test('should refetch the user and update the session', async () => { - const baseUrl = await setup(withoutApi, { profileOptions: { refetch: true }, userInfoPayload: { foo: 'bar' } }); - const cookieJar = await login(baseUrl); - - const profile = await get(baseUrl, '/api/auth/me', { cookieJar }); - expect(profile).toMatchObject({ foo: 'bar', nickname: '__test_nickname__', sub: '__test_sub__' }); - // check that the session is saved - userInfo(withoutApi, 'eyJz93a...k4laUWw', {}); - const profile2 = await get(baseUrl, '/api/auth/me', { cookieJar }); - expect(profile2).toMatchObject({ foo: 'bar', nickname: '__test_nickname__', sub: '__test_sub__' }); + const loginRes = await appRouterLogin(); + const res = await getResponse({ + url: '/api/auth/me', + cookies: { appSession: loginRes.cookies.get('appSession').value }, + profileOpts: { refetch: true }, + userInfoPayload: { foo: 'bar', sub: 'foo' } + }); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ foo: 'bar' }); }); test("should refetch the user and fail if it can't get an access token", async () => { - const afterCallback: AfterCallback = (_req, _res, session: Session): Session => { - session.accessTokenExpiresAt = -60; - return session; - }; - const baseUrl = await setup(withoutApi, { - profileOptions: { refetch: true }, - userInfoPayload: { foo: 'bar' }, - callbackOptions: { - afterCallback + const loginRes = await appRouterLogin({ + callbackOpts: { + afterCallback(_req: NextRequest, session: Session) { + session.accessTokenExpiresAt = -60; + return session; + } } }); - const cookieJar = await login(baseUrl); - - nock(`${withoutApi.issuerBaseURL}`) - .post('/oauth/token', `grant_type=refresh_token&refresh_token=GEbRxBN...edjnXbL`) + nock.cleanAll(); + nock(`${withApi.issuerBaseURL}`) + .post('/oauth/token', (body) => { + //`grant_type=refresh_token&refresh_token=GEbRxBN...edjnXbL` + return body.grant_type === 'refresh_token' && body.refresh_token === 'GEbRxBN...edjnXbL'; + }) .reply(200, { id_token: await makeIdToken({ iss: 'https://acme.auth0.local/' }), token_type: 'Bearer', expires_in: 750, scope: 'read:foo write:foo' }); - await expect(get(baseUrl, '/api/auth/me', { cookieJar })).rejects.toThrow( - 'No access token available to refetch the profile' - ); - }); - test('should refetch the user and preserve new tokens', async () => { - const afterCallback: AfterCallback = (_req, _res, session: Session): Session => { - session.accessTokenExpiresAt = -60; - return session; - }; - const baseUrl = await setup(withApi, { - profileOptions: { refetch: true }, + const res = await getResponse({ + url: '/api/auth/me', + cookies: { appSession: loginRes.cookies.get('appSession').value }, + profileOpts: { refetch: true }, userInfoPayload: { foo: 'bar' }, - callbackOptions: { - afterCallback - }, + clearNock: false, userInfoToken: 'new-access-token' }); - await refreshTokenRotationExchange(withApi, 'GEbRxBN...edjnXbL', {}, 'new-access-token', 'new-refresh-token'); - const cookieJar = await login(baseUrl); - const profile = await get(baseUrl, '/api/auth/me', { cookieJar }); - expect(profile).toMatchObject({ foo: 'bar' }); - const session = await get(baseUrl, '/api/session', { cookieJar }); - expect(session.accessToken).toEqual('new-access-token'); - expect(session.refreshToken).toEqual('new-refresh-token'); + expect(res.status).toBe(500); + expect(res.statusText).toMatch( + /No access token available to refetch the profile|"access_token" property must be a non-empty string/ + ); }); - test('should update the session in the afterRefetch hook', async () => { - const baseUrl = await setup(withoutApi, { - profileOptions: { - refetch: true, - afterRefetch(_req, _res, session) { - session.user.foo = 'bar'; + test('should refetch the user and preserve new tokens', async () => { + const loginRes = await appRouterLogin({ + callbackOpts: { + afterCallback(_req: NextRequest, session: Session) { + session.accessTokenExpiresAt = -60; return session; } } }); - const cookieJar = await login(baseUrl); - - const user = await get(baseUrl, '/api/auth/me', { cookieJar }); - expect(user.foo).toEqual('bar'); + nock.cleanAll(); + await refreshTokenRotationExchange(withApi, 'GEbRxBN...edjnXbL', {}, 'new-access-token', 'new-refresh-token'); + const res = await getResponse({ + url: '/api/auth/me', + cookies: { appSession: loginRes.cookies.get('appSession').value }, + profileOpts: { refetch: true }, + userInfoPayload: { foo: 'bar', sub: 'foo' }, + clearNock: false, + userInfoToken: 'new-access-token' + }); + expect(res.status).toBe(200); + await expect(appRouterGetSession(withApi, res)).resolves.toMatchObject({ + user: expect.objectContaining({ foo: 'bar' }), + accessToken: 'new-access-token', + refreshToken: 'new-refresh-token' + }); + await expect(res.json()).resolves.toMatchObject({ foo: 'bar' }); }); - test('should throw from the afterRefetch hook', async () => { - const baseUrl = await setup(withoutApi, { - profileOptions: { + test('should update the session in the afterRefetch hook', async () => { + const loginRes = await appRouterLogin(); + const res = await getResponse({ + url: '/api/auth/me', + cookies: { appSession: loginRes.cookies.get('appSession').value }, + userInfoPayload: { sub: 'foo' }, + profileOpts: { refetch: true, - afterRefetch() { - throw new Error('some validation error'); + afterRefetch(_req: NextRequest, session: Session) { + return { ...session, user: { ...session.user, foo: 'baz' } }; } } }); - const cookieJar = await login(baseUrl); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ foo: 'baz' }); + }); - await expect(get(baseUrl, '/api/auth/me', { cookieJar })).rejects.toThrowError('some validation error'); + test('should throw from the afterRefetch hook', async () => { + const loginRes = await appRouterLogin(); + await expect( + getResponse({ + url: '/api/auth/me', + cookies: { appSession: loginRes.cookies.get('appSession').value }, + userInfoPayload: { sub: 'foo' }, + profileOpts: { + refetch: true, + afterRefetch() { + throw new Error('some validation error'); + } + } + }) + ).resolves.toMatchObject({ status: 500, statusText: expect.stringMatching(/some validation error/) }); }); }); diff --git a/tests/helpers/testing.test.ts b/tests/helpers/testing.test.ts index 0fb45ddfd..95c7764d0 100644 --- a/tests/helpers/testing.test.ts +++ b/tests/helpers/testing.test.ts @@ -9,10 +9,7 @@ const weekInSeconds = 7 * 24 * 60 * 60; describe('generate-session-cookie', () => { test('use the provided secret', async () => { await generateSessionCookie({}, { secret: '__test_secret__' }); - expect(CookieStore).toHaveBeenCalledWith( - expect.objectContaining({ secret: '__test_secret__' }), - expect.any(Function) - ); + expect(CookieStore).toHaveBeenCalledWith(expect.objectContaining({ secret: '__test_secret__' })); }); test('use the default session configuration values', async () => { @@ -20,8 +17,7 @@ describe('generate-session-cookie', () => { expect(CookieStore).toHaveBeenCalledWith( expect.objectContaining({ session: { absoluteDuration: weekInSeconds, cookie: {} } - }), - expect.any(Function) + }) ); }); @@ -52,8 +48,7 @@ describe('generate-session-cookie', () => { sameSite: 'none' } } - }), - expect.any(Function) + }) ); }); @@ -65,7 +60,7 @@ describe('generate-session-cookie', () => { test('use the current time for the header values', async () => { const now = Date.now(); const current = (now / 1000) | 0; - const clock = jest.useFakeTimers('modern'); + const clock = jest.useFakeTimers(); clock.setSystemTime(now); await generateSessionCookie({}, { secret: '' }); expect(encryptMock).toHaveBeenCalledWith(expect.anything(), { diff --git a/tests/helpers/with-api-auth-required.test.ts b/tests/helpers/with-api-auth-required.test.ts index 2079d1b4c..4cad137b7 100644 --- a/tests/helpers/with-api-auth-required.test.ts +++ b/tests/helpers/with-api-auth-required.test.ts @@ -1,23 +1,143 @@ +import { randomBytes } from 'crypto'; +import { NextRequest, NextResponse } from 'next/server'; import { login, setup, teardown } from '../fixtures/setup'; -import { withoutApi } from '../fixtures/default-settings'; +import { withApi, withoutApi } from '../fixtures/default-settings'; import { get } from '../auth0-session/fixtures/helpers'; +import { getResponse, login as appRouterLogin, getSession } from '../fixtures/app-router-helpers'; +import { initAuth0 } from '../../src'; describe('with-api-auth-required', () => { - afterEach(teardown); + describe('app router', () => { + const getApiResponse = (opts?: any) => { + const auth0Instance = initAuth0(withApi); + return getResponse({ + url: '/api/auth/protected', + auth0Instance, + extraHandlers: { + protected(req: NextRequest, ctx: { params: Record }) { + return auth0Instance.withApiAuthRequired(() => { + return NextResponse.json({ foo: 'bar' }); + })(req, ctx); + }, + 'protected-returns-response'(req: NextRequest, ctx: { params: Record }) { + return auth0Instance.withApiAuthRequired((_req: NextRequest) => { + // @ts-expect-error This is not in lib/dom right now. + return Response.json({ foo: 'bar' }); + })(req, ctx); + }, + 'protected-updates-headers'(req: NextRequest, ctx: { params: Record }) { + return auth0Instance.withApiAuthRequired((_req: NextRequest) => { + const res = NextResponse.json({ foo: 'bar' }); + res.cookies.set('foo', 'bar'); + res.headers.set('baz', 'bar'); + return res; + })(req, ctx); + }, + 'protected-updates-session'(req: NextRequest, ctx: { params: Record }) { + return auth0Instance.withApiAuthRequired(async (req: NextRequest) => { + const res = NextResponse.json({ foo: 'bar' }); + const session = await auth0Instance.getSession(req, res); + await auth0Instance.updateSession(req, res, { + ...session, + user: { ...session?.user, update: opts.update } + }); + return res; + })(req, ctx); + } + }, + ...opts + }); + }; - test('protect an api route', async () => { - const baseUrl = await setup(withoutApi); - await expect(get(baseUrl, '/api/protected')).rejects.toThrow('Unauthorized'); + test('protect an api route', async () => { + await expect(getApiResponse()).resolves.toMatchObject({ status: 401 }); + }); + + test('allow access to an api route with a valid session', async () => { + const loginRes = await appRouterLogin(); + const res = await getApiResponse({ cookies: { appSession: loginRes.cookies.get('appSession').value } }); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toEqual({ foo: 'bar' }); + await expect(getSession(withApi, res)).resolves.toMatchObject({ + user: expect.objectContaining({ sub: '__test_sub__' }) + }); + }); + + test('allow access to an api route that returns a basic response with a valid session', async () => { + const loginRes = await appRouterLogin(); + const res = await getApiResponse({ + url: '/api/auth/protected-returns-response', + cookies: { appSession: loginRes.cookies.get('appSession').value } + }); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toEqual({ foo: 'bar' }); + await expect(getSession(withApi, res)).resolves.toMatchObject({ + user: expect.objectContaining({ sub: '__test_sub__' }) + }); + }); + + test('allow access to an api route that updates the cookies', async () => { + const loginRes = await appRouterLogin(); + const res = await getApiResponse({ + url: '/api/auth/protected-updates-headers', + cookies: { appSession: loginRes.cookies.get('appSession').value } + }); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toEqual({ foo: 'bar' }); + await expect(getSession(withApi, res)).resolves.toMatchObject({ + user: expect.objectContaining({ sub: '__test_sub__' }) + }); + expect(res.cookies.get('foo').value).toBe('bar'); + expect(res.headers.get('baz')).toBe('bar'); + }); + + test('allow access to an api route that updates the session cookie', async () => { + const loginRes = await appRouterLogin(); + const res = await getApiResponse({ + url: '/api/auth/protected-updates-session', + cookies: { appSession: loginRes.cookies.get('appSession').value }, + update: 'foo' + }); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toEqual({ foo: 'bar' }); + await expect(getSession(withApi, res)).resolves.toMatchObject({ + user: expect.objectContaining({ sub: '__test_sub__', update: 'foo' }) + }); + }); + + test('allow access to an api route that updates the session cookie to a chunked cookie', async () => { + const loginRes = await appRouterLogin(); + const update = randomBytes(2000).toString('base64'); + const res = await getApiResponse({ + url: '/api/auth/protected-updates-session', + cookies: { appSession: loginRes.cookies.get('appSession').value }, + update + }); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toEqual({ foo: 'bar' }); + await expect(getSession(withApi, res)).resolves.toMatchObject({ + user: expect.objectContaining({ sub: '__test_sub__', update }) + }); + }); }); - test('allow access to an api route with a valid session', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = await login(baseUrl); - const { - res: { statusCode }, - data - } = await get(baseUrl, '/api/protected', { cookieJar, fullResponse: true }); - expect(statusCode).toBe(200); - expect(data).toEqual({ foo: 'bar' }); + describe('page router', () => { + afterEach(teardown); + + test('protect an api route', async () => { + const baseUrl = await setup(withoutApi); + await expect(get(baseUrl, '/api/protected')).rejects.toThrow('Unauthorized'); + }); + + test('allow access to an api route with a valid session', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = await login(baseUrl); + const { + res: { statusCode }, + data + } = await get(baseUrl, '/api/protected', { cookieJar, fullResponse: true }); + expect(statusCode).toBe(200); + expect(data).toEqual({ foo: 'bar' }); + }); }); }); diff --git a/tests/helpers/with-middleware-auth-required.test.ts b/tests/helpers/with-middleware-auth-required.test.ts index a799697a6..372e701bf 100644 --- a/tests/helpers/with-middleware-auth-required.test.ts +++ b/tests/helpers/with-middleware-auth-required.test.ts @@ -1,6 +1,3 @@ -/** - * @jest-environment @edge-runtime/jest-environment - */ import { NextRequest, NextResponse } from 'next/server'; import { NextFetchEvent } from 'next/dist/server/web/spec-extension/fetch-event'; import { initAuth0 } from '../../src/edge'; @@ -10,6 +7,7 @@ import { encryption } from '../../src/auth0-session/utils/hkdf'; import { defaultConfig } from '../auth0-session/fixtures/helpers'; import { makeIdToken } from '../auth0-session/fixtures/cert'; import * as jose from 'jose'; +import { getSession } from '../fixtures/app-router-helpers'; const encrypted = async (claims: Partial = { sub: '__test_sub__' }): Promise => { const key = await encryption(defaultConfig.secret as string); @@ -34,8 +32,8 @@ const encrypted = async (claims: Partial = { sub: '__test_sub__' .encrypt(key); }; -const setup = async ({ url = 'http://example.com', config = withoutApi, user, middleware }: any = {}) => { - const mw = initAuth0(config).withMiddlewareAuthRequired(middleware); +const setup = async ({ url = 'http://example.com', config = withoutApi, user, middleware, instance }: any = {}) => { + const mw = (instance || initAuth0(config)).withMiddlewareAuthRequired(middleware); const request = new NextRequest(new URL(url)); if (user) { request.cookies.set('appSession', await encrypted({ sub: 'foo' })); @@ -57,17 +55,12 @@ describe('with-middleware-auth-required', () => { test('require auth on anonymous requests to api routes', async () => { const res = await setup({ url: 'http://example.com/api/foo' }); + expect(res.ok).toBe(false); expect(res.status).toEqual(401); - expect(res.headers.get('x-middleware-rewrite')).toEqual('http://example.com/api/auth/401'); - }); - - test('require auth on anonymous requests to api routes with custom 401', async () => { - const res = await setup({ - url: 'http://example.com/api/foo', - config: { ...withoutApi, routes: { unauthorized: '/api/foo-401' } } + await expect(res.json()).resolves.toMatchObject({ + error: 'not_authenticated', + description: 'The user does not have an active session or is not authenticated' }); - expect(res.status).toEqual(401); - expect(res.headers.get('x-middleware-rewrite')).toEqual('http://example.com/api/foo-401'); }); test('return to previous url', async () => { @@ -187,12 +180,36 @@ describe('with-middleware-auth-required', () => { expect(res.headers.get('set-cookie')).toMatch(/foo=bar;/); }); - test('should set status from custom middleware', async () => { + test('should allow responses from custom middleware', async () => { const middleware = () => { - return new NextResponse(null, { status: 400 }); + return NextResponse.json({ foo: 'bar' }); }; const res = await setup({ user: { name: 'dave' }, middleware }); - expect(res.status).toEqual(400); + expect(res.status).toEqual(200); + await expect(res.json()).resolves.toEqual({ foo: 'bar' }); + }); + + test('should allow ReadableStream responses from custom middleware', async () => { + const middleware = () => { + // @ts-expect-errors ts dom doesn't have json + return new Response(Response.json({ foo: 'bar' }).body); + }; + const res = await setup({ user: { name: 'dave' }, middleware }); + expect(res.status).toEqual(200); + await expect(res.json()).resolves.toEqual({ foo: 'bar' }); + }); + + test('should allow responses and cookies from custom middleware', async () => { + const middleware = () => { + const res = NextResponse.json({ foo: 'bar' }); + res.cookies.set('foo', 'bar'); + return res; + }; + const res = await setup({ user: { name: 'dave' }, middleware }); + expect(res.status).toEqual(200); + await expect(res.json()).resolves.toEqual({ foo: 'bar' }); + expect(res.headers.get('set-cookie')).toMatch(/appSession=/); + expect(res.headers.get('set-cookie')).toMatch(/foo=bar;/); }); test('should set just a custom cookie when session is not rolling', async () => { @@ -222,4 +239,52 @@ describe('with-middleware-auth-required', () => { expect(res.status).toEqual(200); expect(res.headers.get('set-cookie')).toBeNull(); }); + + test('should set a custom header', async () => { + const middleware = () => { + const res = NextResponse.next(); + res.headers.set('foo', 'bar'); + return res; + }; + const res = await setup({ + user: { name: 'dave' }, + middleware + }); + expect(res.status).toEqual(200); + expect(res.headers.get('foo')).toBe('bar'); + }); + + test('should update session from custom middleware', async () => { + const instance = initAuth0(withoutApi); + const middleware = async (req: NextRequest) => { + const res = NextResponse.next(); + const session = await instance.getSession(req, res); + await instance.updateSession(req, res, { ...session, user: { ...session?.user, baz: 'bar' } }); + return res; + }; + const res = await setup({ + instance, + user: { name: 'dave' }, + middleware + }); + await expect(getSession(withoutApi, res)).resolves.toMatchObject({ user: { sub: 'foo', baz: 'bar' } }); + }); + + test('should update session with a large claim from middleware', async () => { + const instance = initAuth0(withoutApi); + const middleware = async (req: NextRequest) => { + const res = NextResponse.next(); + const session = await instance.getSession(req, res); + await instance.updateSession(req, res, { ...session, user: { ...session?.user, baz: 'bar'.repeat(2000) } }); + return res; + }; + const res = await setup({ + instance, + user: { name: 'dave' }, + middleware + }); + await expect(getSession(withoutApi, res)).resolves.toMatchObject({ user: { baz: expect.any(String) } }); + expect(res.headers.get('set-cookie')).toMatch(/appSession=;/); + expect(res.headers.get('set-cookie')).toMatch(/appSession.0=/); + }); }); diff --git a/tests/helpers/with-page-auth-required.test.ts b/tests/helpers/with-page-auth-required.test.ts index 732eb42d8..2cbdde16d 100644 --- a/tests/helpers/with-page-auth-required.test.ts +++ b/tests/helpers/with-page-auth-required.test.ts @@ -1,150 +1,234 @@ +import React from 'react'; +import ReactDOMServer from 'react-dom/server'; +import { cookies as nextCookies } from 'next/headers'; +import * as navigation from 'next/navigation'; +import { NextResponse } from 'next/server'; import { URL } from 'url'; import { login, setup, teardown } from '../fixtures/setup'; -import { withoutApi } from '../fixtures/default-settings'; +import { login as appRouterLogin } from '../fixtures/app-router-helpers'; +import { withApi, withoutApi } from '../fixtures/default-settings'; import { get } from '../auth0-session/fixtures/helpers'; +import { initAuth0 } from '../../src'; + +jest.mock('next/headers'); +jest.mock('next/navigation', () => { + const navigation = jest.requireActual('next/navigation'); + return { + ...navigation, + redirect: jest.fn(navigation.redirect) + }; +}); describe('with-page-auth-required ssr', () => { - afterEach(teardown); - - test('protect a page', async () => { - const baseUrl = await setup(withoutApi); - const { - res: { statusCode, headers } - } = await get(baseUrl, '/protected', { fullResponse: true }); - expect(statusCode).toBe(307); - expect(decodeURIComponent(headers.location)).toBe('/api/auth/login?returnTo=/protected'); - }); + describe('app route', () => { + const getPageResponse = ({ config, cookies, returnTo, loginRes, params, searchParams }: any = {}) => { + const res = loginRes || new NextResponse(); + jest.mocked(nextCookies).mockImplementation(() => res.cookies as any); + const opts = { ...withApi, ...config }; + const instance = initAuth0(opts); + let headers = new Headers(); + if (cookies) { + headers.set( + 'Cookie', + Object.entries(cookies) + .map(([k, v]) => `${k}=${v}`) + .join('; ') + ); + } + const handler = instance.withPageAuthRequired(() => Promise.resolve(React.createElement('div', {}, 'foo')), { + returnTo + }); + return handler({ params, searchParams }); + }; - test('allow access to a page with a valid session', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = await login(baseUrl); + test('protect a page', async () => { + jest.spyOn(navigation, 'redirect'); + await expect(getPageResponse({})).rejects.toThrowError('NEXT_REDIRECT'); + expect(navigation.redirect).toHaveBeenCalledWith('/api/auth/login'); + }); - const { - res: { statusCode }, - data - } = await get(baseUrl, '/protected', { cookieJar, fullResponse: true }); - expect(statusCode).toBe(200); - expect(data).toMatch(/Protected Page.*__test_sub__/); - }); + test('protect a page and redirect to returnTo option', async () => { + jest.spyOn(navigation, 'redirect'); + await expect(getPageResponse({ returnTo: '/foo' })).rejects.toThrowError('NEXT_REDIRECT'); + expect(navigation.redirect).toHaveBeenCalledWith('/api/auth/login?returnTo=/foo'); + }); - test('accept a custom returnTo url', async () => { - const baseUrl = await setup(withoutApi, { withPageAuthRequiredOptions: { returnTo: '/foo' } }); - const { - res: { statusCode, headers } - } = await get(baseUrl, '/protected', { fullResponse: true }); - expect(statusCode).toBe(307); - expect(decodeURIComponent(headers.location)).toBe('/api/auth/login?returnTo=/foo'); - }); + test('protect a page and redirect to returnTo fn option', async () => { + jest.spyOn(navigation, 'redirect'); + await expect( + getPageResponse({ + returnTo({ params, searchParams }: any) { + const query = new URLSearchParams(searchParams).toString(); + return `/foo/${params.slug}${query ? `?${query}` : ''}`; + }, + params: { slug: 'bar' }, + searchParams: { foo: 'bar' } + }) + ).rejects.toThrowError('NEXT_REDIRECT'); + expect(navigation.redirect).toHaveBeenCalledWith('/api/auth/login?returnTo=/foo/bar?foo=bar'); + }); - test('accept custom server-side props', async () => { - const spy = jest.fn().mockReturnValue({ props: {} }); - const baseUrl = await setup(withoutApi, { - withPageAuthRequiredOptions: { - getServerSideProps: spy - } + test('allow access to a page with a valid session', async () => { + const loginRes = await appRouterLogin(); + + const loginCookie = loginRes.cookies.get('appSession'); + const res = await getPageResponse({ loginRes }); + expect(ReactDOMServer.renderToString(res)).toBe('
foo
'); + expect(loginRes.cookies.get('appSession')).toBeDefined(); + expect(loginRes.cookies.get('appSession')).not.toEqual(loginCookie); }); - const cookieJar = await login(baseUrl); - const { - res: { statusCode } - } = await get(baseUrl, '/protected', { cookieJar, fullResponse: true }); - expect(statusCode).toBe(200); - expect(spy).toHaveBeenCalledWith(expect.objectContaining({ req: expect.anything(), res: expect.anything() })); - }); - test('allow to override the user prop', async () => { - const baseUrl = await setup(withoutApi, { - withPageAuthRequiredOptions: { - async getServerSideProps() { - return { props: { user: { sub: 'foo' } } }; - } - } + test('use a custom login url', async () => { + await expect( + getPageResponse({ config: { routes: { ...withApi.routes, login: '/api/auth/custom-login' } } }) + ).rejects.toThrowError('NEXT_REDIRECT'); + expect(navigation.redirect).toHaveBeenCalledWith('/api/auth/custom-login'); }); - const cookieJar = await login(baseUrl); - const { data } = await get(baseUrl, '/protected', { cookieJar, fullResponse: true }); - expect(data).toMatch(/Protected Page.*foo/); }); - test('allow to override the user prop when using aync props', async () => { - const baseUrl = await setup(withoutApi, { - withPageAuthRequiredOptions: { - async getServerSideProps() { - return { props: Promise.resolve({ user: { sub: 'foo' } }) }; - } - } + describe('page route', () => { + afterEach(teardown); + + test('protect a page', async () => { + const baseUrl = await setup(withoutApi); + const { + res: { statusCode, headers } + } = await get(baseUrl, '/protected', { fullResponse: true }); + expect(statusCode).toBe(307); + expect(decodeURIComponent(headers.location)).toBe('/api/auth/login?returnTo=/protected'); }); - const cookieJar = await login(baseUrl); - const { data } = await get(baseUrl, '/protected', { cookieJar, fullResponse: true }); - expect(data).toMatch(/Protected Page.*foo/); - }); - test('use a custom login url', async () => { - process.env.NEXT_PUBLIC_AUTH0_LOGIN = '/api/foo'; - const baseUrl = await setup(withoutApi); - const { - res: { statusCode, headers } - } = await get(baseUrl, '/protected', { fullResponse: true }); - expect(statusCode).toBe(307); - expect(decodeURIComponent(headers.location)).toBe('/api/foo?returnTo=/protected'); - delete process.env.NEXT_PUBLIC_AUTH0_LOGIN; - }); + test('allow access to a page with a valid session', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = await login(baseUrl); - test('is a no-op when invoked as a client-side protection from the server', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = await login(baseUrl); - const { - res: { statusCode } - } = await get(baseUrl, '/csr-protected', { cookieJar, fullResponse: true }); - expect(statusCode).toBe(200); - }); + const { + res: { statusCode }, + data + } = await get(baseUrl, '/protected', { cookieJar, fullResponse: true }); + expect(statusCode).toBe(200); + expect(data).toMatch(/Protected Page.*__test_sub__/); + }); - test('should preserve multiple query params in the returnTo URL', async () => { - const baseUrl = await setup(withoutApi, { withPageAuthRequiredOptions: { returnTo: '/foo?bar=baz&qux=quux' } }); - const { - res: { statusCode, headers } - } = await get(baseUrl, '/protected', { fullResponse: true }); - expect(statusCode).toBe(307); - const url = new URL(headers.location, baseUrl); - expect(url.searchParams.get('returnTo')).toEqual('/foo?bar=baz&qux=quux'); - }); + test('accept a custom returnTo url', async () => { + const baseUrl = await setup(withoutApi, { withPageAuthRequiredOptions: { returnTo: '/foo' } }); + const { + res: { statusCode, headers } + } = await get(baseUrl, '/protected', { fullResponse: true }); + expect(statusCode).toBe(307); + expect(decodeURIComponent(headers.location)).toBe('/api/auth/login?returnTo=/foo'); + }); - test('allow access to a page with a valid session and async props', async () => { - const baseUrl = await setup(withoutApi, { - withPageAuthRequiredOptions: { - getServerSideProps() { - return Promise.resolve({ props: Promise.resolve({}) }); + test('accept custom server-side props', async () => { + const spy = jest.fn().mockReturnValue({ props: {} }); + const baseUrl = await setup(withoutApi, { + withPageAuthRequiredOptions: { + getServerSideProps: spy } - } + }); + const cookieJar = await login(baseUrl); + const { + res: { statusCode } + } = await get(baseUrl, '/protected', { cookieJar, fullResponse: true }); + expect(statusCode).toBe(200); + expect(spy).toHaveBeenCalledWith(expect.objectContaining({ req: expect.anything(), res: expect.anything() })); }); - const cookieJar = await login(baseUrl); - const { - res: { statusCode, headers }, - data - } = await get(baseUrl, '/protected', { cookieJar, fullResponse: true }); - expect(statusCode).toBe(200); - expect(data).toMatch(/Protected Page.*__test_sub__/); - const [cookie] = headers['set-cookie']; - expect(cookie).toMatch(/^appSession=/); - }); + test('allow to override the user prop', async () => { + const baseUrl = await setup(withoutApi, { + withPageAuthRequiredOptions: { + async getServerSideProps() { + return { props: { user: { sub: 'foo' } } }; + } + } + }); + const cookieJar = await login(baseUrl); + const { data } = await get(baseUrl, '/protected', { cookieJar, fullResponse: true }); + expect(data).toMatch(/Protected Page.*foo/); + }); - test('save session when getServerSideProps completes async', async () => { - const baseUrl = await setup(withoutApi, { - withPageAuthRequiredOptions: { - async getServerSideProps(ctx) { - await Promise.resolve(); - const session = await (global as any).getSession(ctx.req, ctx.res); - await (global as any).updateSession(ctx.req, ctx.res, { ...session, test: 'Hello World!' }); - return { props: {} }; + test('allow to override the user prop when using async props', async () => { + const baseUrl = await setup(withoutApi, { + withPageAuthRequiredOptions: { + async getServerSideProps() { + return { props: Promise.resolve({ user: { sub: 'foo' } }) }; + } } - } + }); + const cookieJar = await login(baseUrl); + const { data } = await get(baseUrl, '/protected', { cookieJar, fullResponse: true }); + expect(data).toMatch(/Protected Page.*foo/); + }); + + test('use a custom login url', async () => { + process.env.NEXT_PUBLIC_AUTH0_LOGIN = '/api/foo'; + const baseUrl = await setup(withoutApi); + const { + res: { statusCode, headers } + } = await get(baseUrl, '/protected', { fullResponse: true }); + expect(statusCode).toBe(307); + expect(decodeURIComponent(headers.location)).toBe('/api/foo?returnTo=/protected'); + delete process.env.NEXT_PUBLIC_AUTH0_LOGIN; + }); + + test('is a no-op when invoked as a client-side protection from the server', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = await login(baseUrl); + const { + res: { statusCode } + } = await get(baseUrl, '/csr-protected', { cookieJar, fullResponse: true }); + expect(statusCode).toBe(200); + }); + + test('should preserve multiple query params in the returnTo URL', async () => { + const baseUrl = await setup(withoutApi, { withPageAuthRequiredOptions: { returnTo: '/foo?bar=baz&qux=quux' } }); + const { + res: { statusCode, headers } + } = await get(baseUrl, '/protected', { fullResponse: true }); + expect(statusCode).toBe(307); + const url = new URL(headers.location, baseUrl); + expect(url.searchParams.get('returnTo')).toEqual('/foo?bar=baz&qux=quux'); + }); + + test('allow access to a page with a valid session and async props', async () => { + const baseUrl = await setup(withoutApi, { + withPageAuthRequiredOptions: { + getServerSideProps() { + return Promise.resolve({ props: Promise.resolve({}) }); + } + } + }); + const cookieJar = await login(baseUrl); + + const { + res: { statusCode, headers }, + data + } = await get(baseUrl, '/protected', { cookieJar, fullResponse: true }); + expect(statusCode).toBe(200); + expect(data).toMatch(/Protected Page.*__test_sub__/); + const [cookie] = headers['set-cookie']; + expect(cookie).toMatch(/^appSession=/); }); - const cookieJar = await login(baseUrl); - const { - res: { statusCode } - } = await get(baseUrl, '/protected', { cookieJar, fullResponse: true }); - expect(statusCode).toBe(200); - const session = await get(baseUrl, '/api/session', { cookieJar }); - expect(session.test).toBe('Hello World!'); + test('save session when getServerSideProps completes async', async () => { + const baseUrl = await setup(withoutApi, { + withPageAuthRequiredOptions: { + async getServerSideProps(ctx) { + await Promise.resolve(); + const session = await (global as any).getSession(ctx.req, ctx.res); + await (global as any).updateSession(ctx.req, ctx.res, { ...session, test: 'Hello World!' }); + return { props: {} }; + } + } + }); + const cookieJar = await login(baseUrl); + + const { + res: { statusCode } + } = await get(baseUrl, '/protected', { cookieJar, fullResponse: true }); + expect(statusCode).toBe(200); + const session = await get(baseUrl, '/api/session', { cookieJar }); + expect(session.test).toBe('Hello World!'); + }); }); }); diff --git a/tests/http/auth0-next-request.test.ts b/tests/http/auth0-next-request.test.ts new file mode 100644 index 000000000..9252c15b2 --- /dev/null +++ b/tests/http/auth0-next-request.test.ts @@ -0,0 +1,31 @@ +/** + * **REMOVE-TO-TEST-ON-EDGE**@jest-environment @edge-runtime/jest-environment + */ +import Auth0NextRequest from '../../src/http/auth0-next-request'; +import { NextRequest, NextResponse } from 'next/server'; + +const setup = (reqInit?: { headers: { cookie: string } }): [NextRequest, NextResponse] => { + return [new NextRequest(new URL('http://example.com'), reqInit), NextResponse.next()]; +}; + +describe('auth0-next-request', () => { + it('should get all cookies', async () => { + const [req] = setup({ headers: { cookie: 'foo=bar; bar=baz;' } }); + expect(new Auth0NextRequest(req).getCookies()).toMatchObject({ foo: 'bar', bar: 'baz' }); + }); + + it('should get all cookies in Next < 13.0.1', async () => { + const req = { + cookies: new Map([ + ['foo', 'bar'], + ['bar', 'baz'] + ]) + } as unknown as NextRequest; + expect(new Auth0NextRequest(req).getCookies()).toMatchObject({ foo: 'bar', bar: 'baz' }); + }); + + it('should get a cookie by name', async () => { + const [req] = setup({ headers: { cookie: 'foo=bar; bar=baz;' } }); + expect(new Auth0NextRequest(req).getCookies()['foo']).toEqual('bar'); + }); +}); diff --git a/tests/http/auth0-next-response-cookies.test.ts b/tests/http/auth0-next-response-cookies.test.ts new file mode 100644 index 000000000..1a20784ea --- /dev/null +++ b/tests/http/auth0-next-response-cookies.test.ts @@ -0,0 +1,49 @@ +import { cookies as nextCookies } from 'next/headers'; +import { Auth0NextResponseCookies } from '../../src/http'; +import { NextResponse } from 'next/server'; + +jest.mock('next/headers'); + +describe('auth0-next-response', () => { + it('should set a cookie', async () => { + const cookies = new Auth0NextResponseCookies(); + const res = new NextResponse(); + jest.mocked(nextCookies).mockImplementation(() => res.cookies as any); + cookies.setCookie('foo', 'bar'); + expect(res.cookies.get('foo')?.value).toEqual('bar'); + }); + + it('should not throw when setting a cookie fails', async () => { + const cookies = new Auth0NextResponseCookies(); + jest.mocked(nextCookies).mockImplementation( + () => + ({ + set: () => { + throw new Error(); + } + } as any) + ); + expect(() => cookies.setCookie('foo', 'bar')).not.toThrow(); + }); + + it('should delete cookies', async () => { + const cookies = new Auth0NextResponseCookies(); + const res = new NextResponse(); + jest.mocked(nextCookies).mockImplementation(() => res.cookies as any); + cookies.clearCookie('foo'); + expect(res.headers.get('set-cookie')).toEqual('foo=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT'); + }); + + it('should not throw when deleting a cookie fails', async () => { + const cookies = new Auth0NextResponseCookies(); + jest.mocked(nextCookies).mockImplementation( + () => + ({ + delete: () => { + throw new Error(); + } + } as any) + ); + expect(() => cookies.clearCookie('foo')).not.toThrow(); + }); +}); diff --git a/tests/http/auth0-next-response.test.ts b/tests/http/auth0-next-response.test.ts new file mode 100644 index 000000000..8a17d0c0e --- /dev/null +++ b/tests/http/auth0-next-response.test.ts @@ -0,0 +1,44 @@ +/** + * **REMOVE-TO-TEST-ON-EDGE**@jest-environment @edge-runtime/jest-environment + */ +import Auth0NextResponse from '../../src/http/auth0-next-response'; +import { NextRequest, NextResponse } from 'next/server'; + +const setup = (reqInit?: { headers: { cookie: string } }): [NextRequest, NextResponse] => { + return [new NextRequest(new URL('http://example.com'), reqInit), NextResponse.next()]; +}; + +describe('auth0-next-response', () => { + it('should set a cookie', async () => { + const [, res] = setup(); + const auth0Res = new Auth0NextResponse(res); + auth0Res.setCookie('foo', 'bar'); + + expect(auth0Res.res.headers.get('set-cookie')).toEqual('foo=bar; Path=/'); + }); + + it('should set a cookie with opts', async () => { + const [, res] = setup(); + const auth0Res = new Auth0NextResponse(res); + auth0Res.setCookie('foo', 'bar', { httpOnly: true, sameSite: 'strict' }); + + expect(auth0Res.res.headers.get('set-cookie')).toEqual('foo=bar; Path=/; HttpOnly; SameSite=strict'); + }); + + it('should not overwrite existing set cookie', async () => { + const [, res] = setup(); + res.cookies.set('foo', 'bar'); + const auth0Res = new Auth0NextResponse(res); + auth0Res.setCookie('baz', 'qux'); + + expect(auth0Res.res.headers.get('set-cookie')).toEqual(['foo=bar; Path=/', 'baz=qux; Path=/'].join(', ')); + }); + + it('should clear cookies', async () => { + const [, res] = setup(); + const auth0Res = new Auth0NextResponse(res); + auth0Res.clearCookie('foo'); + + expect(auth0Res.res.headers.get('set-cookie')).toMatch(/foo=;.*Expires=.*1970/); + }); +}); diff --git a/tests/session/cache.test.ts b/tests/session/cache.test.ts index e5d7437c1..cf4c3ad6c 100644 --- a/tests/session/cache.test.ts +++ b/tests/session/cache.test.ts @@ -1,7 +1,7 @@ import { IncomingMessage, ServerResponse } from 'http'; import { Socket } from 'net'; -import { mocked } from 'ts-jest/utils'; -import { NodeCookies as Cookies, StatelessSession, getConfig } from '../../src/auth0-session'; +import { StatelessSession, getConfig } from '../../src/auth0-session'; +import { get, set } from '../../src/session/cache'; import { ConfigParameters, Session, SessionCache } from '../../src'; import { withoutApi } from '../fixtures/default-settings'; @@ -10,17 +10,17 @@ describe('SessionCache', () => { let req: IncomingMessage; let res: ServerResponse; let session: Session; - let sessionStore: StatelessSession; + let sessionStore: StatelessSession; const setup = (conf: ConfigParameters) => { const config = getConfig(conf); - sessionStore = mocked(new StatelessSession(config, Cookies)); + sessionStore = jest.mocked(new StatelessSession(config)); sessionStore.save = jest.fn(); session = new Session({ sub: '__test_user__' }); session.idToken = '__test_id_token__'; cache = new SessionCache(config, sessionStore); - req = mocked(new IncomingMessage(new Socket())); - res = mocked(new ServerResponse(req)); + req = jest.mocked(new IncomingMessage(new Socket())); + res = jest.mocked(new ServerResponse(req)); }; beforeEach(() => { @@ -34,7 +34,12 @@ describe('SessionCache', () => { test('should create the session entry', async () => { await cache.create(req, res, session); expect(await cache.get(req, res)).toEqual(session); - expect(sessionStore.save).toHaveBeenCalledWith(req, res, session, undefined); + expect(sessionStore.save).toHaveBeenCalledWith( + expect.objectContaining({ req }), + expect.objectContaining({ res }), + session, + undefined + ); }); test('should delete the session entry', async () => { @@ -82,4 +87,24 @@ describe('SessionCache', () => { expect(sessionStore.read).toHaveBeenCalledTimes(1); expect(sessionStore.save).toHaveBeenCalledTimes(1); }); + + test('should save the session on read and update with a rolling session from RSC', async () => { + sessionStore.read = jest.fn().mockResolvedValue([{ user: { sub: '__test_user__' } }, 500]); + expect((await get({ sessionCache: cache }))[0]?.user).toEqual({ sub: '__test_user__' }); + await set({ sessionCache: cache, session: new Session({ sub: '__new_user__' }) }); + // Note: the cache is not updated from a RSC as there is no request context to cache against + expect((await get({ sessionCache: cache }))[0]?.user).toEqual({ sub: '__test_user__' }); + expect(sessionStore.read).toHaveBeenCalledTimes(2); + expect(sessionStore.save).toHaveBeenCalledTimes(3); + }); + + test('should save the session only on update without a rolling session from RSC', async () => { + setup({ ...withoutApi, session: { rolling: false } }); + sessionStore.read = jest.fn().mockResolvedValue([{ user: { sub: '__test_user__' } }, 500]); + expect((await get({ sessionCache: cache }))[0]?.user).toEqual({ sub: '__test_user__' }); + await set({ session: new Session({ sub: '__new_user__' }), sessionCache: cache }); + expect((await get({ sessionCache: cache }))[0]?.user).toEqual({ sub: '__test_user__' }); + expect(sessionStore.read).toHaveBeenCalledTimes(2); + expect(sessionStore.save).toHaveBeenCalledTimes(1); + }); }); diff --git a/tests/session/get-access-token-app-router-rsc.test.ts b/tests/session/get-access-token-app-router-rsc.test.ts new file mode 100644 index 000000000..600463673 --- /dev/null +++ b/tests/session/get-access-token-app-router-rsc.test.ts @@ -0,0 +1,108 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { cookies as nextCookies } from 'next/headers'; +import nock from 'nock'; +import { withApi } from '../fixtures/default-settings'; +import { AccessTokenRequest, initAuth0, Session } from '../../src'; +import { refreshTokenExchange } from '../fixtures/oidc-nocks'; +import { + getResponse, + GetResponseOpts, + LoginOpts, + login as appRouterLogin, + mockFetch +} from '../fixtures/app-router-helpers'; + +jest.mock('next/headers'); + +const getAccessTokenResponse = async ({ + authenticated = false, + getResOpts = {}, + loginOpts = {}, + getAccessTokenOpts +}: { + authenticated?: boolean; + getResOpts?: Omit; + loginOpts?: LoginOpts; + getAccessTokenOpts?: AccessTokenRequest; +} = {}) => { + const auth0Instance = initAuth0(withApi); + let cookies: { appSession?: string } = {}; + if (authenticated) { + const loginRes = await appRouterLogin(loginOpts); + cookies.appSession = loginRes.cookies.get('appSession').value; + jest.mocked(nextCookies).mockImplementation(() => loginRes.cookies); + } + await refreshTokenExchange( + withApi, + 'GEbRxBN...edjnXbL', + { + email: 'john@test.com', + name: 'john doe', + sub: '123' + }, + 'new-token' + ); + return getResponse({ + auth0Instance, + url: '/api/auth/access-token', + extraHandlers: { + async 'access-token'() { + const at = await auth0Instance.getAccessToken(getAccessTokenOpts); + const session = await auth0Instance.getSession(); + return NextResponse.json({ at, session }); + } + }, + cookies, + clearNock: false, + ...getResOpts + }).finally(() => nock.cleanAll()); +}; + +describe('get access token (app router rsc)', () => { + beforeEach(mockFetch); + + test('should return an access token', async () => { + const res = await getAccessTokenResponse({ + authenticated: true + }); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ + at: { accessToken: 'eyJz93a...k4laUWw' } + }); + }); + + test('should retrieve a new access token if the old one is expired and update the profile', async () => { + const res = await getAccessTokenResponse({ + authenticated: true, + loginOpts: { + callbackOpts: { + afterCallback(_req: NextRequest, session: Session) { + return { ...session, accessTokenExpiresAt: -60 }; + } + } + } + }); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ + at: { accessToken: 'new-token' }, + session: expect.objectContaining({ user: expect.objectContaining({ email: 'john@test.com' }) }) + }); + }); + + test('should retrieve a new access token and update the session based on afterRefresh', async () => { + const res = await getAccessTokenResponse({ + authenticated: true, + getAccessTokenOpts: { + refresh: true, + afterRefresh(_req, _res, session) { + return { ...session, foo: 'baz' }; + } + } + }); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ + at: { accessToken: 'new-token' }, + session: expect.objectContaining({ foo: 'baz' }) + }); + }); +}); diff --git a/tests/session/get-access-token-page-router.test.ts b/tests/session/get-access-token-page-router.test.ts new file mode 100644 index 000000000..56a7668d5 --- /dev/null +++ b/tests/session/get-access-token-page-router.test.ts @@ -0,0 +1,347 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import nock from 'nock'; +import { login, setup, teardown } from '../fixtures/setup'; +import { withApi } from '../fixtures/default-settings'; +import { get } from '../auth0-session/fixtures/helpers'; +import { Session } from '../../src'; +import { failedRefreshTokenExchange, refreshTokenExchange, refreshTokenRotationExchange } from '../fixtures/oidc-nocks'; +import { makeIdToken } from '../auth0-session/fixtures/cert'; + +describe('get access token (page router)', () => { + afterEach(teardown); + + test('should fail if the session is missing', async () => { + const baseUrl = await setup(withApi); + + await expect(get(baseUrl, '/api/access-token')).rejects.toThrow('The user does not have a valid session.'); + }); + + test('should fail if access_token and refresh_token are missing', async () => { + const baseUrl = await setup(withApi, { + callbackOptions: { + afterCallback: (_req: NextApiRequest, _res: NextApiResponse, session: Session): Session => { + delete session.accessToken; + delete session.refreshToken; + return session; + } + } + }); + const cookieJar = await login(baseUrl); + await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( + 'The user does not have a valid access token.' + ); + }); + + test('should fail if access_token expiry is missing', async () => { + const baseUrl = await setup(withApi, { + callbackOptions: { + afterCallback: (_req: NextApiRequest, _res: NextApiResponse, session: Session): Session => { + delete session.accessTokenExpiresAt; + return session; + } + } + }); + const cookieJar = await login(baseUrl); + await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( + 'Expiration information for the access token is not available. The user will need to sign in again.' + ); + }); + + test('should fail if access_token scope is missing', async () => { + const baseUrl = await setup(withApi, { + callbackOptions: { + afterCallback: (_req: NextApiRequest, _res: NextApiResponse, session: Session): Session => { + delete session.accessTokenScope; + return session; + } + }, + getAccessTokenOptions: { + scopes: ['read:foo'] + } + }); + const cookieJar = await login(baseUrl); + await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( + 'An access token with the requested scopes could not be provided. The user will need to sign in again.' + ); + }); + + test("should fail if access_token scope doesn't match requested scope", async () => { + const baseUrl = await setup(withApi, { + callbackOptions: { + afterCallback: (_req: NextApiRequest, _res: NextApiResponse, session: Session): Session => { + session.accessTokenScope = 'read:bar'; + return session; + } + }, + getAccessTokenOptions: { + scopes: ['read:foo'] + } + }); + const cookieJar = await login(baseUrl); + await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( + 'Could not retrieve an access token with scopes "read:foo". The user will need to sign in again.' + ); + }); + + test('should fail if the access token is expired', async () => { + const baseUrl = await setup(withApi, { + callbackOptions: { + afterCallback: (_req: NextApiRequest, _res: NextApiResponse, session: Session): Session => { + delete session.refreshToken; + session.accessTokenExpiresAt = -60; + return session; + } + } + }); + const cookieJar = await login(baseUrl); + await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( + 'The access token expired and a refresh token is not available. The user will need to sign in again.' + ); + }); + + test('should fail if you try to refresh the access token without a refresh token', async () => { + const baseUrl = await setup(withApi, { + callbackOptions: { + afterCallback: (_req: NextApiRequest, _res: NextApiResponse, session: Session): Session => { + delete session.refreshToken; + return session; + } + }, + getAccessTokenOptions: { refresh: true } + }); + const cookieJar = await login(baseUrl); + await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( + 'A refresh token is required to refresh the access token, but none is present.' + ); + }); + + test('should return an access token', async () => { + const baseUrl = await setup(withApi); + const cookieJar = await login(baseUrl); + const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); + expect(accessToken).toEqual('eyJz93a...k4laUWw'); + }); + + test('should retrieve a new access token if the old one is expired and update the profile', async () => { + await refreshTokenExchange( + withApi, + 'GEbRxBN...edjnXbL', + { + email: 'john@test.com', + name: 'john doe', + sub: '123' + }, + 'new-token' + ); + const baseUrl = await setup(withApi, { + callbackOptions: { + afterCallback: (_req: NextApiRequest, _res: NextApiResponse, session: Session): Session => { + session.accessTokenExpiresAt = -60; + return session; + } + } + }); + const cookieJar = await login(baseUrl); + const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); + expect(accessToken).toEqual('new-token'); + const { refreshToken } = await get(baseUrl, '/api/session', { cookieJar }); + expect(refreshToken).toEqual('GEbRxBN...edjnXbL'); + }); + + test('should retrieve a new access token if force refresh is set', async () => { + await refreshTokenExchange( + withApi, + 'GEbRxBN...edjnXbL', + { + email: 'john@test.com', + name: 'john doe', + sub: '123' + }, + 'new-token' + ); + const baseUrl = await setup(withApi, { getAccessTokenOptions: { refresh: true } }); + const cookieJar = await login(baseUrl); + const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); + expect(accessToken).toEqual('new-token'); + const { refreshToken } = await get(baseUrl, '/api/session', { cookieJar }); + expect(refreshToken).toEqual('GEbRxBN...edjnXbL'); + }); + + test('should fail when refresh grant fails', async () => { + await failedRefreshTokenExchange(withApi, 'GEbRxBN...edjnXbL', {}, 500); + const baseUrl = await setup(withApi, { getAccessTokenOptions: { refresh: true } }); + const cookieJar = await login(baseUrl); + await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( + 'The request to refresh the access token failed. CAUSE: expected 200 OK, got: 500 Internal Server Error' + ); + }); + + test('should fail when refresh grant fails with oauth error', async () => { + await failedRefreshTokenExchange( + withApi, + 'GEbRxBN...edjnXbL', + { error: 'invalid_grant', error_description: 'Unknown or invalid refresh token.' }, + 401 + ); + const baseUrl = await setup(withApi, { getAccessTokenOptions: { refresh: true } }); + const cookieJar = await login(baseUrl); + await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( + 'The request to refresh the access token failed. CAUSE: invalid_grant (Unknown or invalid refresh token.)' + ); + }); + + test('should escape oauth error', async () => { + await failedRefreshTokenExchange( + withApi, + 'GEbRxBN...edjnXbL', + { error: '', error_description: '' }, + 401 + ); + const baseUrl = await setup(withApi, { getAccessTokenOptions: { refresh: true } }); + const cookieJar = await login(baseUrl); + await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( + 'The request to refresh the access token failed. CAUSE: <script>alert(1)</script> (<script>alert(2)</script>)' + ); + }); + + test('should retrieve a new access token and rotate the refresh token', async () => { + await refreshTokenRotationExchange( + withApi, + 'GEbRxBN...edjnXbL', + { + email: 'john@test.com', + name: 'john doe', + sub: '123' + }, + 'new-token', + 'new-refresh-token' + ); + const baseUrl = await setup(withApi, { getAccessTokenOptions: { refresh: true } }); + const cookieJar = await login(baseUrl); + const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); + expect(accessToken).toEqual('new-token'); + const { refreshToken } = await get(baseUrl, '/api/session', { cookieJar }); + expect(refreshToken).toEqual('new-refresh-token'); + }); + + test('should return an access token with the given scopes', async () => { + const baseUrl = await setup(withApi, { getAccessTokenOptions: { scopes: ['read:foo'] } }); + const cookieJar = await login(baseUrl); + const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); + expect(accessToken).toEqual('eyJz93a...k4laUWw'); + }); + + test('should not overwrite custom session properties when applying a new access token', async () => { + await refreshTokenExchange( + withApi, + 'GEbRxBN...edjnXbL', + { + email: 'john@test.com', + name: 'john doe', + sub: '123' + }, + 'new-token' + ); + const baseUrl = await setup(withApi, { + getAccessTokenOptions: { refresh: true }, + callbackOptions: { + afterCallback: (_req: NextApiRequest, _res: NextApiResponse, session: Session): Session => { + session.foo = 'bar'; + session.user.bar = 'baz'; + return session; + } + } + }); + const cookieJar = await login(baseUrl); + const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); + expect(accessToken).toEqual('new-token'); + const session = await get(baseUrl, '/api/session', { cookieJar }); + expect(session).toMatchObject({ + foo: 'bar', + accessToken: 'new-token', + refreshToken: 'GEbRxBN...edjnXbL', + user: { + nickname: '__test_nickname__', + email: 'john@test.com', + name: 'john doe', + sub: '123', + bar: 'baz' + } + }); + }); + + test('should retrieve a new access token and update the session based on afterRefresh', async () => { + await refreshTokenExchange(withApi, 'GEbRxBN...edjnXbL', {}, 'new-token'); + const baseUrl = await setup(withApi, { + getAccessTokenOptions: { + refresh: true, + afterRefresh(_req, _res, session) { + delete session.accessTokenScope; + return session as Session; + } + } + }); + const cookieJar = await login(baseUrl); + const { accessTokenScope } = await get(baseUrl, '/api/session', { cookieJar }); + expect(accessTokenScope).not.toBeUndefined(); + const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); + expect(accessToken).toEqual('new-token'); + const { accessTokenScope: newAccessTokenScope } = await get(baseUrl, '/api/session', { + cookieJar + }); + expect(newAccessTokenScope).toBeUndefined(); + }); + + test('should retrieve a new access token and update the session based on the storeIDToken config', async () => { + await refreshTokenExchange(withApi, 'GEbRxBN...edjnXbL', {}, 'new-token'); + const baseUrl = await setup( + { ...withApi, session: { storeIDToken: false } }, + { + getAccessTokenOptions: { + refresh: true + } + } + ); + const cookieJar = await login(baseUrl); + const session = await get(baseUrl, '/api/session', { cookieJar }); + expect(session.idToken).toBeUndefined(); + const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); + expect(accessToken).toEqual('new-token'); + const newSession = await get(baseUrl, '/api/session', { + cookieJar + }); + expect(newSession.idToken).toBeUndefined(); + }); + + test('should pass custom auth params in refresh grant request body', async () => { + const idToken = await makeIdToken({ + iss: `${withApi.issuerBaseURL}/`, + aud: withApi.clientID, + email: 'john@test.com', + name: 'john doe', + sub: '123' + }); + + const spy = jest.fn(); + nock(`${withApi.issuerBaseURL}`) + .post('/oauth/token', /grant_type=refresh_token/) + .reply(200, (_, body) => { + spy(body); + return { + access_token: 'new-token', + id_token: idToken, + token_type: 'Bearer', + expires_in: 750, + scope: 'read:foo write:foo' + }; + }); + + const baseUrl = await setup(withApi, { + getAccessTokenOptions: { refresh: true, authorizationParams: { baz: 'qux' } } + }); + const cookieJar = await login(baseUrl); + const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); + expect(accessToken).toEqual('new-token'); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('baz=qux')); + }); +}); diff --git a/tests/session/get-access-token.test.ts b/tests/session/get-access-token.test.ts index 28f37aa14..364f58718 100644 --- a/tests/session/get-access-token.test.ts +++ b/tests/session/get-access-token.test.ts @@ -1,346 +1,266 @@ -import { login, setup, teardown } from '../fixtures/setup'; -import { withApi } from '../fixtures/default-settings'; -import { get } from '../auth0-session/fixtures/helpers'; -import { Session } from '../../src'; -import { failedRefreshTokenExchange, refreshTokenExchange, refreshTokenRotationExchange } from '../fixtures/oidc-nocks'; -import { makeIdToken } from '../auth0-session/fixtures/cert'; +/** + * **REMOVE-TO-TEST-ON-EDGE**@jest-environment @edge-runtime/jest-environment + */ +import { NextRequest, NextResponse } from 'next/server'; import nock from 'nock'; +import { withApi } from '../fixtures/default-settings'; +import { AccessTokenRequest, Session } from '../../src'; +import { refreshTokenExchange } from '../fixtures/oidc-nocks'; +import { + getResponse, + GetResponseOpts, + LoginOpts, + login as appRouterLogin, + mockFetch, + initAuth0 +} from '../fixtures/app-router-helpers'; -describe('get access token', () => { - afterEach(teardown); +const getAccessTokenResponse = async ({ + authenticated = false, + getResOpts = {}, + loginOpts = {}, + getAccessTokenOpts +}: { + authenticated?: boolean; + getResOpts?: Omit; + loginOpts?: LoginOpts; + getAccessTokenOpts?: AccessTokenRequest; +} = {}) => { + const auth0Instance = initAuth0(withApi); + let cookies: { appSession?: string } = {}; + if (authenticated) { + const loginRes = await appRouterLogin(loginOpts); + cookies.appSession = loginRes.cookies.get('appSession').value; + } + await refreshTokenExchange( + withApi, + 'GEbRxBN...edjnXbL', + { + email: 'john@test.com', + name: 'john doe', + sub: '123' + }, + 'new-token' + ); + return getResponse({ + auth0Instance, + url: '/api/auth/access-token', + extraHandlers: { + async 'access-token'(req: NextRequest) { + const res = new NextResponse(); + const at = await auth0Instance.getAccessToken(req, res, getAccessTokenOpts); + const session = await auth0Instance.getSession(req, res); + return NextResponse.json({ at, session }, res); + } + }, + cookies, + clearNock: false, + ...getResOpts + }).finally(() => nock.cleanAll()); +}; - test('should fail if the session is missing', async () => { - const baseUrl = await setup(withApi); +describe('get access token (api route)', () => { + beforeEach(mockFetch); - await expect(get(baseUrl, '/api/access-token')).rejects.toThrow('The user does not have a valid session.'); + test('should fail if the session is missing', async () => { + await expect(getAccessTokenResponse()).resolves.toMatchObject({ + status: 500, + statusText: expect.stringMatching(/The user does not have a valid session/) + }); }); test('should fail if access_token and refresh_token are missing', async () => { - const baseUrl = await setup(withApi, { - callbackOptions: { - afterCallback: (_req, _res, session): Session => { - delete session.accessToken; - delete session.refreshToken; - return session; + await expect( + getAccessTokenResponse({ + authenticated: true, + loginOpts: { + callbackOpts: { + afterCallback(_req: NextRequest, session: Session) { + delete session.accessToken; + delete session.refreshToken; + return session; + } + } } - } + }) + ).resolves.toMatchObject({ + status: 500, + statusText: expect.stringMatching(/The user does not have a valid access token/) }); - const cookieJar = await login(baseUrl); - await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( - 'The user does not have a valid access token.' - ); }); test('should fail if access_token expiry is missing', async () => { - const baseUrl = await setup(withApi, { - callbackOptions: { - afterCallback: (_req, _res, session): Session => { - delete session.accessTokenExpiresAt; - return session; + await expect( + getAccessTokenResponse({ + authenticated: true, + loginOpts: { + callbackOpts: { + afterCallback(_req: NextRequest, session: Session) { + delete session.accessTokenExpiresAt; + return session; + } + } } - } + }) + ).resolves.toMatchObject({ + status: 500, + statusText: expect.stringMatching(/Expiration information for the access token is not available/) }); - const cookieJar = await login(baseUrl); - await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( - 'Expiration information for the access token is not available. The user will need to sign in again.' - ); }); test('should fail if access_token scope is missing', async () => { - const baseUrl = await setup(withApi, { - callbackOptions: { - afterCallback: (_req, _res, session): Session => { - delete session.accessTokenScope; - return session; + await expect( + getAccessTokenResponse({ + authenticated: true, + loginOpts: { + callbackOpts: { + afterCallback(_req: NextRequest, session: Session) { + delete session.accessTokenScope; + return session; + } + } + }, + getAccessTokenOpts: { + scopes: ['read:foo'] } - }, - getAccessTokenOptions: { - scopes: ['read:foo'] - } + }) + ).resolves.toMatchObject({ + status: 500, + statusText: expect.stringMatching(/An access token with the requested scopes could not be provided/) }); - const cookieJar = await login(baseUrl); - await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( - 'An access token with the requested scopes could not be provided. The user will need to sign in again.' - ); }); test("should fail if access_token scope doesn't match requested scope", async () => { - const baseUrl = await setup(withApi, { - callbackOptions: { - afterCallback: (_req, _res, session): Session => { - session.accessTokenScope = 'read:bar'; - return session; + await expect( + getAccessTokenResponse({ + authenticated: true, + loginOpts: { + callbackOpts: { + afterCallback(_req: NextRequest, session: Session) { + return { ...session, accessTokenScope: 'read:bar' }; + } + } + }, + getAccessTokenOpts: { + scopes: ['read:foo'] } - }, - getAccessTokenOptions: { - scopes: ['read:foo'] - } + }) + ).resolves.toMatchObject({ + status: 500, + statusText: expect.stringMatching(/Could not retrieve an access token with scopes "read:foo"/) }); - const cookieJar = await login(baseUrl); - await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( - 'Could not retrieve an access token with scopes "read:foo". The user will need to sign in again.' - ); }); test('should fail if the access token is expired', async () => { - const baseUrl = await setup(withApi, { - callbackOptions: { - afterCallback: (_req, _res, session): Session => { - delete session.refreshToken; - session.accessTokenExpiresAt = -60; - return session; + await expect( + getAccessTokenResponse({ + authenticated: true, + loginOpts: { + callbackOpts: { + afterCallback(_req: NextRequest, session: Session) { + delete session.refreshToken; + return { ...session, accessTokenExpiresAt: -60 }; + } + } } - } + }) + ).resolves.toMatchObject({ + status: 500, + statusText: expect.stringMatching(/The access token expired and a refresh token is not available/) }); - const cookieJar = await login(baseUrl); - await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( - 'The access token expired and a refresh token is not available. The user will need to sign in again.' - ); }); test('should fail if you try to refresh the access token without a refresh token', async () => { - const baseUrl = await setup(withApi, { - callbackOptions: { - afterCallback: (_req, _res, session): Session => { - delete session.refreshToken; - return session; - } - }, - getAccessTokenOptions: { refresh: true } + await expect( + getAccessTokenResponse({ + authenticated: true, + loginOpts: { + callbackOpts: { + afterCallback(_req: NextRequest, session: Session) { + delete session.refreshToken; + return session; + } + } + }, + getAccessTokenOpts: { refresh: true } + }) + ).resolves.toMatchObject({ + status: 500, + statusText: expect.stringMatching(/A refresh token is required to refresh the access token, but none is present/) }); - const cookieJar = await login(baseUrl); - await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( - 'A refresh token is required to refresh the access token, but none is present.' - ); }); test('should return an access token', async () => { - const baseUrl = await setup(withApi); - const cookieJar = await login(baseUrl); - const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); - expect(accessToken).toEqual('eyJz93a...k4laUWw'); + const res = await getAccessTokenResponse({ + authenticated: true + }); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ + at: { accessToken: 'eyJz93a...k4laUWw' } + }); }); test('should retrieve a new access token if the old one is expired and update the profile', async () => { - await refreshTokenExchange( - withApi, - 'GEbRxBN...edjnXbL', - { - email: 'john@test.com', - name: 'john doe', - sub: '123' - }, - 'new-token' - ); - const baseUrl = await setup(withApi, { - callbackOptions: { - afterCallback: (_req, _res, session): Session => { - session.accessTokenExpiresAt = -60; - return session; + const res = await getAccessTokenResponse({ + authenticated: true, + loginOpts: { + callbackOpts: { + afterCallback(_req: NextRequest, session: Session) { + return { ...session, accessTokenExpiresAt: -60 }; + } } } }); - const cookieJar = await login(baseUrl); - const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); - expect(accessToken).toEqual('new-token'); - const { refreshToken } = await get(baseUrl, '/api/session', { cookieJar }); - expect(refreshToken).toEqual('GEbRxBN...edjnXbL'); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ + at: { accessToken: 'new-token' }, + session: expect.objectContaining({ user: expect.objectContaining({ email: 'john@test.com' }) }) + }); }); test('should retrieve a new access token if force refresh is set', async () => { - await refreshTokenExchange( - withApi, - 'GEbRxBN...edjnXbL', - { - email: 'john@test.com', - name: 'john doe', - sub: '123' - }, - 'new-token' - ); - const baseUrl = await setup(withApi, { getAccessTokenOptions: { refresh: true } }); - const cookieJar = await login(baseUrl); - const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); - expect(accessToken).toEqual('new-token'); - const { refreshToken } = await get(baseUrl, '/api/session', { cookieJar }); - expect(refreshToken).toEqual('GEbRxBN...edjnXbL'); - }); - - test('should fail when refresh grant fails', async () => { - await failedRefreshTokenExchange(withApi, 'GEbRxBN...edjnXbL', {}, 500); - const baseUrl = await setup(withApi, { getAccessTokenOptions: { refresh: true } }); - const cookieJar = await login(baseUrl); - await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( - 'The request to refresh the access token failed. CAUSE: expected 200 OK, got: 500 Internal Server Error' - ); - }); - - test('should fail when refresh grant fails with oauth error', async () => { - await failedRefreshTokenExchange( - withApi, - 'GEbRxBN...edjnXbL', - { error: 'invalid_grant', error_description: 'Unknown or invalid refresh token.' }, - 401 - ); - const baseUrl = await setup(withApi, { getAccessTokenOptions: { refresh: true } }); - const cookieJar = await login(baseUrl); - await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( - 'The request to refresh the access token failed. CAUSE: invalid_grant (Unknown or invalid refresh token.)' - ); - }); - - test('should escape oauth error', async () => { - await failedRefreshTokenExchange( - withApi, - 'GEbRxBN...edjnXbL', - { error: '', error_description: '' }, - 401 - ); - const baseUrl = await setup(withApi, { getAccessTokenOptions: { refresh: true } }); - const cookieJar = await login(baseUrl); - await expect(get(baseUrl, '/api/access-token', { cookieJar })).rejects.toThrow( - 'The request to refresh the access token failed. CAUSE: <script>alert(1)</script> (<script>alert(2)</script>)' - ); - }); - - test('should retrieve a new access token and rotate the refresh token', async () => { - await refreshTokenRotationExchange( - withApi, - 'GEbRxBN...edjnXbL', - { - email: 'john@test.com', - name: 'john doe', - sub: '123' - }, - 'new-token', - 'new-refresh-token' - ); - const baseUrl = await setup(withApi, { getAccessTokenOptions: { refresh: true } }); - const cookieJar = await login(baseUrl); - const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); - expect(accessToken).toEqual('new-token'); - const { refreshToken } = await get(baseUrl, '/api/session', { cookieJar }); - expect(refreshToken).toEqual('new-refresh-token'); - }); - - test('should return an access token with the given scopes', async () => { - const baseUrl = await setup(withApi, { getAccessTokenOptions: { scopes: ['read:foo'] } }); - const cookieJar = await login(baseUrl); - const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); - expect(accessToken).toEqual('eyJz93a...k4laUWw'); - }); - - test('should not overwrite custom session properties when applying a new access token', async () => { - await refreshTokenExchange( - withApi, - 'GEbRxBN...edjnXbL', - { - email: 'john@test.com', - name: 'john doe', - sub: '123' - }, - 'new-token' - ); - const baseUrl = await setup(withApi, { - getAccessTokenOptions: { refresh: true }, - callbackOptions: { - afterCallback: (_req, _res, session): Session => { - session.foo = 'bar'; - session.user.bar = 'baz'; - return session; - } - } + const res = await getAccessTokenResponse({ + authenticated: true, + getAccessTokenOpts: { refresh: true } }); - const cookieJar = await login(baseUrl); - const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); - expect(accessToken).toEqual('new-token'); - const session = await get(baseUrl, '/api/session', { cookieJar }); - expect(session).toMatchObject({ - foo: 'bar', - accessToken: 'new-token', - refreshToken: 'GEbRxBN...edjnXbL', - user: { - nickname: '__test_nickname__', - email: 'john@test.com', - name: 'john doe', - sub: '123', - bar: 'baz' - } + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ + at: { accessToken: 'new-token' }, + session: expect.objectContaining({ user: expect.objectContaining({ email: 'john@test.com' }) }) }); }); test('should retrieve a new access token and update the session based on afterRefresh', async () => { - await refreshTokenExchange(withApi, 'GEbRxBN...edjnXbL', {}, 'new-token'); - const baseUrl = await setup(withApi, { - getAccessTokenOptions: { + const res = await getAccessTokenResponse({ + authenticated: true, + getAccessTokenOpts: { refresh: true, afterRefresh(_req, _res, session) { - delete session.accessTokenScope; - return session; + return { ...session, foo: 'baz' }; } } }); - const cookieJar = await login(baseUrl); - const { accessTokenScope } = await get(baseUrl, '/api/session', { cookieJar }); - expect(accessTokenScope).not.toBeUndefined(); - const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); - expect(accessToken).toEqual('new-token'); - const { accessTokenScope: newAccessTokenScope } = await get(baseUrl, '/api/session', { - cookieJar + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ + at: { accessToken: 'new-token' }, + session: expect.objectContaining({ foo: 'baz' }) }); - expect(newAccessTokenScope).toBeUndefined(); }); - test('should retrieve a new access token and update the session based on the storeIDToken config', async () => { - await refreshTokenExchange(withApi, 'GEbRxBN...edjnXbL', {}, 'new-token'); - const baseUrl = await setup( - { ...withApi, session: { storeIDToken: false } }, - { - getAccessTokenOptions: { - refresh: true + test('should fail when the refresh grant request fails', async () => { + const res = await getAccessTokenResponse({ + authenticated: true, + getAccessTokenOpts: { + refresh: true, + afterRefresh(_req, _res, session) { + return { ...session, foo: 'baz' }; } } - ); - const cookieJar = await login(baseUrl); - const session = await get(baseUrl, '/api/session', { cookieJar }); - expect(session.idToken).toBeUndefined(); - const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); - expect(accessToken).toEqual('new-token'); - const newSession = await get(baseUrl, '/api/session', { - cookieJar - }); - expect(newSession.idToken).toBeUndefined(); - }); - - test('should pass custom auth params in refresh grant request body', async () => { - const idToken = await makeIdToken({ - iss: `${withApi.issuerBaseURL}/`, - aud: withApi.clientID, - email: 'john@test.com', - name: 'john doe', - sub: '123' }); - - const spy = jest.fn(); - nock(`${withApi.issuerBaseURL}`) - .post('/oauth/token', /grant_type=refresh_token/) - .reply(200, (_, body) => { - spy(body); - return { - access_token: 'new-token', - id_token: idToken, - token_type: 'Bearer', - expires_in: 750, - scope: 'read:foo write:foo' - }; - }); - - const baseUrl = await setup(withApi, { - getAccessTokenOptions: { refresh: true, authorizationParams: { baz: 'qux' } } + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ + at: { accessToken: 'new-token' }, + session: expect.objectContaining({ foo: 'baz' }) }); - const cookieJar = await login(baseUrl); - const { accessToken } = await get(baseUrl, '/api/access-token', { cookieJar }); - expect(accessToken).toEqual('new-token'); - expect(spy).toHaveBeenCalledWith(expect.stringContaining('baz=qux')); }); }); diff --git a/tests/session/session.test.ts b/tests/session/session.test.ts index fd2094acc..3c68c8498 100644 --- a/tests/session/session.test.ts +++ b/tests/session/session.test.ts @@ -1,9 +1,11 @@ import { TokenSet } from 'openid-client'; -import { fromJson, fromTokenSet } from '../../src/session'; +import { fromJson, fromTokenEndpointResponse } from '../../src/session'; import { makeIdToken } from '../auth0-session/fixtures/cert'; import { Session } from '../../src'; -const routes = { login: '', callback: '', postLogoutRedirect: '', unauthorized: '' }; +const routes = { login: '', callback: '', postLogoutRedirect: '' }; + +const getLoginState = () => Promise.resolve({}); describe('session', () => { test('should construct a session with a user', async () => { @@ -13,9 +15,10 @@ describe('session', () => { describe('from tokenSet', () => { test('should construct a session from a tokenSet', async () => { expect( - fromTokenSet(new TokenSet({ id_token: await makeIdToken({ foo: 'bar', bax: 'qux' }) }), { + fromTokenEndpointResponse(new TokenSet({ id_token: await makeIdToken({ foo: 'bar', bax: 'qux' }) }), { identityClaimFilter: ['baz'], routes, + getLoginState, session: { storeIDToken: true } }).user ).toEqual({ @@ -33,9 +36,10 @@ describe('session', () => { test('should store the ID Token by default', async () => { expect( - fromTokenSet(new TokenSet({ id_token: await makeIdToken({ foo: 'bar' }) }), { + fromTokenEndpointResponse(new TokenSet({ id_token: await makeIdToken({ foo: 'bar' }) }), { identityClaimFilter: ['baz'], routes, + getLoginState, session: { storeIDToken: true } }).idToken ).toBeDefined(); @@ -43,7 +47,7 @@ describe('session', () => { test('should not store the ID Token', async () => { expect( - fromTokenSet(new TokenSet({ id_token: await makeIdToken({ foo: 'bar' }) }), { + fromTokenEndpointResponse(new TokenSet({ id_token: await makeIdToken({ foo: 'bar' }) }), { session: { storeIDToken: false, name: '', @@ -52,6 +56,7 @@ describe('session', () => { absoluteDuration: 0, cookie: { transient: false, httpOnly: false, sameSite: 'lax' } }, + getLoginState, identityClaimFilter: ['baz'], routes }).idToken diff --git a/tests/session/touch-session.test.ts b/tests/session/touch-session.test.ts index 733fb8dad..c5c0b726d 100644 --- a/tests/session/touch-session.test.ts +++ b/tests/session/touch-session.test.ts @@ -1,45 +1,92 @@ import { login, setup, teardown } from '../fixtures/setup'; import { withoutApi } from '../fixtures/default-settings'; import { get } from '../auth0-session/fixtures/helpers'; +import { getResponse, login as appRouterLogin } from '../fixtures/app-router-helpers'; +import { NextRequest, NextResponse } from 'next/server'; +import { initAuth0 } from '../../src'; describe('touch-session', () => { - afterEach(teardown); + describe('app router', () => { + test('should not update the session when getting the session', async () => { + const config = { ...withoutApi, session: { autoSave: false } }; + const auth0Instance = initAuth0(config); + const loginRes = await appRouterLogin({ config }); + const res = await getResponse({ + url: '/api/auth/session', + config, + auth0Instance, + cookies: { appSession: loginRes.cookies.get('appSession').value }, + extraHandlers: { + async session(req: NextRequest) { + const res = new NextResponse(); + await auth0Instance.getSession(req, res); + return res; + } + } + }); + expect(res.headers.get('set-cookie')).toBeNull(); + }); - test('should not update the session when getting the session', async () => { - const baseUrl = await setup({ - ...withoutApi, - session: { - autoSave: false - } + test('should update the session when calling touchSession', async () => { + const config = { ...withoutApi, session: { autoSave: false } }; + const auth0Instance = initAuth0(config); + const loginRes = await appRouterLogin({ config }); + const res = await getResponse({ + url: '/api/auth/session', + config, + auth0Instance, + cookies: { appSession: loginRes.cookies.get('appSession').value }, + extraHandlers: { + async session(req: NextRequest) { + const res = new NextResponse(); + await auth0Instance.getSession(req, res); + await auth0Instance.touchSession(req, res); + return res; + } + } + }); + expect(res.headers.get('set-cookie')).not.toBeNull(); }); - const cookieJar = await login(baseUrl); - const [authCookie] = await cookieJar.getCookies(baseUrl); - await get(baseUrl, '/api/auth/me', { cookieJar }); - const [updatedAuthCookie] = await cookieJar.getCookies(baseUrl); - expect(updatedAuthCookie).toEqual(authCookie); }); + describe('page router', () => { + afterEach(teardown); - test('should update the session when calling touchSession', async () => { - const baseUrl = await setup({ - ...withoutApi, - session: { - autoSave: false - } + test('should not update the session when getting the session', async () => { + const baseUrl = await setup({ + ...withoutApi, + session: { + autoSave: false + } + }); + const cookieJar = await login(baseUrl); + const [authCookie] = await cookieJar.getCookies(baseUrl); + await get(baseUrl, '/api/auth/me', { cookieJar }); + const [updatedAuthCookie] = await cookieJar.getCookies(baseUrl); + expect(updatedAuthCookie).toEqual(authCookie); + }); + + test('should update the session when calling touchSession', async () => { + const baseUrl = await setup({ + ...withoutApi, + session: { + autoSave: false + } + }); + const cookieJar = await login(baseUrl); + const [authCookie] = await cookieJar.getCookies(baseUrl); + await get(baseUrl, '/api/touch-session', { cookieJar }); + const [updatedAuthCookie] = await cookieJar.getCookies(baseUrl); + expect(updatedAuthCookie).not.toEqual(authCookie); }); - const cookieJar = await login(baseUrl); - const [authCookie] = await cookieJar.getCookies(baseUrl); - await get(baseUrl, '/api/touch-session', { cookieJar }); - const [updatedAuthCookie] = await cookieJar.getCookies(baseUrl); - expect(updatedAuthCookie).not.toEqual(authCookie); - }); - test('should not throw when there is no session', async () => { - const baseUrl = await setup({ - ...withoutApi, - session: { - autoSave: false - } + test('should not throw when there is no session', async () => { + const baseUrl = await setup({ + ...withoutApi, + session: { + autoSave: false + } + }); + await expect(get(baseUrl, '/api/touch-session')).resolves.not.toThrow(); }); - await expect(get(baseUrl, '/api/touch-session')).resolves.not.toThrow(); }); }); diff --git a/tests/session/update-session.test.ts b/tests/session/update-session.test.ts index 4d8de02d2..537f63c3c 100644 --- a/tests/session/update-session.test.ts +++ b/tests/session/update-session.test.ts @@ -1,49 +1,104 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { cookies as nextCookies } from 'next/headers'; +import { CookieJar } from 'tough-cookie'; import { login, setup, teardown } from '../fixtures/setup'; -import { withoutApi } from '../fixtures/default-settings'; +import { withApi, withoutApi } from '../fixtures/default-settings'; import { get, post } from '../auth0-session/fixtures/helpers'; -import { CookieJar } from 'tough-cookie'; +import { getResponse, login as appRouterLogin, getSession } from '../fixtures/app-router-helpers'; +import { initAuth0 } from '../../src'; + +jest.mock('next/headers'); describe('update-user', () => { - afterEach(teardown); + describe('app router', () => { + test('should update session', async () => { + const loginRes = await appRouterLogin(); + const auth0Instance = initAuth0(withApi); + const res = await getResponse({ + url: '/api/auth/update', + auth0Instance, + cookies: { appSession: loginRes.cookies.get('appSession').value }, + extraHandlers: { + async update(req: NextRequest) { + const res = new NextResponse(); + const session = await auth0Instance.getSession(req, res); + await auth0Instance.updateSession(req, res, { ...session, user: { ...session?.user, foo: 'bar' } }); + return res; + } + } + }); + expect(res.status).toBe(200); + await expect(getSession(withApi, res)).resolves.toMatchObject({ user: expect.objectContaining({ foo: 'bar' }) }); + }); - test('should update session', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = await login(baseUrl); - const user = await get(baseUrl, '/api/auth/me', { cookieJar }); - expect(user).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' }); - await post(baseUrl, '/api/update-session', { cookieJar, body: { session: { foo: 'bar' } } }); - const updatedSession = await get(baseUrl, '/api/session', { cookieJar }); - expect(updatedSession).toMatchObject({ - foo: 'bar', - user: expect.objectContaining({ nickname: '__test_nickname__', sub: '__test_sub__' }) + test('should update session from a server component', async () => { + const loginRes = await appRouterLogin(); + // Note: An updated session from a React Server Component will not persist + // because you can't write to a cookie from a RSC in the current version of Next.js + // This test passes because we're mocking the dynamic `cookies` function. + jest.mocked(nextCookies).mockImplementation(() => loginRes.cookies); + const auth0Instance = initAuth0(withApi); + const res = await getResponse({ + url: '/api/auth/update', + auth0Instance, + cookies: { appSession: loginRes.cookies.get('appSession').value }, + extraHandlers: { + async update() { + // const res = new NextResponse(); + const session = await auth0Instance.getSession(); + await auth0Instance.updateSession({ ...session, user: { ...session?.user, foo: 'bar' } }); + return new NextResponse(); + } + } + }); + expect(res.status).toBe(200); + await expect(getSession(withApi, loginRes)).resolves.toMatchObject({ + user: expect.objectContaining({ foo: 'bar' }) + }); }); }); + describe('page router', () => { + afterEach(teardown); - test('should ignore updates if session is not defined', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = await login(baseUrl); - const user = await get(baseUrl, '/api/auth/me', { cookieJar }); - expect(user).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' }); - await post(baseUrl, '/api/update-session', { cookieJar, body: { session: undefined } }); - const updatedUser = await get(baseUrl, '/api/auth/me', { cookieJar }); - expect(updatedUser).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' }); - }); + test('should update session', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = await login(baseUrl); + const user = await get(baseUrl, '/api/auth/me', { cookieJar }); + expect(user).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' }); + await post(baseUrl, '/api/update-session', { cookieJar, body: { session: { foo: 'bar' } } }); + const updatedSession = await get(baseUrl, '/api/session', { cookieJar }); + expect(updatedSession).toMatchObject({ + foo: 'bar', + user: expect.objectContaining({ nickname: '__test_nickname__', sub: '__test_sub__' }) + }); + }); - test('should ignore updates if user is not logged in', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = new CookieJar(); - await expect(get(baseUrl, '/api/auth/me', { cookieJar })).resolves.toBe(''); - await post(baseUrl, '/api/update-session', { body: { session: { sub: 'foo' } }, cookieJar }); - await expect(get(baseUrl, '/api/auth/me', { cookieJar })).resolves.toBe(''); - }); + test('should ignore updates if session is not defined', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = await login(baseUrl); + const user = await get(baseUrl, '/api/auth/me', { cookieJar }); + expect(user).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' }); + await post(baseUrl, '/api/update-session', { cookieJar, body: { session: undefined } }); + const updatedUser = await get(baseUrl, '/api/auth/me', { cookieJar }); + expect(updatedUser).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' }); + }); - test('should ignore updates if user is not defined in update', async () => { - const baseUrl = await setup(withoutApi); - const cookieJar = await login(baseUrl); - const user = await get(baseUrl, '/api/auth/me', { cookieJar }); - expect(user).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' }); - await post(baseUrl, '/api/update-session', { cookieJar, body: { session: { user: undefined } } }); - const updatedUser = await get(baseUrl, '/api/auth/me', { cookieJar }); - expect(updatedUser).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' }); + test('should ignore updates if user is not logged in', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = new CookieJar(); + await expect(get(baseUrl, '/api/auth/me', { cookieJar })).resolves.toBe(''); + await post(baseUrl, '/api/update-session', { body: { session: { sub: 'foo' } }, cookieJar }); + await expect(get(baseUrl, '/api/auth/me', { cookieJar })).resolves.toBe(''); + }); + + test('should ignore updates if user is not defined in update', async () => { + const baseUrl = await setup(withoutApi); + const cookieJar = await login(baseUrl); + const user = await get(baseUrl, '/api/auth/me', { cookieJar }); + expect(user).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' }); + await post(baseUrl, '/api/update-session', { cookieJar, body: { session: { user: undefined } } }); + const updatedUser = await get(baseUrl, '/api/auth/me', { cookieJar }); + expect(updatedUser).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' }); + }); }); }); diff --git a/tests/utils/errors.test.ts b/tests/utils/errors.test.ts index da76adbb0..f1a2c9111 100644 --- a/tests/utils/errors.test.ts +++ b/tests/utils/errors.test.ts @@ -54,10 +54,6 @@ describe('AccessTokenError', () => { }); describe('HandlerError', () => { - test('should not be instance of itself', () => { - expect(new HandlerError({ code: '', message: '', name: '', cause: new Error() })).not.toBeInstanceOf(HandlerError); - }); - test('should set all required properties', () => { const code = 'foo'; const message = 'bar'; diff --git a/tests/utils/middleware-cookies.test.ts b/tests/utils/middleware-cookies.test.ts deleted file mode 100644 index 42be0a9ad..000000000 --- a/tests/utils/middleware-cookies.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * @jest-environment @edge-runtime/jest-environment - */ -import MiddlewareCookies from '../../src/utils/middleware-cookies'; -import { NextRequest, NextResponse } from 'next/server'; -import { serialize } from 'cookie'; - -const setup = (reqInit?: { headers: Record }): [NextRequest, NextResponse] => { - return [new NextRequest(new URL('http://example.com'), reqInit), NextResponse.next()]; -}; - -describe('cookie', () => { - it('should get all cookies', async () => { - const [req] = setup({ headers: { cookie: 'foo=bar; bar=baz;' } }); - expect(new MiddlewareCookies().getAll(req)).toMatchObject({ foo: 'bar', bar: 'baz' }); - }); - - it('should get all cookies in Next < 13.0.1', async () => { - const req = { - cookies: new Map([ - ['foo', 'bar'], - ['bar', 'baz'] - ]) - } as unknown as NextRequest; - expect(new MiddlewareCookies().getAll(req)).toMatchObject({ foo: 'bar', bar: 'baz' }); - }); - - it('should get a cookie by name', async () => { - const [req] = setup({ headers: { cookie: 'foo=bar; bar=baz;' } }); - expect(new MiddlewareCookies().getAll(req)['foo']).toEqual('bar'); - }); - - it('should set a cookie', async () => { - const [, res] = setup(); - const setter = new MiddlewareCookies(); - setter.set('foo', 'bar'); - setter.commit(res); - expect(res.headers.get('set-cookie')).toEqual('foo=bar'); - }); - - it('should set a cookie with opts', async () => { - const [, res] = setup(); - const setter = new MiddlewareCookies(); - setter.set('foo', 'bar', { httpOnly: true, sameSite: 'strict' }); - setter.commit(res); - expect(res.headers.get('set-cookie')).toEqual('foo=bar; HttpOnly; SameSite=Strict'); - }); - - it('should not overwrite existing set cookie', async () => { - const [, res] = setup(); - res.headers.set('set-cookie', 'foo=bar'); - const setter = new MiddlewareCookies(); - setter.set('baz', 'qux'); - setter.commit(res); - expect(res.headers.get('set-cookie')).toEqual(['foo=bar', 'baz=qux'].join(', ')); - }); - - it('should not overwrite existing set cookie with expiry', async () => { - const [, res] = setup(); - res.headers.set('set-cookie', serialize('foo', '', { expires: new Date(0) })); - const setter = new MiddlewareCookies(); - setter.set('baz', 'qux'); - setter.commit(res); - expect(res.headers.get('set-cookie')).toEqual( - ['foo=; Expires=Thu, 01 Jan 1970 00:00:00 GMT', 'baz=qux'].join(', ') - ); - if ('getAll' in res.headers) { - expect((res.headers.getAll as (header: string) => string[])('set-cookie')).toHaveLength(2); - } - }); - - it('should override existing cookies that equal name', async () => { - const [, res] = setup(); - res.headers.set('set-cookie', ['foo=bar', 'baz=qux'].join(', ')); - const setter = new MiddlewareCookies(); - setter.set('foo', 'qux'); - setter.commit(res, 'foo'); - expect(res.headers.get('set-cookie')).toEqual(['baz=qux', 'foo=qux'].join(', ')); - }); - - it('should override existing cookies that match name', async () => { - const [, res] = setup(); - res.headers.set('set-cookie', ['foo.1=bar', 'foo.2=baz'].join(', ')); - const setter = new MiddlewareCookies(); - setter.set('foo', 'qux'); - setter.commit(res, 'foo'); - expect(res.headers.get('set-cookie')).toEqual('foo=qux'); - }); - - it('should clear cookies', async () => { - const [, res] = setup(); - const setter = new MiddlewareCookies(); - setter.clear('foo'); - setter.commit(res); - expect(res.headers.get('set-cookie')).toEqual('foo=; Max-Age=0'); - }); -}); diff --git a/tests/utils/req-helpers.test.ts b/tests/utils/req-helpers.test.ts new file mode 100644 index 000000000..6900125c3 --- /dev/null +++ b/tests/utils/req-helpers.test.ts @@ -0,0 +1,34 @@ +import { Socket } from 'net'; +import { IncomingMessage } from 'http'; +import { NextRequest } from 'next/server'; +import { isRequest, isNextApiRequest } from '../../src/utils/req-helpers'; + +describe('req-helpers', () => { + const req = new Request(new URL('http://example.com')); + const reqNode16 = new Proxy(req, {}); + const reqNext = new NextRequest(new URL('http://example.com')); + const nodeReq = new IncomingMessage(new Socket()); + class NextApiRequest extends IncomingMessage { + constructor() { + super(new Socket()); + } + query = {}; + } + const nextApiReq = new NextApiRequest(); + + test('#isRequest', () => { + expect(isRequest(req)).toBe(true); + expect(isRequest(reqNode16)).toBe(true); + expect(isRequest(reqNext)).toBe(true); + expect(isRequest(nodeReq)).toBe(false); + expect(isRequest(nextApiReq)).toBe(false); + }); + + test('#isNextApiRequest', () => { + expect(isNextApiRequest(req)).toBe(false); + expect(isNextApiRequest(reqNode16)).toBe(false); + expect(isNextApiRequest(reqNext)).toBe(false); + expect(isNextApiRequest(nodeReq)).toBe(false); + expect(isNextApiRequest(nextApiReq)).toBe(true); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index e1c0070bd..7336ba556 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,7 @@ "sourceMap": true, "skipLibCheck": true, "strict": true, - "target": "es5", + "target": "ES2017", "useUnknownInCatchVariables": false, "lib": ["es6", "dom"], "downlevelIteration": true,