diff --git a/.editorconfig b/.editorconfig index 0018f03..f93062d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,5 +13,9 @@ trim_trailing_whitespace = true indent_style = space indent_size = 4 +[src/django_otp_webauthn/templates/*.html] +# Final newlines don't have to end up in the HTML output of end users +insert_final_newline = false + [*.md] trim_trailing_whitespace = false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8966080..da70e05 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,6 @@ env: FORCE_COLOR: "1" # Make tools pretty. TOX_TESTENV_PASSENV: FORCE_COLOR PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_NO_PYTHON_VERSION_WARNING: "1" jobs: qa_javascript: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8a3747d..2564fec 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,6 +6,12 @@ repos: hooks: - id: trailing-whitespace - id: end-of-file-fixer + exclude: | + (?x)^( + src/django_otp_webauthn/templates/django_otp_webauthn/auth_scripts.html| + src/django_otp_webauthn/templates/django_otp_webauthn/register_scripts.html| + src/django_otp_webauthn/templates/django_otp_webauthn/sync_signals_scripts.html + )$ - id: check-added-large-files - id: check-case-conflict - id: check-json diff --git a/CHANGELOG.md b/CHANGELOG.md index 65bb8ca..b51fb16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Nothing yet +- **New feature (experimental):** the browser will now be signaled to remove an unknown Passkey after a failed authentication attempt. + - The purpose of this is to improve user experience by removing Passkeys that are no longer valid from the users' device, stopping the user from being prompted to use this Passkey in the future. + - This is controlled by the new `OTP_WEBAUTHN_SIGNAL_UNKNOWN_CREDENTIAL` setting, which defaults to `True`. If set to `False`, the browser will not be signaled. + - It works on recent versions of Chrome, Edge and Safari but not Firefox (as of October 2025). + - Read more about the browser API used: [`PublicKeyCredential.signalUnknownCredential` on MDN](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/signalUnknownCredential_static). + - This feature is experimental because not all browsers support it properly yet. The specification is also still in draft status and may change in the future. +- **New feature (experimental)**: the `render_otp_webauthn_sync_signals_scripts` template tag has been added to allow updating user details stored in the browser when they change on the server side. + - The purpose of this is to improve user experience by keeping the user details (like display name) in sync between server and client, so that the browser can show the correct information when prompting the user to select a Passkey. + - It works on recent versions of Chrome, Edge and Safari but not Firefox (as of October 2025). + - This feature is experimental because not all browsers support it properly yet. The specification is also still in draft status and may change in the future. + - Read more about the browser APIs used: + - [`PublicKeyCredential.signalCurrentUserDetails` on MDN](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/signalCurrentUserDetails_static) + - [`PublicKeyCredential.signalAllAcceptedCredentials` on MDN](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/signalAllAcceptedCredentials_static) ### Changed diff --git a/client/src/auth.ts b/client/src/auth.ts index fa9065b..4a87758 100644 --- a/client/src/auth.ts +++ b/client/src/auth.ts @@ -85,10 +85,11 @@ import { return; } + const responseJson = await response.json(); let attResp; try { attResp = await startAuthentication({ - optionsJSON: await response.json(), + optionsJSON: responseJson, useBrowserAutofill: true, }); } catch (error: unknown) { @@ -149,6 +150,9 @@ import { // Handle failed verification if (!verificationResp.ok && "detail" in verificationJSON) { + if (verificationResp.status === 404) { + signalPasskeyMissing(attResp.rawId, responseJson.rpId); + } loginField.dispatchEvent( new CustomEvent(EVENT_VERIFICATION_FAILED, { detail: { @@ -232,6 +236,8 @@ import { }, }); + const responseJson = await response.json(); + if (!response.ok) { await setPasskeyVerifyState({ buttonDisabled: false, @@ -258,7 +264,7 @@ import { try { attResp = await startAuthentication({ - optionsJSON: await response.json(), + optionsJSON: responseJson, useBrowserAutofill: false, }); } catch (error: unknown) { @@ -284,6 +290,17 @@ import { status: gettext("Verification canceled or not allowed."), }); break; + case "SecurityError": + await setPasskeyVerifyState({ + buttonDisabled: false, + buttonLabel, + requestFocus: true, + statusEnum: StatusEnum.SECURITY_ERROR, + status: gettext( + "Passkey authentication failed. A technical problem prevents the verification process for beginning. Please try another method.", + ), + }); + break; default: await setPasskeyVerifyState({ buttonDisabled: false, @@ -363,6 +380,9 @@ import { const verificationJSON = await verificationResp.json(); if (!verificationResp.ok) { + if (verificationResp.status === 404 && attResp) { + signalPasskeyMissing(attResp.rawId, responseJson.rpId); + } const msg = verificationJSON.detail || gettext("Verification failed. An unknown error occurred."); @@ -471,6 +491,48 @@ import { } } + /* Uses the `PublicKeyCredential.signalUnknownCredential` API to inform the browser + * that the Passkey that was used is not recognized by the server, prompting the browser to delete it from its stored credentials. + * This function is a no-op if the API is not supported by the browser. + */ + async function signalPasskeyMissing( + credentialId: string, + rpId: string, + ): Promise { + if (!config.removeUnknownCredential) { + console.trace( + "Not signaling unknown credential to the browser as per configuration.", + ); + return; + } + if (!("signalUnknownCredential" in PublicKeyCredential)) { + console.trace( + "PublicKeyCredential.signalUnknownCredential is not supported by this browser. Won't signal.", + ); + return; + } + + try { + await (PublicKeyCredential as any).signalUnknownCredential({ + rpId, + credentialId, + }); + console.trace( + // Important: 'Credential not found' is used as a needle for automated tests that check that the signaling happened. + "Credential not found. Requested browser remove credential.", + { + rpId, + credentialId, + }, + ); + } catch (error) { + console.error( + "Error while signaling unknown credential to the browser", + error, + ); + } + } + async function setPasskeyVerificationVisible( visible: boolean, ): Promise { diff --git a/client/src/sync_signals.ts b/client/src/sync_signals.ts new file mode 100644 index 0000000..7af38f0 --- /dev/null +++ b/client/src/sync_signals.ts @@ -0,0 +1,93 @@ +import { SyncSignalConfig } from "./types"; + +// Extend the PublicKeyCredentialConstructor interface to include the sync signal methods. +// These don't exist currently in TypeScript's lib.dom.d.ts, so we declare them here. +// At some point this can likely be removed. +interface PublicKeyCredentialConstructor { + signalCurrentUserDetails?(details: { + rpId?: string; + userId?: string; + name?: string; + displayName?: string; + }): Promise; + signalAllAcceptedCredentials(options: { + rpId: string; + userId: string; + allAcceptedCredentialIds: string[]; + }): Promise; +} + +// augment the global constructor type +declare var PublicKeyCredential: PublicKeyCredentialConstructor; + +/** + * Client-side sync signals for WebAuthn credentials. + * + * Reads JSON configuration from a script[id="webauthn-sync-signals-config"] and + * calls the PublicKeyCredential.signalCurrentUserDetails and + * PublicKeyCredential.signalAllAcceptedCredentials browser APIs to update user details + * and to hide removed credentials, so they won't be shown in future authentication prompts. + */ +(() => async () => { + const configScript = document.getElementById( + "otp_webauthn_sync_signals_config", + ); + if (!configScript) { + return; + } + + const config = JSON.parse( + configScript.textContent || "{}", + ) as SyncSignalConfig; + if (!config) { + return; + } + // Remove the config script tag from the DOM now that we've read it + // don't make it available to any other scripts for security/privacy reasons + configScript.remove(); + + // Signal current user details + if ( + typeof PublicKeyCredential === "undefined" || + typeof PublicKeyCredential.signalCurrentUserDetails !== "function" + ) { + console.warn( + "PublicKeyCredential.signalCurrentUserDetails is not supported by this browser.", + ); + return; + } else { + const payload = { + rpId: config.rpId, + userId: config.userId, + name: config.name, + displayName: config.displayName, + }; + await PublicKeyCredential.signalCurrentUserDetails(payload); + console.log( + "[WebAuthn] Signaled current user details to the browser.", + payload, + ); + } + + // Signal all accepted credentials + if ( + typeof PublicKeyCredential === "undefined" || + typeof PublicKeyCredential.signalAllAcceptedCredentials !== "function" + ) { + console.warn( + "PublicKeyCredential.signalAllAcceptedCredentials is not supported by this browser.", + ); + return; + } else { + const payload = { + rpId: config.rpId, + userId: config.userId, + allAcceptedCredentialIds: config.credentialIds, + }; + await PublicKeyCredential.signalAllAcceptedCredentials(payload); + console.log( + "[WebAuthn] Signaled accepted credentials to the browser.", + payload, + ); + } +})()(); diff --git a/client/src/types.ts b/client/src/types.ts index 9e59853..48dd39e 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -32,4 +32,13 @@ export type Config = { nextFieldSelector: string; csrfToken: string; + removeUnknownCredential: boolean; +}; + +export type SyncSignalConfig = { + rpId: string; + userId: string; + name: string; + displayName: string; + credentialIds: string[]; }; diff --git a/client/webpack.base.config.ts b/client/webpack.base.config.ts index fabd6ad..40f6c59 100644 --- a/client/webpack.base.config.ts +++ b/client/webpack.base.config.ts @@ -6,6 +6,7 @@ const config: Configuration = { entry: { auth: "./client/src/auth.ts", register: "./client/src/register.ts", + sync_signals: "./client/src/sync_signals.ts", }, module: { rules: [ diff --git a/docs/contributing/index.rst b/docs/contributing/index.rst index 3a4ff23..63bfca4 100644 --- a/docs/contributing/index.rst +++ b/docs/contributing/index.rst @@ -123,7 +123,7 @@ For test coverage, use these commands: .. code-block:: console - coverage run manage.py test + coverage run -m pytest coverage report Generate a visual HTML report in the htmlcov directory with the following command: diff --git a/docs/getting_started/quickstart.rst b/docs/getting_started/quickstart.rst index 669e545..eb9de8d 100644 --- a/docs/getting_started/quickstart.rst +++ b/docs/getting_started/quickstart.rst @@ -248,3 +248,11 @@ Once you’ve done this, you will see the following on your login page: * a **Register Passkey** button on the login page * a **Login using a Passkey** button on the login page + +Next steps +---------- + +Now that you have Django OTP WebAuthn set up, you can explore additional features such as: + +* :ref:`keeping_passkeys_in_sync`, to improve your users' experience. +* :ref:`configure_related_origins`, for when your application is active on multiple domains and you want to share Passkeys across them. diff --git a/docs/how_to_guides/index.rst b/docs/how_to_guides/index.rst index 08cd96e..7a39e2f 100644 --- a/docs/how_to_guides/index.rst +++ b/docs/how_to_guides/index.rst @@ -25,6 +25,10 @@ Here are what you will find in this section: Learn how to configure WebAuthn to work across multiple domains. For example, if your main application runs on ``https://example.com`` and you have a localized version on ``https://example.co.uk``. + .. grid-item-card:: :ref:`Keeping Passkeys up-to-date with changing user details ` + + Learn how to keep Passkey user details saved in users' browsers up-to-date when details like email or username change. + .. toctree:: :maxdepth: 2 :hidden: @@ -33,3 +37,4 @@ Here are what you will find in this section: Customize helper class Customize models Configure related origins + Keep passkeys up-to-date diff --git a/docs/how_to_guides/keeping_passkeys_in_sync.rst b/docs/how_to_guides/keeping_passkeys_in_sync.rst new file mode 100644 index 0000000..aa892ba --- /dev/null +++ b/docs/how_to_guides/keeping_passkeys_in_sync.rst @@ -0,0 +1,100 @@ +.. _keep_passkeys_up_to_date: + +Keep passkeys up to date with user details +========================================== + +When users update their details, such as their email address or username, the changes don't automatically reflect in their stored :term:`passkeys `. This can cause confusion because outdated information still appears during authentication. + +To keep passkeys up to date with user details, Django OTP WebAuthn provides the ``render_otp_webauthn_sync_signals_scripts`` template tag. It calls the appropriate browser APIs to update stored passkeys. + + +.. _what_is_render_otp_webauthn_sync_signals_scripts: + +What is ``render_otp_webauthn_sync_signals_scripts``? +----------------------------------------------------- + +``render_otp_webauthn_sync_signals_scripts`` is a lightweight template tag that you can add to your base template to check for a key in the session. The key indicates that user details have changed. If the template tag detects the key, it renders the JavaScript needed to update the stored passkeys in the user's browser. + +To use the ``render_otp_webauthn_sync_signals_scripts`` template tag, add it near the bottom of your base template, just before the closing ```` tag. This ensures it doesn't block the initial page load, but still executes once the page is fully loaded. + +.. code-block:: html + + + {% load otp_webauthn %} + ... + + ... + + ... + ... + {% render_otp_webauthn_sync_signals_scripts %} + + + +How ``render_otp_webauthn_sync_signals_scripts`` works +------------------------------------------------------ + +When a user authenticates using a passkey or registers a new one, the default authentication and registration views call the ``django_otp_webauthn.utils.request_user_details_sync`` function. This function sets a flag in the user's session, indicating that their details require synchronization. + +On the next page load, when the templates are rendered, ``render_otp_webauthn_sync_signals_scripts`` checks this flag and outputs JavaScript that uses the appropriate WebAuthn API to update the stored passkeys with the latest user details. After the synchronization is complete, the flag is cleared from the session to prevent rendering the JavaScript on subsequent page loads. + +Remove deleted Passkeys from the browser +---------------------------------------- + +When a user removes a passkey from your application, the browser doesn't automatically update its stored passkeys. So, the deleted passkey may still appear the next time the user tries to authenticate. + +To handle, call the ``request_user_details_sync`` utility function after you remove a passkey. This ensures that on the next page load, the browser receives the information it needs to remove the deleted passkey from storage. Depending on the browser’s implementation, the user may be prompted to confirm the removal. + +If the user tries to use a removed passkey during authentication, the browser automatically determines that the passkey is +no longer valid through the `PublicKeyCredential.signalUnknownCredential +`_ +WebAuthn API. + + +Trigger user details synchronization +------------------------------------ + +To trigger the user details synchronization process, call the ``request_user_details_sync`` utility function whenever a user updates their details. For example, if you have a view that lets users change their email address or username, add a call to this function after the update succeeds. This ensures that the next time the user loads a page, their passkeys are updated with the new details. + +.. code-block:: py + + # your_app/views.py + from django_otp_webauthn.utils import request_user_details_sync + + def update_user_details(request): + if request.method == "POST": + # Assume we have a form that updates user details + form = UserDetailsForm(request.POST, instance=request.user) + if form.is_valid(): + form.save() + # Request user details sync after updating details + request_user_details_sync(request) + # Redirect or render success response + return redirect("profile") + else: + form = UserDetailsForm(instance=request.user) + return render(request, "update_user_details.html", {"form": form}) + +The synchronization happens in the background, so you won't see any messages or indicators when it occurs. If needed, you can check your browser’s console for any errors or logs related to the synchronization process. + +How does this work from a technical perspective? +------------------------------------------------ + +The ``render_otp_webauthn_sync_signals_scripts`` template tag is a convenience +wrapper that ends up calling the +``PublicKeyCredential.signalAllAcceptedCredentials`` and +``PublicKeyCredential.signalCurrentUserDetails`` WebAuthn browser APIs. It +automatically retrieves a list of currently registered credentials and the +current user details in the format these APIs expect. + +For more information about these APIs, refer to the following resources: + +- `PublicKeyCredential.signalCurrentUserDetails `_ +- `PublicKeyCredential.signalAllAcceptedCredentials `_ + +.. note:: + + As of November 2025, these APIs are still relatively new and don't enjoy + broad support from all browsers. Please see `Web authentication signal + methods on caniuse.com `_ for the + most up-to-date browser support information. diff --git a/docs/wordlist.txt b/docs/wordlist.txt index beac3c0..3683e96 100644 --- a/docs/wordlist.txt +++ b/docs/wordlist.txt @@ -1,5 +1,6 @@ AbstractWebAuthnAttestation AbstractWebAuthnCredential +APIs auth authenticator authenticator's @@ -13,42 +14,47 @@ BeginCredentialRegistrationView biometric biometrics Caddy +caniuse ccTLD CompleteCredentialAuthenticationView CompleteCredentialRegistrationView cryptographic django -Frontend frontend +Frontend Furo gettext github -http htmlcov +http HTTPS js JSON localhost OneToOneField +otp OTP OTPMiddleware -otp passwordless pradyunsg pre -PyPI +PublicKeyCredential py +PyPI Quickstart +residentKey +reStructuredText rpID rpIDs -reStructuredText -residentKey +signalAllAcceptedCredentials +signalCurrentUserDetails +signalUnknownCredential Stormbase subclasses subclassing untrusted -WebAuthn webauthn +WebAuthn WebAuthn's WebAuthnCredential WebAuthnUserHandle diff --git a/package.json b/package.json index 2872459..908fcd6 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@babel/register": "^7.23.7", "@types/node": "^24.3.0", "all-contributors-cli": "^6.26.1", - "esbuild": "^0.25.0", + "esbuild": "^0.27.0", "prettier": "3.6.2", "ts-loader": "^9.5.1", "typescript": "^5.4.5", diff --git a/sandbox/templates/admin/base.html b/sandbox/templates/admin/base.html new file mode 100644 index 0000000..cb29904 --- /dev/null +++ b/sandbox/templates/admin/base.html @@ -0,0 +1,7 @@ +{% extends "admin/base.html" %} +{% load otp_webauthn %} + +{% block extrabody %} + {{ block.super }} + {% render_otp_webauthn_sync_signals_scripts %} +{% endblock extrabody %} diff --git a/sandbox/templates/base.html b/sandbox/templates/base.html index 4e29a2a..0a6fd53 100644 --- a/sandbox/templates/base.html +++ b/sandbox/templates/base.html @@ -1,4 +1,4 @@ -{% load static %} +{% load static otp_webauthn %} @@ -10,5 +10,6 @@ {% block content %}{% endblock content %} + {% render_otp_webauthn_sync_signals_scripts %} diff --git a/src/django_otp_webauthn/locale/nl/LC_MESSAGES/django.po b/src/django_otp_webauthn/locale/nl/LC_MESSAGES/django.po index 5a2c7eb..cd82b6c 100644 --- a/src/django_otp_webauthn/locale/nl/LC_MESSAGES/django.po +++ b/src/django_otp_webauthn/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-27 21:44+0000\n" +"POT-Creation-Date: 2025-11-22 13:46+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,19 +18,19 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: src/django_otp_webauthn/admin.py:41 +#: src/django_otp_webauthn/admin.py:38 msgid "COSE public key" msgstr "Publieke COSE-sleutel" -#: src/django_otp_webauthn/admin.py:63 +#: src/django_otp_webauthn/admin.py:62 msgid "Identity" msgstr "Identiteit" -#: src/django_otp_webauthn/admin.py:69 +#: src/django_otp_webauthn/admin.py:68 msgid "Meta" msgstr "Meta-gegevens" -#: src/django_otp_webauthn/admin.py:75 +#: src/django_otp_webauthn/admin.py:74 msgid "WebAuthn credential data" msgstr "WebAuthn-gegevens" @@ -68,26 +68,26 @@ msgstr "" "De Passkey die u probeert te gebruiken is niet gevonden. Misschien is deze " "verwijderd?" -#: src/django_otp_webauthn/models.py:79 -msgid "WebAuthn attestation" -msgstr "WebAuthn attestatie" - -#: src/django_otp_webauthn/models.py:80 -msgid "WebAuthn attestations" -msgstr "WebAuthn attestaties" - -#: src/django_otp_webauthn/models.py:96 +#: src/django_otp_webauthn/models.py:86 msgid "format" msgstr "formaat" -#: src/django_otp_webauthn/models.py:100 +#: src/django_otp_webauthn/models.py:90 msgid "data" msgstr "data" -#: src/django_otp_webauthn/models.py:104 +#: src/django_otp_webauthn/models.py:94 msgid "client data JSON" msgstr "client data JSON" +#: src/django_otp_webauthn/models.py:106 +msgid "WebAuthn attestation" +msgstr "WebAuthn attestatie" + +#: src/django_otp_webauthn/models.py:107 +msgid "WebAuthn attestations" +msgstr "WebAuthn attestaties" + #: src/django_otp_webauthn/models.py:148 msgid "WebAuthn credential" msgstr "WebAuthn credential" @@ -140,22 +140,22 @@ msgstr "gehashte credential id" msgid "discoverable" msgstr "zichtbaar" -#: src/django_otp_webauthn/models.py:404 +#: src/django_otp_webauthn/models.py:407 msgid "credential" msgstr "credential" -#: src/django_otp_webauthn/models.py:429 -msgid "WebAuthn user handle" -msgstr "WebAuthn user handle" - -#: src/django_otp_webauthn/models.py:430 -msgid "WebAuthn user handles" -msgstr "WebAuthn user handles" - -#: src/django_otp_webauthn/models.py:437 +#: src/django_otp_webauthn/models.py:433 msgid "handle hex" msgstr "handle hex" -#: src/django_otp_webauthn/models.py:447 +#: src/django_otp_webauthn/models.py:443 msgid "user" msgstr "gebruiker" + +#: src/django_otp_webauthn/models.py:447 +msgid "WebAuthn user handle" +msgstr "WebAuthn user handle" + +#: src/django_otp_webauthn/models.py:448 +msgid "WebAuthn user handles" +msgstr "WebAuthn user handles" diff --git a/src/django_otp_webauthn/locale/nl/LC_MESSAGES/djangojs.po b/src/django_otp_webauthn/locale/nl/LC_MESSAGES/djangojs.po index e4fab57..d3874e1 100644 --- a/src/django_otp_webauthn/locale/nl/LC_MESSAGES/djangojs.po +++ b/src/django_otp_webauthn/locale/nl/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-05-11 17:52+0200\n" +"POT-Creation-Date: 2025-11-22 13:46+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -40,6 +40,14 @@ msgstr "Verificatie afgebroken." msgid "Verification canceled or not allowed." msgstr "Verificatie geannuleerd of niet toegestaan." +msgid "" +"Passkey authentication failed. A technical problem prevents the verification " +"process for beginning. Please try another method." +msgstr "" +"Inloggen met Passkey mislukt. Er is een technisch probleem opgetreden " +"waardoor het proces niet kan worden gestart. Probeer een andere " +"authenticatiemethode." + msgid "Verification failed. An unknown error occurred." msgstr "Verificatie mislukt. Er is een onbekende fout opgetreden." diff --git a/src/django_otp_webauthn/models.py b/src/django_otp_webauthn/models.py index ad25767..0daa5af 100644 --- a/src/django_otp_webauthn/models.py +++ b/src/django_otp_webauthn/models.py @@ -13,7 +13,7 @@ from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ from django_otp.models import Device, DeviceManager, TimestampMixin -from webauthn.helpers import parse_attestation_object +from webauthn.helpers import bytes_to_base64url, parse_attestation_object from webauthn.helpers.structs import ( AttestationObject, AuthenticatorTransport, @@ -383,6 +383,11 @@ def get_webauthn_helper(cls, request: HttpRequest): helper = import_string(app_settings.OTP_WEBAUTHN_HELPER_CLASS) return helper(request=request) + @property + def credential_id_base64url(self) -> str: + """Return the base64url-encoded credential id.""" + return bytes_to_base64url(self.credential_id) + class WebAuthnCredential(AbstractWebAuthnCredential): """A OTP device that validates against a user's credential. @@ -454,6 +459,10 @@ def save(self, *args, **kwargs): def handle(self) -> bytes: return bytes.fromhex(self.handle_hex) + @property + def handle_base64url(self) -> str: + return bytes_to_base64url(self.handle) + @classmethod def get_handle_for_user(cls, user: AbstractBaseUser) -> bytes: """Return the user handle for the given user.""" diff --git a/src/django_otp_webauthn/settings.py b/src/django_otp_webauthn/settings.py index 5cb29e4..9afe2c6 100644 --- a/src/django_otp_webauthn/settings.py +++ b/src/django_otp_webauthn/settings.py @@ -106,6 +106,14 @@ class AppSettings: accessibility guidelines regarding timeouts. See https://www.w3.org/TR/WCAG22/#enough-time. """ + OTP_WEBAUTHN_SIGNAL_UNKNOWN_CREDENTIAL = True + """If ``True``, when the user tries to + authenticate using a credential that does not exist on the + server, the client-side script will signal the browser the Passkey does not + exist anymore. It is up to browsers to implement this signal, but the + intention is for them to (automatically) remove the Passkey from the user's + device and not have show it as an option to the user again.""" + def __getattribute__(self, __name: str): # Check if a Django project settings should override the app default. # In order to avoid returning any random properties of the django settings, we inspect the prefix firstly. diff --git a/src/django_otp_webauthn/templates/django_otp_webauthn/auth_scripts.html b/src/django_otp_webauthn/templates/django_otp_webauthn/auth_scripts.html index 8a3a52e..fcc1dce 100644 --- a/src/django_otp_webauthn/templates/django_otp_webauthn/auth_scripts.html +++ b/src/django_otp_webauthn/templates/django_otp_webauthn/auth_scripts.html @@ -2,4 +2,4 @@ {{ configuration|json_script:"otp_webauthn_config"}} -{% endspaceless %} +{% endspaceless %} \ No newline at end of file diff --git a/src/django_otp_webauthn/templates/django_otp_webauthn/register_scripts.html b/src/django_otp_webauthn/templates/django_otp_webauthn/register_scripts.html index 102f6ad..a8548a1 100644 --- a/src/django_otp_webauthn/templates/django_otp_webauthn/register_scripts.html +++ b/src/django_otp_webauthn/templates/django_otp_webauthn/register_scripts.html @@ -2,4 +2,4 @@ {{ configuration|json_script:"otp_webauthn_config"}} -{% endspaceless %} +{% endspaceless %} \ No newline at end of file diff --git a/src/django_otp_webauthn/templates/django_otp_webauthn/sync_signals_scripts.html b/src/django_otp_webauthn/templates/django_otp_webauthn/sync_signals_scripts.html new file mode 100644 index 0000000..f19ac70 --- /dev/null +++ b/src/django_otp_webauthn/templates/django_otp_webauthn/sync_signals_scripts.html @@ -0,0 +1,6 @@ +{% load static %}{% spaceless %} + {% if configuration %} + {{ configuration|json_script:"otp_webauthn_sync_signals_config"}} + + {% endif %} +{% endspaceless %} \ No newline at end of file diff --git a/src/django_otp_webauthn/templatetags/otp_webauthn.py b/src/django_otp_webauthn/templatetags/otp_webauthn.py index 89dd762..3c307fd 100644 --- a/src/django_otp_webauthn/templatetags/otp_webauthn.py +++ b/src/django_otp_webauthn/templatetags/otp_webauthn.py @@ -2,9 +2,13 @@ from django.http import HttpRequest from django.middleware import csrf from django.urls import reverse +from webauthn.helpers import bytes_to_base64url +from django_otp_webauthn.helpers import WebAuthnHelper from django_otp_webauthn.settings import app_settings +from django_otp_webauthn.utils import get_credential_model +WebAuthnCredential = get_credential_model() register = template.Library() @@ -15,6 +19,7 @@ def get_configuration(request: HttpRequest, extra_options: dict = None) -> dict: "autocompleteLoginFieldSelector": None, "nextFieldSelector": "input[name='next']", "csrfToken": csrf.get_token(request), + "removeUnknownCredential": app_settings.OTP_WEBAUTHN_SIGNAL_UNKNOWN_CREDENTIAL, "beginAuthenticationUrl": reverse( "otp_webauthn:credential-authentication-begin" ), @@ -55,3 +60,53 @@ def render_otp_webauthn_register_scripts(context): request = context["request"] context["configuration"] = get_configuration(request) return context + + +@register.inclusion_tag( + "django_otp_webauthn/sync_signals_scripts.html", takes_context=True +) +def render_otp_webauthn_sync_signals_scripts(context): + """Renders a script that calls the + ``PublicKeyCredential.signalCurrentUserDetails`` and + ``PublicKeyCredential.signalAllAcceptedCredentials`` browser apis to update user details + and to hide removed credentials, so they won't be shown in future authentication prompts. + + These scripts are only rendered if the user is authenticated and if a sync is needed. + + A sync can be requested by calling the ``django_otp_webauthn.utils.set_webauthn_sync_signal`` utility function. + """ + request = context["request"] + + # Bail out if the user is not authenticated + if not request.user.is_authenticated: + return {} + + # Bail out if no sync is needed + if "otp_webauthn_sync_needed" not in request.session: + return {} + + # Consume the sync needed flag + request.session.pop("otp_webauthn_sync_needed") + + helper: WebAuthnHelper = WebAuthnCredential.get_webauthn_helper(request) + user_entity = helper.get_user_entity(request.user) + rp_id = helper.get_relying_party_domain() + + # Convert all credential ids to base64url-encoded strings, as is needed by + # the WebAuthn API + credential_ids = [ + bytes_to_base64url(descriptor.id) + for descriptor in WebAuthnCredential.get_credential_descriptors_for_user( + request.user + ) + ] + + # The data the client-side script uses to signal the browser + context["configuration"] = { + "rpId": rp_id, + "userId": bytes_to_base64url(user_entity.id), + "name": user_entity.name, + "displayName": user_entity.display_name, + "credentialIds": credential_ids, + } + return context diff --git a/src/django_otp_webauthn/utils.py b/src/django_otp_webauthn/utils.py index 748ae7a..cca2adc 100644 --- a/src/django_otp_webauthn/utils.py +++ b/src/django_otp_webauthn/utils.py @@ -5,6 +5,7 @@ from django.apps import apps from django.core.exceptions import ImproperlyConfigured +from django.http import HttpRequest from django.urls import reverse from webauthn.helpers import exceptions as pywebauthn_exceptions @@ -149,3 +150,19 @@ def get_attestation_model_string() -> str: """Returns the string representation of the WebAuthnAttestation model that is active in this project.""" return app_settings.OTP_WEBAUTHN_ATTESTATION_MODEL + + +def request_user_details_sync(request: HttpRequest) -> None: + """Marks the current session as needing user details and accepted credentials + synchronization with the browser. + + The implementation will cause the `{% render_otp_webauthn_sync_signals_scripts %}` + template tag to render a script that calls the `PublicKeyCredential.signalCurrentUserDetails` + and `PublicKeyCredential.signalAllAcceptedCredentials` browser apis on the + next page load. + + This ensures the users' browser is made aware of changes to user details and + credentials. + """ + request.session["otp_webauthn_sync_needed"] = True + request.session.save() diff --git a/src/django_otp_webauthn/views.py b/src/django_otp_webauthn/views.py index 173f08b..42f5b04 100644 --- a/src/django_otp_webauthn/views.py +++ b/src/django_otp_webauthn/views.py @@ -21,7 +21,11 @@ from django_otp_webauthn import exceptions from django_otp_webauthn.models import AbstractWebAuthnCredential from django_otp_webauthn.settings import app_settings -from django_otp_webauthn.utils import get_credential_model, rewrite_exceptions +from django_otp_webauthn.utils import ( + get_credential_model, + request_user_details_sync, + rewrite_exceptions, +) WebAuthnCredential = get_credential_model() User = get_user_model() @@ -153,6 +157,8 @@ def post(self, *args, **kwargs): # change that indicator. if not self.request.user.is_verified(): otp_login(self.request, device) + + request_user_details_sync(self.request) return Response(data={"id": device.pk}, content_type="application/json") @@ -245,6 +251,7 @@ def complete_auth(self, device: AbstractWebAuthnCredential) -> AbstractBaseUser: # Mark the user as having passed verification otp_login(self.request, device) + request_user_details_sync(self.request) success_url_allowed_hosts = set() diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 286edce..7f6029d 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -1,4 +1,5 @@ import random +import re from concurrent.futures import Future import pytest @@ -78,6 +79,87 @@ def _return(): return _wait_for_javascript_event +@pytest.fixture +def wait_for_console_message(page): + """Returns a function that blocks until a certain console message has been posted. + + Only saves the first matching message. You can use this fixture multiple times + in a test to wait for multiple different messages. + + Args: + message_text_regex: A regex string to match against the console message text. + level: The console message level to match against (e.g. "log", "error", etc.) + + Returns: + A function that, when called, blocks until the console message is seen, and then returns the playwright ConsoleMessage object. + See: https://playwright.dev/python/docs/api/class-consolemessage + + Usage: + def my_testcase(live_server, page, wait_for_console_message): + # Set up the listener to capture the console message + await_message = wait_for_console_message(r"hello", level="info") + + page.goto(live_server.url) + # Do stuff that triggers the console message + page.evaluate("console.log('hello', 42, { foo: 'bar' })") + + # Wait for and get the console message. This will block until the message is seen, or raise a timeout error. + msg = await_message() + + # Now you can inspect the msg object as needed + msg.args[0].json_value() # hello + msg.args[1].json_value() # 42 + """ + + waiters: list[dict] = [] + buffer: list = [] + + def _on_console(msg): + # store raw messages so later-created waiters can match them too + buffer.append(msg) + # satisfy any outstanding waiters + for waiter in list(waiters): + try: + if not waiter["future"].done() and waiter["regex"].search(msg.text): + waiter["future"].set_result(msg) + except Exception: # noqa: S110, BLE001 - yes, do catch naked exceptions and don't log them + # guard handler from raising (handlers should be best-effort) + pass + + # register the listener immediately so we don't miss early messages + page.on("console", _on_console) + + def _factory(pattern, timeout: int = 5000, level: str | None = None): + regex = re.compile(pattern) if isinstance(pattern, (str, bytes)) else pattern + # If we've already seen a matching message, return it immediately. + for msg in buffer: + if regex.search(msg.text) and (level is None or msg.type == level): + + def _immediate(msg=msg): + return msg + + return _immediate + + future = Future() + waiter = {"regex": regex, "future": future} + waiters.append(waiter) + + def _waiter(): + try: + # concurrent.futures.Future expects seconds, but our testing convention is milliseconds + sec = timeout / 1000.0 if timeout is not None else None + return future.result(timeout=sec) + finally: + try: + waiters.remove(waiter) + except ValueError: + pass + + return _waiter + + return _factory + + @pytest.fixture def virtual_authenticator(cdpsession): def _get_authenticator(authenticator: VirtualAuthenticator): @@ -106,22 +188,68 @@ def _get_credential(authenticator_id: str, credential: VirtualCredential): @pytest.fixture -def playwright_force_login(live_server, context): - """Fixture that forces the given user to be logged in by manipulating the session cookie.""" +def playwright_manipulate_session(live_server, context): + """Fixture that allows direct manipulation of the session. - def _playwright_force_login(user): - login_helper = DjangoTestClient() - login_helper.force_login(user) + Usage: + def my_pytest_playwright_testcase(live_server, page, playwright_manipulate_session): + # Update or add a specific key: + playwright_manipulate_session(lambda session: session.update({"some_key": "some_value"})) + + # Remove a specific key: + playwright_manipulate_session(lambda session: session.pop("some_key", None)) + + # Clear the session entirely: + playwright_manipulate_session(lambda session: session.clear()) + + # Navigate the live server as normal, with the updated session: + page.goto(live_server.url) + """ + + def _playwright_manipulate_session(modify_func): + # Get the session id cookie from the browser context + session_cookie_value = None + for cookie in context.cookies(): + if cookie["name"] == "sessionid": + session_cookie_value = cookie["value"] + break + + # Instantiate a Django test client to gain access to the SessionStore + # and load the session if we already have one + client = DjangoTestClient() + if session_cookie_value: + client.cookies["sessionid"] = session_cookie_value + + session = client.session + # let the caller modify the session + modify_func(session) + + session.save() + # Update the browser context with the new session id cookie context.add_cookies( [ { "url": live_server.url, "name": "sessionid", - "value": login_helper.cookies["sessionid"].value, + "value": session.session_key, } ] ) - return user + + return _playwright_manipulate_session + + +@pytest.fixture +def playwright_force_login(live_server, context, playwright_manipulate_session): + """Fixture that forces the given user to be logged in by manipulating the session cookie.""" + + def _playwright_force_login(user): + login_helper = DjangoTestClient() + login_helper.force_login(user) + + playwright_manipulate_session( + lambda session: session.update(login_helper.session.items()) + ) return _playwright_force_login diff --git a/tests/e2e/fixtures.py b/tests/e2e/fixtures.py index 345ce5d..9a69de8 100644 --- a/tests/e2e/fixtures.py +++ b/tests/e2e/fixtures.py @@ -13,7 +13,7 @@ class StatusEnum(enum.StrEnum): UNKNOWN_ERROR = "unknown-error" STATE_ERROR = "state-error" SECURITY_ERROR = "security-error" - GET_OPTIONS_FAILED = ("get-options-failed",) + GET_OPTIONS_FAILED = "get-options-failed" ABORTED = "aborted" NOT_ALLOWED_OR_ABORTED = "not-allowed-or-aborted" SERVER_ERROR = "server-error" diff --git a/tests/e2e/test_credential_authentication.py b/tests/e2e/test_credential_authentication.py index 7652023..df55b4d 100644 --- a/tests/e2e/test_credential_authentication.py +++ b/tests/e2e/test_credential_authentication.py @@ -1,5 +1,6 @@ from django.urls import reverse from playwright.sync_api import expect +from webauthn import base64url_to_bytes from tests.e2e.fixtures import StatusEnum, VirtualAuthenticator, VirtualCredential from tests.factories import WebAuthnCredentialFactory @@ -40,6 +41,129 @@ def test_authenticate_credential__internal_passwordless_manual( assert user.username in page.content() +def test_authenticate_credential__manual_signal_no_credential( + live_server, + django_db_serialized_rollback, + page, + user, + virtual_authenticator, + virtual_credential, + wait_for_javascript_event, + wait_for_console_message, +): + """Verify that manually using the 'Authenticate with Passkey' button with a credential + that does not exist signals an unknown credential to the browser.""" + + credential = WebAuthnCredentialFactory(user=user, discoverable=True) + authenticator = virtual_authenticator(VirtualAuthenticator.internal()) + authenticator_id = authenticator["authenticatorId"] + + # Create a virtual credential from our database model + virtual_credential(authenticator_id, VirtualCredential.from_model(credential)) + + # Remove the credential to simulate it was not found / unknown + credential.delete() + + # Go to the login with passkey page + page.goto(live_server.url + reverse("login-passkey")) + await_failure_event = wait_for_javascript_event(JS_EVENT_VERIFICATION_FAILED) + + login_button = page.locator("button#passkey-verification-button") + expect(login_button).to_be_visible() + + login_button.click() + expected_url = live_server.url + reverse( + "otp_webauthn:credential-authentication-complete" + ) + + # Looks for this message in the console log to confirm the signaling happened + await_credential_not_found_message = wait_for_console_message( + r"Credential not found", level="trace" + ) + + with page.expect_response(expected_url, timeout=5000) as response_info: + assert response_info.value.status == 404 # Credential was not found + + # We must wait for the failure event. The signaling happens before the + # failure event, so we know for sure is must have happened. If we don't wait + # in this indirect way, we will block forever waiting for the console + # message and timeout. + await_failure_event() + + # If this does not return, most likely `auth.ts/signalPasskeyMissing()` + # returned early and did not post a console message confirming the + # signaling happened. We check for console messages because there is + # no other way to confirm this using Chrome DevTools Protocol. + message = await_credential_not_found_message() + + # Check the message contains the right arguments - the assumption is that + # if the arguments are correct, the signaling was called correctly. + message_values = message.args[1].json_value() + assert message_values["rpId"] == "localhost" + assert credential.credential_id == base64url_to_bytes( + message_values["credentialId"] + ) + + +def test_authenticate_credential__manual_disable_signal_no_credential( + live_server, + django_db_serialized_rollback, + page, + user, + virtual_authenticator, + virtual_credential, + wait_for_javascript_event, + wait_for_console_message, + settings, +): + """Verify that manually using the 'Authenticate with Passkey' button with a credential with + a credential that does not exist does NOT signal an unknown credential to the browser + when ``OTP_WEBAUTHN_SIGNAL_UNKNOWN_CREDENTIAL = False``.""" + settings.OTP_WEBAUTHN_SIGNAL_UNKNOWN_CREDENTIAL = False + credential = WebAuthnCredentialFactory(user=user, discoverable=True) + authenticator = virtual_authenticator(VirtualAuthenticator.internal()) + authenticator_id = authenticator["authenticatorId"] + + # Create a virtual credential from our database model + virtual_credential(authenticator_id, VirtualCredential.from_model(credential)) + + # Remove the credential to simulate it was not found / unknown + credential.delete() + + # Go to the login with passkey page + page.goto(live_server.url + reverse("login-passkey")) + await_failure_event = wait_for_javascript_event(JS_EVENT_VERIFICATION_FAILED) + + login_button = page.locator("button#passkey-verification-button") + expect(login_button).to_be_visible() + + login_button.click() + expected_url = live_server.url + reverse( + "otp_webauthn:credential-authentication-complete" + ) + + # Looks for this message in the console log to confirm the signaling happened + await_credential_not_found_message = wait_for_console_message( + r"Not signaling unknown credential to the browser as per configuration", + level="trace", + ) + + with page.expect_response(expected_url, timeout=5000) as response_info: + assert response_info.value.status == 404 # Credential was not found + + # We must wait for the failure event. The signaling happens before the + # failure event, so we know for sure is must have happened. If we don't wait + # in this indirect way, we will block forever waiting for the console + # message and timeout. + await_failure_event() + + # If this does not return, most likely `auth.ts/signalPasskeyMissing()` + # returned early and did not post a console message confirming the + # signaling happened. We check for console messages because there is + # no other way to confirm this using Chrome DevTools Protocol. + await_credential_not_found_message() + + def test_authenticate_credential__internal_passwordless_using_autofill( live_server, django_db_serialized_rollback, @@ -68,6 +192,62 @@ def test_authenticate_credential__internal_passwordless_using_autofill( assert user.username in page.content() +def test_authenticate_credential__passwordless_signal_no_credential( + live_server, + django_db_serialized_rollback, + page, + user, + virtual_authenticator, + virtual_credential, + wait_for_javascript_event, + wait_for_console_message, +): + """Verify that using autofill with a credential that does not exist + signals an unknown credential to the browser.""" + credential = WebAuthnCredentialFactory(user=user, discoverable=True) + authenticator = virtual_authenticator(VirtualAuthenticator.internal()) + authenticator_id = authenticator["authenticatorId"] + + # Create a virtual credential from our database model + virtual_credential(authenticator_id, VirtualCredential.from_model(credential)) + + # Remove the credential to simulate it was not found / unknown + credential.delete() + + # Looks for this message in the console log to confirm the signaling happened + await_credential_not_found_message = wait_for_console_message( + r"Credential not found", level="trace" + ) + # Visit the login page with the autofill form + expected_url = live_server.url + reverse( + "otp_webauthn:credential-authentication-complete" + ) + with page.expect_response(expected_url, timeout=5000) as response_info: + page.goto(live_server.url + reverse("auth:login")) + await_failure_event = wait_for_javascript_event(JS_EVENT_VERIFICATION_FAILED) + assert response_info.value.status == 404 # Credential was not found + + # We must wait for the failure event. The signaling happens before the + # failure event, so we know for sure is must have happened. If we don't wait + # in this indirect way, we will block forever waiting for the console + # message and timeout. + await_failure_event() + + # If this does not return, most likely `auth.ts/signalPasskeyMissing()` + # returned early and did not post a console message confirming the + # signaling happened. We check for console messages because there is + # no other way to confirm this using Chrome DevTools Protocol. + message = await_credential_not_found_message() + + # Check the message contains the right arguments - the assumption is that + # if the arguments are correct, the signaling was called correctly. + message_values = message.args[1].json_value() + assert message_values["rpId"] == "localhost" + assert credential.credential_id == base64url_to_bytes( + message_values["credentialId"] + ) + + def test_authenticate_credential__internal_second_factor_fails_when_credential_is_disabled( live_server, django_db_serialized_rollback, diff --git a/tests/e2e/test_credential_registration.py b/tests/e2e/test_credential_registration.py index 82083dc..afbb653 100644 --- a/tests/e2e/test_credential_registration.py +++ b/tests/e2e/test_credential_registration.py @@ -252,7 +252,7 @@ def test_register_credential__fail_bad_rpid( register_button.click() page.wait_for_selector( - f"#passkey-register-status-message[data-status-enum='{StatusEnum.SECURITY_ERROR}']", + f"#passkey-register-status-message[data-status-enum='{StatusEnum.SECURITY_ERROR.value}']", timeout=2000, ) # Did the right events fire? diff --git a/tests/e2e/test_webauthn_signals.py b/tests/e2e/test_webauthn_signals.py new file mode 100644 index 0000000..d1ff751 --- /dev/null +++ b/tests/e2e/test_webauthn_signals.py @@ -0,0 +1,69 @@ +import pytest +from playwright.sync_api import expect + +from tests.factories import WebAuthnCredentialFactory + + +def test_webauthn_signals_triggered( + live_server, + django_db_serialized_rollback, + page, + playwright_force_login, + playwright_manipulate_session, + user, + wait_for_console_message, +): + """Verify the ``PublicKeyCredential.signalCurrentUserDetails`` and + ``PublicKeyCredential.signalAllAcceptedCredentials`` browser signals are + triggered. + """ + + playwright_force_login(user) + playwright_manipulate_session( + lambda session: session.update({"otp_webauthn_sync_needed": True}) + ) + + # Create some credentials for the user + WebAuthnCredentialFactory(user=user) + WebAuthnCredentialFactory(user=user) + + # Set up signal waiters + await_signal_accepted_credentials = wait_for_console_message( + r"Signaled accepted credentials to the browser." + ) + await_signal_user_details = wait_for_console_message( + r"Signaled current user details to the browser." + ) + + page.goto(live_server.url) + + # Double check that PublicKeyCredential.signalCurrentUserDetails and + # PublicKeyCredential.signalAllAcceptedCredentials apis are available in this browser + # otherwise the result won't be meaningful. + if not page.evaluate("() => 'signalCurrentUserDetails' in PublicKeyCredential"): + pytest.skip( + "PublicKeyCredential.signalCurrentUserDetails does not exist in this browser, cannot test this feature!", + ) + if not page.evaluate("() => 'signalAllAcceptedCredentials' in PublicKeyCredential"): + pytest.skip( + "PublicKeyCredential.signalAllAcceptedCredentials does not exist in this browser, cannot test this feature!", + ) + + # Wait for the signals to be sent + try: + await_signal_accepted_credentials() + except TimeoutError: + pytest.fail( + "sync_signals.ts did not post console message indicating PublicKeyCredential.signalAllAcceptedCredentials was called." + ) + try: + await_signal_user_details() + except TimeoutError: + pytest.fail( + "sync_signals.ts did not post console message indicating PublicKeyCredential.signalCurrentUserDetails was called." + ) + + # Verify that the config script tag has been removed from the DOM + expect(page.locator("script[id='otp_webauthn_sync_signals_config']")).to_have_count( + 0 + ) diff --git a/tests/integration/test_views_authentication.py b/tests/integration/test_views_authentication.py index 13e4014..346490e 100644 --- a/tests/integration/test_views_authentication.py +++ b/tests/integration/test_views_authentication.py @@ -19,7 +19,7 @@ def test_authentication__anonymous_user_passwordless_login_disallowed( """Test that an anonymous user is not allowed to begin authentication if passwordless login is disabled.""" settings.OTP_WEBAUTHN_ALLOW_PASSWORDLESS_LOGIN = False response = api_client.post(url) - assert response.status_code == 403 + assert response.status_code == 403, response.data assert response.data["detail"].code == "passwordless_login_disabled" @@ -40,12 +40,12 @@ def test_authentication__http_verbs(api_client, user, url): # OPTIONS should be allowed response = api_client.options(url) - assert response.status_code == 200 + assert response.status_code == 200, response.data # POST should be allowed response = api_client.post(url) # We expect either a 200 or a 400 response (because we are not passing any data) - assert response.status_code == 200 or response.status_code == 400 + assert response.status_code == 200 or response.status_code == 400, response.data # BEGIN AUTHENTICATION VIEW @@ -63,7 +63,7 @@ def test_authentication_begin__anonymous_user_passwordless_login_allowed( ] } response = api_client.post(reverse("otp_webauthn:credential-authentication-begin")) - assert response.status_code == 200 + assert response.status_code == 200, response.data data = response.json() session = api_client.session @@ -94,7 +94,7 @@ def test_authentication_begin__logged_in_user( WebAuthnCredentialFactory(user=user, credential_id=credential_id) response = api_client.post(reverse("otp_webauthn:credential-authentication-begin")) - assert response.status_code == 200 + assert response.status_code == 200, response.data data = response.json() session = api_client.session @@ -121,7 +121,7 @@ def test_authentication_complete__no_state(api_client, user): url = reverse("otp_webauthn:credential-authentication-complete") api_client.force_login(user) response = api_client.post(url) - assert response.status_code == 400 + assert response.status_code == 400, response.data assert response.data["detail"].code == "invalid_state" @@ -131,7 +131,7 @@ def test_authentication_complete__no_reusing_state(api_client, user): api_client.force_login(user) api_client.session["otp_webauthn_authentication_state"] = {"challenge": "challenge"} response = api_client.post(url) - assert response.status_code == 400 + assert response.status_code == 400, response.data # The state should be removed from the session - there is no reusing it assert not api_client.session.get("otp_webauthn_authentication_state") @@ -202,7 +202,7 @@ def test_authentication_complete__anonymous_user_passwordless_login_allowed( data=payload, format="json", ) - assert response.status_code == 200 + assert response.status_code == 200, response.data data = response.json() assert data["id"] == credential.pk session = api_client.session @@ -212,6 +212,8 @@ def test_authentication_complete__anonymous_user_passwordless_login_allowed( assert ( session["_auth_user_backend"] == "django_otp_webauthn.backends.WebAuthnBackend" ) + # Signaled that user details sync is needed + assert session["otp_webauthn_sync_needed"] is True @pytest.mark.django_db @@ -229,12 +231,14 @@ def test_authentication_complete__verify_existing_user(api_client, settings, use data=payload, format="json", ) - assert response.status_code == 200 + assert response.status_code == 200, response.data data = response.json() assert data["id"] == credential.pk session = api_client.session assert "otp_webauthn_authentication_state" not in session assert session["otp_device_id"] == credential.persistent_id + # Signaled that user details sync is needed + assert session["otp_webauthn_sync_needed"] is True @pytest.mark.django_db @@ -253,11 +257,13 @@ def test_authentication_complete_device_usable__unconfirmed(api_client, user): data=payload, format="json", ) - assert response.status_code == 403 + assert response.status_code == 403, response.data assert response.data["detail"].code == "credential_disabled" session = api_client.session assert "otp_webauthn_authentication_state" not in session assert "otp_device_id" not in session + # No user details sync should be requested + assert "otp_webauthn_sync_needed" not in session @pytest.mark.django_db @@ -280,11 +286,13 @@ def test_authentication_complete_device_usable__user_disabled( data=payload, format="json", ) - assert response.status_code == 403 + assert response.status_code == 403, response.data assert response.data["detail"].code == "user_disabled" session = api_client.session assert "otp_webauthn_authentication_state" not in session assert "otp_device_id" not in session + # No user details sync should be requested + assert "otp_webauthn_sync_needed" not in session @pytest.mark.django_db @@ -300,6 +308,6 @@ def test_authentication_complete_get_success_url__understands_next_url_parameter data=payload, format="json", ) - assert response.status_code == 200 + assert response.status_code == 200, response.data data = response.json() assert data["redirect_url"] == "/admin" diff --git a/tests/integration/test_views_registration.py b/tests/integration/test_views_registration.py index ec86f4e..c55c661 100644 --- a/tests/integration/test_views_registration.py +++ b/tests/integration/test_views_registration.py @@ -18,18 +18,18 @@ def test_registration_begin__http_verbs(api_client, user): # OPTIONS should be allowed response = api_client.options(url) - assert response.status_code == 200 + assert response.status_code == 200, response.data # POST should be allowed response = api_client.post(url) - assert response.status_code == 200 + assert response.status_code == 200, response.data @pytest.mark.django_db def test_registration_begin__user_not_authenticated(api_client): url = reverse("otp_webauthn:credential-registration-begin") response = api_client.post(url) - assert response.status_code == 403 + assert response.status_code == 403, response.data @pytest.mark.django_db @@ -42,7 +42,7 @@ def test_registration_begin__no_existing_credentials( url = reverse("otp_webauthn:credential-registration-begin") api_client.force_login(user) response = api_client.post(url) - assert response.status_code == 200 + assert response.status_code == 200, response.data data = response.json() # Response should conform to the schema we established @@ -74,7 +74,7 @@ def test_registration_begin__has_existing_credentials( url = reverse("otp_webauthn:credential-registration-begin") api_client.force_login(user) response = api_client.post(url) - assert response.status_code == 200 + assert response.status_code == 200, response.data data = response.json() # Response should conform to the schema we established @@ -97,7 +97,7 @@ def test_registration_begin__passwordless_login_enabled( url = reverse("otp_webauthn:credential-registration-begin") api_client.force_login(user) response = api_client.post(url) - assert response.status_code == 200 + assert response.status_code == 200, response.data data = response.json() # Response should conform to the schema we established @@ -117,7 +117,7 @@ def test_registration_begin__passwordless_login_disabled( url = reverse("otp_webauthn:credential-registration-begin") api_client.force_login(user) response = api_client.post(url) - assert response.status_code == 200 + assert response.status_code == 200, response.data data = response.json() # Response should conform to the schema we established @@ -134,7 +134,7 @@ def test_registration_begin__keeps_state( url = reverse("otp_webauthn:credential-registration-begin") api_client.force_login(user) response = api_client.post(url) - assert response.status_code == 200 + assert response.status_code == 200, response.data data = response.json() # Response should conform to the schema we established @@ -159,18 +159,18 @@ def test_registration_complete__http_verbs(api_client, user): # OPTIONS should be allowed response = api_client.options(url) - assert response.status_code == 200 + assert response.status_code == 200, response.data # POST should be allowed, though this response will be a 400 because we're not sending the right data response = api_client.post(url) - assert response.status_code == 400 + assert response.status_code == 400, response.data @pytest.mark.django_db def test_registration_complete__user_not_authenticated(api_client): url = reverse("otp_webauthn:credential-registration-complete") response = api_client.post(url) - assert response.status_code == 403 + assert response.status_code == 403, response.data @pytest.mark.django_db @@ -178,7 +178,7 @@ def test_registration_complete__no_state(api_client, user): url = reverse("otp_webauthn:credential-registration-complete") api_client.force_login(user) response = api_client.post(url) - assert response.status_code == 400 + assert response.status_code == 400, response.data assert response.data["detail"].code == "invalid_state" assert api_client.session.get("otp_device_id") is None @@ -189,7 +189,7 @@ def test_registration_complete__no_reusing_state(api_client, user): api_client.force_login(user) api_client.session["otp_webauthn_register_state"] = {"challenge": "challenge"} response = api_client.post(url) - assert response.status_code == 400 + assert response.status_code == 400, response.data # The state should be removed from the session - there is no reusing it assert not api_client.session.get("otp_webauthn_register_state") @@ -230,7 +230,7 @@ def test_registration_complete__valid_response_but_already_verified( "authenticatorAttachment": "platform", } response = api_client.post(url, data=payload, format="json") - assert response.status_code == 200 + assert response.status_code == 200, response.data cred = credential_model.objects.last() assert cred.pk == response.data["id"] @@ -241,6 +241,9 @@ def test_registration_complete__valid_response_but_already_verified( assert cred.persistent_id != credential.persistent_id assert api_client.session["otp_device_id"] == credential.persistent_id + # Signaled that user details sync is needed + assert api_client.session["otp_webauthn_sync_needed"] is True + @pytest.mark.django_db def test_registration_complete__valid_response(api_client, user, credential_model): @@ -270,7 +273,7 @@ def test_registration_complete__valid_response(api_client, user, credential_mode "authenticatorAttachment": "platform", } response = api_client.post(url, data=payload, format="json") - assert response.status_code == 200 + assert response.status_code == 200, response.data cred = credential_model.objects.first() assert cred.pk == response.data["id"] @@ -279,3 +282,6 @@ def test_registration_complete__valid_response(api_client, user, credential_mode # The user session wasn't 2FA verified before, so now it should be assert api_client.session["otp_device_id"] == cred.persistent_id + + # Signaled that user details sync is needed + assert api_client.session["otp_webauthn_sync_needed"] is True diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py index e6d9ea6..2cc71d7 100644 --- a/tests/unit/test_models.py +++ b/tests/unit/test_models.py @@ -6,6 +6,7 @@ from django.db import IntegrityError from django.test.utils import isolate_apps from django.utils import timezone +from webauthn.helpers import bytes_to_base64url from webauthn.helpers.structs import ( AttestationObject, AuthenticatorTransport, @@ -128,8 +129,19 @@ def test_credentials_are_unique(): @pytest.mark.django_db -def test_get_by_credential_id(django_assert_num_queries): - """Test that the get_by_credential_id method works.""" +def test_credential_credential_id_base64url(): + """Test that the credential_id_base64url property works.""" + credential_id = b"credential_id" + expected_base64url = "Y3JlZGVudGlhbF9pZA" + + credential = WebAuthnCredentialFactory(credential_id=credential_id) + + assert credential.credential_id_base64url == expected_base64url + + +@pytest.mark.django_db +def test_credential_get_by_credential_id(django_assert_num_queries): + """Test that WebAuthnCredential.get_by_credential_id method works.""" credential_id = b"credential_id" cred1 = WebAuthnCredentialFactory(credential_id=credential_id) @@ -253,6 +265,25 @@ def test_user_handle_generate_handle_hex(user_handle_model): assert len(handle) == 64 +@pytest.mark.django_db +def test_user_handle_handle_property(user_handle): + """Test that the handle property works.""" + handle_bytes = user_handle.handle + + assert isinstance(handle_bytes, bytes) + assert handle_bytes == bytes.fromhex(user_handle.handle_hex) + + +@pytest.mark.django_db +def test_user_handle_handle_base64url_property(user_handle): + """Test that the handle_base64url property works.""" + expected_base64url = bytes_to_base64url(bytes.fromhex(user_handle.handle_hex)) + handle_base64url = user_handle.handle_base64url + + assert isinstance(handle_base64url, str) + assert handle_base64url == expected_base64url + + @pytest.mark.django_db def test_user_handle__str__(user_handle): """Test that the __str__ method works.""" diff --git a/tests/unit/test_templatetags.py b/tests/unit/test_templatetags.py index 76ca198..335b00e 100644 --- a/tests/unit/test_templatetags.py +++ b/tests/unit/test_templatetags.py @@ -1,5 +1,6 @@ import json +import pytest from bs4 import BeautifulSoup from django.shortcuts import resolve_url from django.template import Context, Template @@ -8,6 +9,8 @@ from django_otp_webauthn.templatetags.otp_webauthn import ( get_configuration, ) +from django_otp_webauthn.utils import request_user_details_sync +from tests.factories import WebAuthnCredentialFactory, WebAuthnUserHandleFactory def test_get_configuration__defaults(rf): @@ -19,6 +22,7 @@ def test_get_configuration__defaults(rf): assert "csrfToken" in configuration assert configuration["nextFieldSelector"] == "input[name='next']" + assert configuration["removeUnknownCredential"] is True # Assert that the URLs are actually resolved assert resolve_url(configuration["beginAuthenticationUrl"]) assert resolve_url(configuration["completeAuthenticationUrl"]) @@ -26,6 +30,15 @@ def test_get_configuration__defaults(rf): assert resolve_url(configuration["completeRegistrationUrl"]) +def test_get_configuration__disable_signal_unknown_credential(rf, settings): + """Test that the configuration reflects the setting to disable signaling unknown credentials.""" + settings.OTP_WEBAUTHN_SIGNAL_UNKNOWN_CREDENTIAL = False + request = rf.get("/") + configuration = get_configuration(request) + + assert configuration["removeUnknownCredential"] is False + + def test_get_configuration__extra_options(rf): """Test that extra options are added to the configuration.""" request = rf.get("/") @@ -106,3 +119,75 @@ def test_render_otp_webauthn_auth_scripts__no_passwordless(rf, settings): assert soup.select_one(f'script[src="{i18n_url}"]') assert soup.select_one('script[src$="otp_webauthn_auth.js"]') + + +def test_render_otp_webauthn_sync_signals_scripts__not_authenticated(client): + """Test that the sync signals scripts render nothing for anonymous users, even + if the sync needed flag is set.""" + request = client.request().wsgi_request + # Set the sync needed flag in the session + request_user_details_sync(request) + + context = Context({"request": request}) + template = Template( + "{% load otp_webauthn %}{% render_otp_webauthn_sync_signals_scripts %}" + ) + rendered = template.render(context) + # User is not authenticated, so we stay silent and render + # nothing - even though the sync needed flag is set. + assert rendered == "" + + +@pytest.mark.django_db +def test_render_otp_webauthn_sync_signals_scripts__authenticated( + client, user, settings +): + """Test that the sync signals scripts render correctly for authenticated users + when the sync needed flag is set.""" + settings.OTP_WEBAUTHN_RP_ID = "testserver" + user.username = "testuser" + user.first_name = "Test" + user.last_name = "User" + user.save() + + handle = WebAuthnUserHandleFactory(user=user) + credential1 = WebAuthnCredentialFactory(user=user) + credential2_unconfirmed = WebAuthnCredentialFactory(user=user, confirmed=False) + credential3_other_user = WebAuthnCredentialFactory() + + client.force_login(user) + request = client.request().wsgi_request + # Set the sync needed flag in the session + request_user_details_sync(request) + + context = Context({"request": request}) + template = Template( + "{% load otp_webauthn %}{% render_otp_webauthn_sync_signals_scripts %}" + ) + rendered = template.render(context) + soup = BeautifulSoup(rendered, "html.parser") + config_el = soup.select_one("script[id=otp_webauthn_sync_signals_config]") + assert config_el + config = json.loads(config_el.text) + assert config["rpId"] == "testserver" + assert config["userId"] == handle.handle_base64url + assert config["name"] == "testuser" + assert config["displayName"] == "Test User (testuser)" + + assert len(config["credentialIds"]) == 2 + + assert credential1.credential_id_base64url in config["credentialIds"] + # Even if (temporarily) unconfirmed, the credential should be included. + # It might become confirmed again later, so don't cause its removal just yet. + assert credential2_unconfirmed.credential_id_base64url in config["credentialIds"] + + # Definitely do not include credentials of other users + assert credential3_other_user.credential_id_base64url not in config["credentialIds"] + + # But if we render again, the flag should have been consumed and we stay + # silent this time + assert "otp_webauthn_sync_needed" not in request.session + + rendered = template.render(context) + # We did not set the sync needed flag, so we stay silent and render nothing + assert rendered == "" diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 889a6d0..f131be0 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -9,6 +9,7 @@ get_attestation_model_string, get_credential_model, get_credential_model_string, + request_user_details_sync, rewrite_exceptions, ) @@ -167,3 +168,15 @@ def test_rewrite_exceptions_uncaught_exception(): with pytest.raises(Exception): # noqa: B017, PT011 with rewrite_exceptions(): raise Exception() + + +def test_request_user_details_sync(client): + """Test that the ``utils.request_user_details_sync`` function sets the sync needed flag in the session.""" + request = client.request().wsgi_request + + assert "otp_webauthn_sync_needed" not in request.session + + request_user_details_sync(request) + + assert "otp_webauthn_sync_needed" in request.session + assert request.session["otp_webauthn_sync_needed"] is True diff --git a/yarn.lock b/yarn.lock index 984a4a1..5963f50 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,14 +2,6 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - "@babel/code-frame@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" @@ -19,39 +11,39 @@ js-tokens "^4.0.0" picocolors "^1.1.1" -"@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" - integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== +"@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.5.tgz#a8a4962e1567121ac0b3b487f52107443b455c7f" + integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA== "@babel/core@^7.24.5": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.3.tgz#aceddde69c5d1def69b839d09efa3e3ff59c97cb" - integrity sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ== + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.5.tgz#4c81b35e51e1b734f510c99b07dfbc7bbbb48f7e" + integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw== dependencies: - "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.3" + "@babel/generator" "^7.28.5" "@babel/helper-compilation-targets" "^7.27.2" "@babel/helper-module-transforms" "^7.28.3" - "@babel/helpers" "^7.28.3" - "@babel/parser" "^7.28.3" + "@babel/helpers" "^7.28.4" + "@babel/parser" "^7.28.5" "@babel/template" "^7.27.2" - "@babel/traverse" "^7.28.3" - "@babel/types" "^7.28.2" + "@babel/traverse" "^7.28.5" + "@babel/types" "^7.28.5" + "@jridgewell/remapping" "^2.3.5" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" - integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== +"@babel/generator@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.5.tgz#712722d5e50f44d07bc7ac9fe84438742dd61298" + integrity sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ== dependencies: - "@babel/parser" "^7.28.3" - "@babel/types" "^7.28.2" + "@babel/parser" "^7.28.5" + "@babel/types" "^7.28.5" "@jridgewell/gen-mapping" "^0.3.12" "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" @@ -74,26 +66,26 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz#3e747434ea007910c320c4d39a6b46f20f371d46" - integrity sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg== +"@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3", "@babel/helper-create-class-features-plugin@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz#472d0c28028850968979ad89f173594a6995da46" + integrity sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ== dependencies: "@babel/helper-annotate-as-pure" "^7.27.3" - "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-member-expression-to-functions" "^7.28.5" "@babel/helper-optimise-call-expression" "^7.27.1" "@babel/helper-replace-supers" "^7.27.1" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - "@babel/traverse" "^7.28.3" + "@babel/traverse" "^7.28.5" semver "^6.3.1" "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz#05b0882d97ba1d4d03519e4bce615d70afa18c53" - integrity sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ== + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz#7c1ddd64b2065c7f78034b25b43346a7e19ed997" + integrity sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw== dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - regexpu-core "^6.2.0" + "@babel/helper-annotate-as-pure" "^7.27.3" + regexpu-core "^6.3.1" semver "^6.3.1" "@babel/helper-define-polyfill-provider@^0.6.5": @@ -112,13 +104,13 @@ resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== -"@babel/helper-member-expression-to-functions@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44" - integrity sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA== +"@babel/helper-member-expression-to-functions@^7.27.1", "@babel/helper-member-expression-to-functions@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz#f3e07a10be37ed7a63461c63e6929575945a6150" + integrity sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg== dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" + "@babel/traverse" "^7.28.5" + "@babel/types" "^7.28.5" "@babel/helper-module-imports@^7.27.1": version "7.27.1" @@ -180,10 +172,10 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== -"@babel/helper-validator-identifier@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" - integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== +"@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== "@babel/helper-validator-option@^7.27.1": version "7.27.1" @@ -199,28 +191,28 @@ "@babel/traverse" "^7.28.3" "@babel/types" "^7.28.2" -"@babel/helpers@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.3.tgz#b83156c0a2232c133d1b535dd5d3452119c7e441" - integrity sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw== +"@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== dependencies: "@babel/template" "^7.27.2" - "@babel/types" "^7.28.2" + "@babel/types" "^7.28.4" -"@babel/parser@^7.27.2", "@babel/parser@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71" - integrity sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA== +"@babel/parser@^7.27.2", "@babel/parser@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.5.tgz#0b0225ee90362f030efd644e8034c99468893b08" + integrity sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ== dependencies: - "@babel/types" "^7.28.2" + "@babel/types" "^7.28.5" -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz#61dd8a8e61f7eb568268d1b5f129da3eee364bf9" - integrity sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA== +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz#fbde57974707bbfa0376d34d425ff4fa6c732421" + integrity sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q== dependencies: "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.27.1" + "@babel/traverse" "^7.28.5" "@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.27.1": version "7.27.1" @@ -326,10 +318,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-block-scoping@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz#e7c50cbacc18034f210b93defa89638666099451" - integrity sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q== +"@babel/plugin-transform-block-scoping@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz#e0d3af63bd8c80de2e567e690a54e84d85eb16f6" + integrity sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g== dependencies: "@babel/helper-plugin-utils" "^7.27.1" @@ -349,17 +341,17 @@ "@babel/helper-create-class-features-plugin" "^7.28.3" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-classes@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz#598297260343d0edbd51cb5f5075e07dee91963a" - integrity sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg== +"@babel/plugin-transform-classes@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz#75d66175486788c56728a73424d67cbc7473495c" + integrity sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA== dependencies: "@babel/helper-annotate-as-pure" "^7.27.3" "@babel/helper-compilation-targets" "^7.27.2" "@babel/helper-globals" "^7.28.0" "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-replace-supers" "^7.27.1" - "@babel/traverse" "^7.28.3" + "@babel/traverse" "^7.28.4" "@babel/plugin-transform-computed-properties@^7.27.1": version "7.27.1" @@ -369,13 +361,13 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/template" "^7.27.1" -"@babel/plugin-transform-destructuring@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz#0f156588f69c596089b7d5b06f5af83d9aa7f97a" - integrity sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A== +"@babel/plugin-transform-destructuring@^7.28.0", "@babel/plugin-transform-destructuring@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz#b8402764df96179a2070bb7b501a1586cf8ad7a7" + integrity sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw== dependencies: "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.28.0" + "@babel/traverse" "^7.28.5" "@babel/plugin-transform-dotall-regex@^7.27.1": version "7.27.1" @@ -415,10 +407,10 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-destructuring" "^7.28.0" -"@babel/plugin-transform-exponentiation-operator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz#fc497b12d8277e559747f5a3ed868dd8064f83e1" - integrity sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ== +"@babel/plugin-transform-exponentiation-operator@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz#7cc90a8170e83532676cfa505278e147056e94fe" + integrity sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw== dependencies: "@babel/helper-plugin-utils" "^7.27.1" @@ -460,10 +452,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-logical-assignment-operators@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz#890cb20e0270e0e5bebe3f025b434841c32d5baa" - integrity sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw== +"@babel/plugin-transform-logical-assignment-operators@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz#d028fd6db8c081dee4abebc812c2325e24a85b0e" + integrity sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA== dependencies: "@babel/helper-plugin-utils" "^7.27.1" @@ -490,15 +482,15 @@ "@babel/helper-module-transforms" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-modules-systemjs@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz#00e05b61863070d0f3292a00126c16c0e024c4ed" - integrity sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA== +"@babel/plugin-transform-modules-systemjs@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz#7439e592a92d7670dfcb95d0cbc04bd3e64801d2" + integrity sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew== dependencies: - "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-module-transforms" "^7.28.3" "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.5" "@babel/plugin-transform-modules-umd@^7.27.1": version "7.27.1" @@ -537,16 +529,16 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-object-rest-spread@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz#d23021857ffd7cd809f54d624299b8086402ed8d" - integrity sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA== +"@babel/plugin-transform-object-rest-spread@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz#9ee1ceca80b3e6c4bac9247b2149e36958f7f98d" + integrity sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew== dependencies: "@babel/helper-compilation-targets" "^7.27.2" "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-destructuring" "^7.28.0" "@babel/plugin-transform-parameters" "^7.27.7" - "@babel/traverse" "^7.28.0" + "@babel/traverse" "^7.28.4" "@babel/plugin-transform-object-super@^7.27.1": version "7.27.1" @@ -563,10 +555,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-optional-chaining@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz#874ce3c4f06b7780592e946026eb76a32830454f" - integrity sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg== +"@babel/plugin-transform-optional-chaining@^7.27.1", "@babel/plugin-transform-optional-chaining@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz#8238c785f9d5c1c515a90bf196efb50d075a4b26" + integrity sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ== dependencies: "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" @@ -602,10 +594,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-regenerator@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz#b8eee0f8aed37704bbcc932fd0b1a0a34d0b7344" - integrity sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A== +"@babel/plugin-transform-regenerator@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz#9d3fa3bebb48ddd0091ce5729139cd99c67cea51" + integrity sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA== dependencies: "@babel/helper-plugin-utils" "^7.27.1" @@ -660,13 +652,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-typescript@^7.27.1": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz#796cbd249ab56c18168b49e3e1d341b72af04a6b" - integrity sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg== +"@babel/plugin-transform-typescript@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz#441c5f9a4a1315039516c6c612fc66d5f4594e72" + integrity sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA== dependencies: "@babel/helper-annotate-as-pure" "^7.27.3" - "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.28.5" "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" "@babel/plugin-syntax-typescript" "^7.27.1" @@ -703,15 +695,15 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/preset-env@^7.24.5": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.28.3.tgz#2b18d9aff9e69643789057ae4b942b1654f88187" - integrity sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg== + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.28.5.tgz#82dd159d1563f219a1ce94324b3071eb89e280b0" + integrity sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg== dependencies: - "@babel/compat-data" "^7.28.0" + "@babel/compat-data" "^7.28.5" "@babel/helper-compilation-targets" "^7.27.2" "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-validator-option" "^7.27.1" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.27.1" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.28.5" "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.27.1" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.27.1" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.27.1" @@ -724,42 +716,42 @@ "@babel/plugin-transform-async-generator-functions" "^7.28.0" "@babel/plugin-transform-async-to-generator" "^7.27.1" "@babel/plugin-transform-block-scoped-functions" "^7.27.1" - "@babel/plugin-transform-block-scoping" "^7.28.0" + "@babel/plugin-transform-block-scoping" "^7.28.5" "@babel/plugin-transform-class-properties" "^7.27.1" "@babel/plugin-transform-class-static-block" "^7.28.3" - "@babel/plugin-transform-classes" "^7.28.3" + "@babel/plugin-transform-classes" "^7.28.4" "@babel/plugin-transform-computed-properties" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.0" + "@babel/plugin-transform-destructuring" "^7.28.5" "@babel/plugin-transform-dotall-regex" "^7.27.1" "@babel/plugin-transform-duplicate-keys" "^7.27.1" "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.27.1" "@babel/plugin-transform-dynamic-import" "^7.27.1" "@babel/plugin-transform-explicit-resource-management" "^7.28.0" - "@babel/plugin-transform-exponentiation-operator" "^7.27.1" + "@babel/plugin-transform-exponentiation-operator" "^7.28.5" "@babel/plugin-transform-export-namespace-from" "^7.27.1" "@babel/plugin-transform-for-of" "^7.27.1" "@babel/plugin-transform-function-name" "^7.27.1" "@babel/plugin-transform-json-strings" "^7.27.1" "@babel/plugin-transform-literals" "^7.27.1" - "@babel/plugin-transform-logical-assignment-operators" "^7.27.1" + "@babel/plugin-transform-logical-assignment-operators" "^7.28.5" "@babel/plugin-transform-member-expression-literals" "^7.27.1" "@babel/plugin-transform-modules-amd" "^7.27.1" "@babel/plugin-transform-modules-commonjs" "^7.27.1" - "@babel/plugin-transform-modules-systemjs" "^7.27.1" + "@babel/plugin-transform-modules-systemjs" "^7.28.5" "@babel/plugin-transform-modules-umd" "^7.27.1" "@babel/plugin-transform-named-capturing-groups-regex" "^7.27.1" "@babel/plugin-transform-new-target" "^7.27.1" "@babel/plugin-transform-nullish-coalescing-operator" "^7.27.1" "@babel/plugin-transform-numeric-separator" "^7.27.1" - "@babel/plugin-transform-object-rest-spread" "^7.28.0" + "@babel/plugin-transform-object-rest-spread" "^7.28.4" "@babel/plugin-transform-object-super" "^7.27.1" "@babel/plugin-transform-optional-catch-binding" "^7.27.1" - "@babel/plugin-transform-optional-chaining" "^7.27.1" + "@babel/plugin-transform-optional-chaining" "^7.28.5" "@babel/plugin-transform-parameters" "^7.27.7" "@babel/plugin-transform-private-methods" "^7.27.1" "@babel/plugin-transform-private-property-in-object" "^7.27.1" "@babel/plugin-transform-property-literals" "^7.27.1" - "@babel/plugin-transform-regenerator" "^7.28.3" + "@babel/plugin-transform-regenerator" "^7.28.4" "@babel/plugin-transform-regexp-modifiers" "^7.27.1" "@babel/plugin-transform-reserved-words" "^7.27.1" "@babel/plugin-transform-shorthand-properties" "^7.27.1" @@ -788,15 +780,15 @@ esutils "^2.0.2" "@babel/preset-typescript@^7.24.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz#190742a6428d282306648a55b0529b561484f912" - integrity sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ== + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz#540359efa3028236958466342967522fd8f2a60c" + integrity sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g== dependencies: "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-validator-option" "^7.27.1" "@babel/plugin-syntax-jsx" "^7.27.1" "@babel/plugin-transform-modules-commonjs" "^7.27.1" - "@babel/plugin-transform-typescript" "^7.27.1" + "@babel/plugin-transform-typescript" "^7.28.5" "@babel/register@^7.23.7": version "7.28.3" @@ -810,9 +802,9 @@ source-map-support "^0.5.16" "@babel/runtime@^7.18.9", "@babel/runtime@^7.7.6": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.3.tgz#75c5034b55ba868121668be5d5bb31cc64e6e61a" - integrity sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA== + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" + integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== "@babel/template@^7.27.1", "@babel/template@^7.27.2": version "7.27.2" @@ -823,161 +815,161 @@ "@babel/parser" "^7.27.2" "@babel/types" "^7.27.1" -"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.3.tgz#6911a10795d2cce43ec6a28cffc440cca2593434" - integrity sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ== +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4", "@babel/traverse@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.5.tgz#450cab9135d21a7a2ca9d2d35aa05c20e68c360b" + integrity sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ== dependencies: "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.3" + "@babel/generator" "^7.28.5" "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.28.3" + "@babel/parser" "^7.28.5" "@babel/template" "^7.27.2" - "@babel/types" "^7.28.2" + "@babel/types" "^7.28.5" debug "^4.3.1" -"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.4.4": - version "7.28.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" - integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== +"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.28.5", "@babel/types@^7.4.4": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.5.tgz#10fc405f60897c35f07e85493c932c7b5ca0592b" + integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA== dependencies: "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" "@discoveryjs/json-ext@^0.6.1": version "0.6.3" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz#f13c7c205915eb91ae54c557f5e92bddd8be0e83" integrity sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ== -"@esbuild/aix-ppc64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz#bef96351f16520055c947aba28802eede3c9e9a9" - integrity sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA== - -"@esbuild/android-arm64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz#d2e70be7d51a529425422091e0dcb90374c1546c" - integrity sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg== - -"@esbuild/android-arm@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.9.tgz#d2a753fe2a4c73b79437d0ba1480e2d760097419" - integrity sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ== - -"@esbuild/android-x64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.9.tgz#5278836e3c7ae75761626962f902a0d55352e683" - integrity sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw== - -"@esbuild/darwin-arm64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz#f1513eaf9ec8fa15dcaf4c341b0f005d3e8b47ae" - integrity sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg== - -"@esbuild/darwin-x64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz#e27dbc3b507b3a1cea3b9280a04b8b6b725f82be" - integrity sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ== - -"@esbuild/freebsd-arm64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz#364e3e5b7a1fd45d92be08c6cc5d890ca75908ca" - integrity sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q== - -"@esbuild/freebsd-x64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz#7c869b45faeb3df668e19ace07335a0711ec56ab" - integrity sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg== - -"@esbuild/linux-arm64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz#48d42861758c940b61abea43ba9a29b186d6cb8b" - integrity sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw== - -"@esbuild/linux-arm@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz#6ce4b9cabf148274101701d112b89dc67cc52f37" - integrity sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw== - -"@esbuild/linux-ia32@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz#207e54899b79cac9c26c323fc1caa32e3143f1c4" - integrity sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A== - -"@esbuild/linux-loong64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz#0ba48a127159a8f6abb5827f21198b999ffd1fc0" - integrity sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ== - -"@esbuild/linux-mips64el@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz#a4d4cc693d185f66a6afde94f772b38ce5d64eb5" - integrity sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA== - -"@esbuild/linux-ppc64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz#0f5805c1c6d6435a1dafdc043cb07a19050357db" - integrity sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w== - -"@esbuild/linux-riscv64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz#6776edece0f8fca79f3386398b5183ff2a827547" - integrity sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg== - -"@esbuild/linux-s390x@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz#3f6f29ef036938447c2218d309dc875225861830" - integrity sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA== - -"@esbuild/linux-x64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz#831fe0b0e1a80a8b8391224ea2377d5520e1527f" - integrity sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg== - -"@esbuild/netbsd-arm64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz#06f99d7eebe035fbbe43de01c9d7e98d2a0aa548" - integrity sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q== - -"@esbuild/netbsd-x64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz#db99858e6bed6e73911f92a88e4edd3a8c429a52" - integrity sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g== - -"@esbuild/openbsd-arm64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz#afb886c867e36f9d86bb21e878e1185f5d5a0935" - integrity sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ== - -"@esbuild/openbsd-x64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz#30855c9f8381fac6a0ef5b5f31ac6e7108a66ecf" - integrity sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA== - -"@esbuild/openharmony-arm64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz#2f2144af31e67adc2a8e3705c20c2bd97bd88314" - integrity sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg== - -"@esbuild/sunos-x64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz#69b99a9b5bd226c9eb9c6a73f990fddd497d732e" - integrity sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw== - -"@esbuild/win32-arm64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz#d789330a712af916c88325f4ffe465f885719c6b" - integrity sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ== - -"@esbuild/win32-ia32@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz#52fc735406bd49688253e74e4e837ac2ba0789e3" - integrity sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww== - -"@esbuild/win32-x64@0.25.9": - version "0.25.9" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz#585624dc829cfb6e7c0aa6c3ca7d7e6daa87e34f" - integrity sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ== +"@esbuild/aix-ppc64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz#1d8be43489a961615d49e037f1bfa0f52a773737" + integrity sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A== + +"@esbuild/android-arm64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz#bd1763194aad60753fa3338b1ba9bda974b58724" + integrity sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ== + +"@esbuild/android-arm@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.0.tgz#69c7b57f02d3b3618a5ba4f82d127b57665dc397" + integrity sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ== + +"@esbuild/android-x64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.0.tgz#6ea22b5843acb23243d0126c052d7d3b6a11ca90" + integrity sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q== + +"@esbuild/darwin-arm64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz#5ad7c02bc1b1a937a420f919afe40665ba14ad1e" + integrity sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg== + +"@esbuild/darwin-x64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz#48470c83c5fd6d1fc7c823c2c603aeee96e101c9" + integrity sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g== + +"@esbuild/freebsd-arm64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz#d5a8effd8b0be7be613cd1009da34d629d4c2457" + integrity sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw== + +"@esbuild/freebsd-x64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz#9bde638bda31aa244d6d64dbafafb41e6e799bcc" + integrity sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g== + +"@esbuild/linux-arm64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz#96008c3a207d8ca495708db714c475ea5bf7e2af" + integrity sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ== + +"@esbuild/linux-arm@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz#9b47cb0f222e567af316e978c7f35307db97bc0e" + integrity sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ== + +"@esbuild/linux-ia32@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz#d1e1e38d406cbdfb8a49f4eca0c25bbc344e18cc" + integrity sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw== + +"@esbuild/linux-loong64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz#c13bc6a53e3b69b76f248065bebee8415b44dfce" + integrity sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg== + +"@esbuild/linux-mips64el@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz#05f8322eb0a96ce1bfbc59691abe788f71e2d217" + integrity sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg== + +"@esbuild/linux-ppc64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz#6fc5e7af98b4fb0c6a7f0b73ba837ce44dc54980" + integrity sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA== + +"@esbuild/linux-riscv64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz#508afa9f69a3f97368c0bf07dd894a04af39d86e" + integrity sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ== + +"@esbuild/linux-s390x@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz#21fda656110ee242fc64f87a9e0b0276d4e4ec5b" + integrity sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w== + +"@esbuild/linux-x64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz#1758a85dcc09b387fd57621643e77b25e0ccba59" + integrity sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw== + +"@esbuild/netbsd-arm64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz#a0131159f4db6e490da35cc4bb51ef0d03b7848a" + integrity sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w== + +"@esbuild/netbsd-x64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz#6f4877d7c2ba425a2b80e4330594e0b43caa2d7d" + integrity sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA== + +"@esbuild/openbsd-arm64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz#cbefbd4c2f375cebeb4f965945be6cf81331bd01" + integrity sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ== + +"@esbuild/openbsd-x64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz#31fa9e8649fc750d7c2302c8b9d0e1547f57bc84" + integrity sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A== + +"@esbuild/openharmony-arm64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz#03727780f1fdf606e7b56193693a715d9f1ee001" + integrity sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA== + +"@esbuild/sunos-x64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz#866a35f387234a867ced35af8906dfffb073b9ff" + integrity sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA== + +"@esbuild/win32-arm64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz#53de43a9629b8a34678f28cd56cc104db1b67abb" + integrity sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg== + +"@esbuild/win32-ia32@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz#924d2aed8692fea5d27bfb6500f9b8b9c1a34af4" + integrity sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ== + +"@esbuild/win32-x64@0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz#64995295227e001f2940258617c6674efb3ac48d" + integrity sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg== "@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": version "0.3.13" @@ -987,6 +979,14 @@ "@jridgewell/sourcemap-codec" "^1.5.0" "@jridgewell/trace-mapping" "^0.3.24" +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" @@ -1006,17 +1006,17 @@ integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": - version "0.3.30" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz#4a76c4daeee5df09f5d3940e087442fb36ce2b99" - integrity sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q== + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" "@simplewebauthn/browser@^13.0.0": - version "13.1.2" - resolved "https://registry.yarnpkg.com/@simplewebauthn/browser/-/browser-13.1.2.tgz#e904373854616e469c4c1ab9d8c4f704e9ac6db1" - integrity sha512-aZnW0KawAM83fSBUgglP5WofbrLbLyr7CoPqYr66Eppm7zO86YX6rrCjRB3hQKPrL7ATvY4FVXlykZ6w6FwYYw== + version "13.2.2" + resolved "https://registry.yarnpkg.com/@simplewebauthn/browser/-/browser-13.2.2.tgz#4cde38c4c6969a039c23c2a3d931ecb69f937910" + integrity sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA== "@types/eslint-scope@^3.7.7": version "3.7.7" @@ -1045,11 +1045,11 @@ integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/node@*", "@types/node@^24.3.0": - version "24.3.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.3.0.tgz#89b09f45cb9a8ee69466f18ee5864e4c3eb84dec" - integrity sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow== + version "24.10.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.1.tgz#91e92182c93db8bd6224fca031e2370cef9a8f01" + integrity sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ== dependencies: - undici-types "~7.10.0" + undici-types "~7.16.0" "@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": version "1.14.1" @@ -1202,7 +1202,7 @@ acorn-import-phases@^1.0.3: resolved "https://registry.yarnpkg.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz#16eb850ba99a056cb7cbfe872ffb8972e18c8bd7" integrity sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ== -acorn@^8.14.0, acorn@^8.15.0: +acorn@^8.15.0: version "8.15.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== @@ -1297,6 +1297,11 @@ babel-plugin-polyfill-regenerator@^0.6.5: dependencies: "@babel/helper-define-polyfill-provider" "^0.6.5" +baseline-browser-mapping@^2.8.25: + version "2.8.30" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz#5c7420acc2fd20f3db820a40c6521590a671d137" + integrity sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA== + braces@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -1304,15 +1309,16 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browserslist@^4.24.0, browserslist@^4.25.3: - version "4.25.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.3.tgz#9167c9cbb40473f15f75f85189290678b99b16c5" - integrity sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ== +browserslist@^4.24.0, browserslist@^4.26.3, browserslist@^4.28.0: + version "4.28.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.0.tgz#9cefece0a386a17a3cd3d22ebf67b9deca1b5929" + integrity sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ== dependencies: - caniuse-lite "^1.0.30001735" - electron-to-chromium "^1.5.204" - node-releases "^2.0.19" - update-browserslist-db "^1.1.3" + baseline-browser-mapping "^2.8.25" + caniuse-lite "^1.0.30001754" + electron-to-chromium "^1.5.249" + node-releases "^2.0.27" + update-browserslist-db "^1.1.4" buffer-from@^1.0.0: version "1.1.2" @@ -1324,10 +1330,10 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-lite@^1.0.30001735: - version "1.0.30001737" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz#8292bb7591932ff09e9a765f12fdf5629a241ccc" - integrity sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw== +caniuse-lite@^1.0.30001754: + version "1.0.30001756" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz#fe80104631102f88e58cad8aa203a2c3e5ec9ebd" + integrity sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A== chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" @@ -1415,11 +1421,11 @@ convert-source-map@^2.0.0: integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== core-js-compat@^3.43.0: - version "3.45.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.45.1.tgz#424f3f4af30bf676fd1b67a579465104f64e9c7a" - integrity sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA== + version "3.47.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.47.0.tgz#698224bbdbb6f2e3f39decdda4147b161e3772a3" + integrity sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ== dependencies: - browserslist "^4.25.3" + browserslist "^4.28.0" cross-spawn@^7.0.3: version "7.0.6" @@ -1431,9 +1437,9 @@ cross-spawn@^7.0.3: which "^2.0.1" debug@^4.1.0, debug@^4.3.1, debug@^4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" - integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" @@ -1447,10 +1453,10 @@ didyoumean@^1.2.1: resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== -electron-to-chromium@^1.5.204: - version "1.5.208" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz#609c29502fd7257b4d721e3446f3ae391a0ca1b3" - integrity sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg== +electron-to-chromium@^1.5.249: + version "1.5.259" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz#d4393167ec14c5a046cebaec3ddf3377944ce965" + integrity sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ== emoji-regex@^8.0.0: version "8.0.0" @@ -1466,46 +1472,46 @@ enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.3: tapable "^2.2.0" envinfo@^7.14.0: - version "7.14.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.14.0.tgz#26dac5db54418f2a4c1159153a0b2ae980838aae" - integrity sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg== + version "7.20.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.20.0.tgz#3fd9de69fb6af3e777a017dfa033676368d67dd7" + integrity sha512-+zUomDcLXsVkQ37vUqWBvQwLaLlj8eZPSi61llaEFAVBY5mhcXdaSw1pSJVl4yTYD5g/gEfpNl28YYk4IPvrrg== es-module-lexer@^1.2.1: version "1.7.0" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== -esbuild@^0.25.0: - version "0.25.9" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.9.tgz#15ab8e39ae6cdc64c24ff8a2c0aef5b3fd9fa976" - integrity sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g== +esbuild@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.0.tgz#db983bed6f76981361c92f50cf6a04c66f7b3e1d" + integrity sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA== optionalDependencies: - "@esbuild/aix-ppc64" "0.25.9" - "@esbuild/android-arm" "0.25.9" - "@esbuild/android-arm64" "0.25.9" - "@esbuild/android-x64" "0.25.9" - "@esbuild/darwin-arm64" "0.25.9" - "@esbuild/darwin-x64" "0.25.9" - "@esbuild/freebsd-arm64" "0.25.9" - "@esbuild/freebsd-x64" "0.25.9" - "@esbuild/linux-arm" "0.25.9" - "@esbuild/linux-arm64" "0.25.9" - "@esbuild/linux-ia32" "0.25.9" - "@esbuild/linux-loong64" "0.25.9" - "@esbuild/linux-mips64el" "0.25.9" - "@esbuild/linux-ppc64" "0.25.9" - "@esbuild/linux-riscv64" "0.25.9" - "@esbuild/linux-s390x" "0.25.9" - "@esbuild/linux-x64" "0.25.9" - "@esbuild/netbsd-arm64" "0.25.9" - "@esbuild/netbsd-x64" "0.25.9" - "@esbuild/openbsd-arm64" "0.25.9" - "@esbuild/openbsd-x64" "0.25.9" - "@esbuild/openharmony-arm64" "0.25.9" - "@esbuild/sunos-x64" "0.25.9" - "@esbuild/win32-arm64" "0.25.9" - "@esbuild/win32-ia32" "0.25.9" - "@esbuild/win32-x64" "0.25.9" + "@esbuild/aix-ppc64" "0.27.0" + "@esbuild/android-arm" "0.27.0" + "@esbuild/android-arm64" "0.27.0" + "@esbuild/android-x64" "0.27.0" + "@esbuild/darwin-arm64" "0.27.0" + "@esbuild/darwin-x64" "0.27.0" + "@esbuild/freebsd-arm64" "0.27.0" + "@esbuild/freebsd-x64" "0.27.0" + "@esbuild/linux-arm" "0.27.0" + "@esbuild/linux-arm64" "0.27.0" + "@esbuild/linux-ia32" "0.27.0" + "@esbuild/linux-loong64" "0.27.0" + "@esbuild/linux-mips64el" "0.27.0" + "@esbuild/linux-ppc64" "0.27.0" + "@esbuild/linux-riscv64" "0.27.0" + "@esbuild/linux-s390x" "0.27.0" + "@esbuild/linux-x64" "0.27.0" + "@esbuild/netbsd-arm64" "0.27.0" + "@esbuild/netbsd-x64" "0.27.0" + "@esbuild/openbsd-arm64" "0.27.0" + "@esbuild/openbsd-x64" "0.27.0" + "@esbuild/openharmony-arm64" "0.27.0" + "@esbuild/sunos-x64" "0.27.0" + "@esbuild/win32-arm64" "0.27.0" + "@esbuild/win32-ia32" "0.27.0" + "@esbuild/win32-x64" "0.27.0" escalade@^3.2.0: version "3.2.0" @@ -1567,9 +1573,9 @@ fast-deep-equal@^3.1.3: integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-uri@^3.0.1: - version "3.0.6" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" - integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== fastest-levenshtein@^1.0.12: version "1.0.16" @@ -1695,7 +1701,7 @@ interpret@^3.1.1: resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== -is-core-module@^2.16.0: +is-core-module@^2.16.1: version "2.16.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== @@ -1743,16 +1749,11 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -jsesc@^3.0.2: +jsesc@^3.0.2, jsesc@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== -jsesc@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" - integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== - json-fixer@^1.6.8: version "1.6.15" resolved "https://registry.yarnpkg.com/json-fixer/-/json-fixer-1.6.15.tgz#f1f03b6771fcb383695d458c53e50b10999fba7f" @@ -1782,10 +1783,10 @@ kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -loader-runner@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" - integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== +loader-runner@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.1.tgz#6c76ed29b0ccce9af379208299f07f876de737e3" + integrity sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q== locate-path@^3.0.0: version "3.0.0" @@ -1879,10 +1880,10 @@ node-fetch@^2.6.0: dependencies: whatwg-url "^5.0.0" -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== +node-releases@^2.0.27: + version "2.0.27" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" + integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== onetime@^5.1.0: version "5.1.2" @@ -2010,10 +2011,10 @@ rechoir@^0.8.0: dependencies: resolve "^1.20.0" -regenerate-unicode-properties@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" - integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA== +regenerate-unicode-properties@^10.2.2: + version "10.2.2" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz#aa113812ba899b630658c7623466be71e1f86f66" + integrity sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g== dependencies: regenerate "^1.4.2" @@ -2022,29 +2023,29 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regexpu-core@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.2.0.tgz#0e5190d79e542bf294955dccabae04d3c7d53826" - integrity sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA== +regexpu-core@^6.3.1: + version "6.4.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.4.0.tgz#3580ce0c4faedef599eccb146612436b62a176e5" + integrity sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA== dependencies: regenerate "^1.4.2" - regenerate-unicode-properties "^10.2.0" + regenerate-unicode-properties "^10.2.2" regjsgen "^0.8.0" - regjsparser "^0.12.0" + regjsparser "^0.13.0" unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.1.0" + unicode-match-property-value-ecmascript "^2.2.1" regjsgen@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== -regjsparser@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.12.0.tgz#0e846df6c6530586429377de56e0475583b088dc" - integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ== +regjsparser@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.0.tgz#01f8351335cf7898d43686bc74d2dd71c847ecc0" + integrity sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q== dependencies: - jsesc "~3.0.2" + jsesc "~3.1.0" require-directory@^2.1.1: version "2.1.1" @@ -2074,11 +2075,11 @@ resolve-from@^5.0.0: integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve@^1.20.0, resolve@^1.22.10: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== dependencies: - is-core-module "^2.16.0" + is-core-module "^2.16.1" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -2112,10 +2113,10 @@ safe-buffer@^5.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -schema-utils@^4.3.0, schema-utils@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.2.tgz#0c10878bf4a73fd2b1dfd14b9462b26788c806ae" - integrity sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ== +schema-utils@^4.3.0, schema-utils@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" + integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== dependencies: "@types/json-schema" "^7.0.9" ajv "^8.9.0" @@ -2133,9 +2134,9 @@ semver@^6.3.1: integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.3.4: - version "7.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" - integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== serialize-javascript@^6.0.2: version "6.0.2" @@ -2226,10 +2227,10 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tapable@^2.1.1, tapable@^2.2.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.3.tgz#4b67b635b2d97578a06a2713d2f04800c237e99b" - integrity sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg== +tapable@^2.2.0, tapable@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" + integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== terser-webpack-plugin@^5.3.11: version "5.3.14" @@ -2243,12 +2244,12 @@ terser-webpack-plugin@^5.3.11: terser "^5.31.1" terser@^5.31.1: - version "5.43.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.43.1.tgz#88387f4f9794ff1a29e7ad61fb2932e25b4fdb6d" - integrity sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg== + version "5.44.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.44.1.tgz#e391e92175c299b8c284ad6ded609e37303b0a9c" + integrity sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw== dependencies: "@jridgewell/source-map" "^0.3.3" - acorn "^8.14.0" + acorn "^8.15.0" commander "^2.20.0" source-map-support "~0.5.20" @@ -2298,14 +2299,14 @@ type-fest@^0.21.3: integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== typescript@^5.4.5: - version "5.9.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" - integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== -undici-types@~7.10.0: - version "7.10.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.10.0.tgz#4ac2e058ce56b462b056e629cc6a02393d3ff350" - integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag== +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.1" @@ -2320,25 +2321,25 @@ unicode-match-property-ecmascript@^2.0.0: unicode-canonical-property-names-ecmascript "^2.0.0" unicode-property-aliases-ecmascript "^2.0.0" -unicode-match-property-value-ecmascript@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz#a0401aee72714598f739b68b104e4fe3a0cb3c71" - integrity sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg== +unicode-match-property-value-ecmascript@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz#65a7adfad8574c219890e219285ce4c64ed67eaa" + integrity sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg== unicode-property-aliases-ecmascript@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" - integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== + version "2.2.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz#301d4f8a43d2b75c97adfad87c9dd5350c9475d1" + integrity sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ== -update-browserslist-db@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" - integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== +update-browserslist-db@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz#7802aa2ae91477f255b86e0e46dbc787a206ad4a" + integrity sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A== dependencies: escalade "^3.2.0" picocolors "^1.1.1" -watchpack@^2.4.1: +watchpack@^2.4.4: version "2.4.4" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.4.tgz#473bda72f0850453da6425081ea46fc0d7602947" integrity sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA== @@ -2385,9 +2386,9 @@ webpack-sources@^3.3.3: integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg== webpack@^5.94.0: - version "5.101.3" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.101.3.tgz#3633b2375bb29ea4b06ffb1902734d977bc44346" - integrity sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A== + version "5.103.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.103.0.tgz#17a7c5a5020d5a3a37c118d002eade5ee2c6f3da" + integrity sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw== dependencies: "@types/eslint-scope" "^3.7.7" "@types/estree" "^1.0.8" @@ -2397,7 +2398,7 @@ webpack@^5.94.0: "@webassemblyjs/wasm-parser" "^1.14.1" acorn "^8.15.0" acorn-import-phases "^1.0.3" - browserslist "^4.24.0" + browserslist "^4.26.3" chrome-trace-event "^1.0.2" enhanced-resolve "^5.17.3" es-module-lexer "^1.2.1" @@ -2406,13 +2407,13 @@ webpack@^5.94.0: glob-to-regexp "^0.4.1" graceful-fs "^4.2.11" json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" + loader-runner "^4.3.1" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^4.3.2" - tapable "^2.1.1" + schema-utils "^4.3.3" + tapable "^2.3.0" terser-webpack-plugin "^5.3.11" - watchpack "^2.4.1" + watchpack "^2.4.4" webpack-sources "^3.3.3" whatwg-url@^5.0.0: