From b8588c1a10ee6c015440551a6d3937d789be01ca Mon Sep 17 00:00:00 2001 From: Jonas Leder <jonas.leder@jobrouter.com> Date: Fri, 25 Nov 2022 16:43:26 +0100 Subject: [PATCH] add option to enable 2fa --- assets/app.js | 1 + assets/scripts/enable2fa.js | 14 ++ composer.json | 2 + composer.lock | 267 ++++++++++++++++++++++++++- config/bundles.php | 1 + config/packages/scheb_2fa.yaml | 7 + config/packages/security.yaml | 11 +- config/routes/scheb_2fa.yaml | 7 + migrations/Version20221125144505.php | 31 ++++ package.json | 1 + src/Controller/ProfileController.php | 26 +++ src/Entity/Users.php | 29 ++- symfony.lock | 13 ++ templates/pages/profile.html.twig | 15 ++ yarn.lock | 114 +++++++++++- 15 files changed, 531 insertions(+), 8 deletions(-) create mode 100644 assets/scripts/enable2fa.js create mode 100644 config/packages/scheb_2fa.yaml create mode 100644 config/routes/scheb_2fa.yaml create mode 100644 migrations/Version20221125144505.php diff --git a/assets/app.js b/assets/app.js index 894fb9c..53c19df 100644 --- a/assets/app.js +++ b/assets/app.js @@ -14,4 +14,5 @@ import './bootstrap'; //import bootstrap from node modules import 'bootstrap/dist/js/bootstrap.bundle'; +import './scripts/enable2fa'; import './scripts/executeAction'; \ No newline at end of file diff --git a/assets/scripts/enable2fa.js b/assets/scripts/enable2fa.js new file mode 100644 index 0000000..ee17b13 --- /dev/null +++ b/assets/scripts/enable2fa.js @@ -0,0 +1,14 @@ +const QRCode = require('qrcode') + +async function enable2FA(element){ + const totpDiv = document.getElementById('totpSetup'); + const totpConfig = await (await fetch('/profile/totp/config')).json(); + + totpDiv.querySelector('img').src = await QRCode.toDataURL(totpConfig.qrData); + totpDiv.querySelector('code').innerText = totpConfig.secret; + totpDiv.style.display = 'block'; + element.style.display = 'none'; + +} + +window.enable2FA = enable2FA; \ No newline at end of file diff --git a/composer.json b/composer.json index 6e5b6c4..989386b 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,8 @@ "doctrine/doctrine-bundle": "^2.7", "doctrine/doctrine-migrations-bundle": "^3.2", "doctrine/orm": "^2.13", + "scheb/2fa-bundle": "^6.3", + "scheb/2fa-google-authenticator": "^6.3", "symfony/console": "6.1.*", "symfony/dotenv": "6.1.*", "symfony/flex": "^2", diff --git a/composer.lock b/composer.lock index c2022fe..9c6b42c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dc01e869df0e37c9e7132c30e1e9ef35", + "content-hash": "8357892ff73f4558e202c3dc4a78a0a0", "packages": [ { "name": "doctrine/annotations", @@ -1524,6 +1524,73 @@ ], "time": "2022-11-21T01:32:31+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.6.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "58c3f47f650c94ec05a151692652a868995d2938" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", + "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "shasum": "" + }, + "require": { + "php": "^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "vimeo/psalm": "^1|^2|^3|^4" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2022-06-14T06:56:20+00:00" + }, { "name": "psr/cache", "version": "3.0.0", @@ -1726,6 +1793,204 @@ }, "time": "2021-07-14T16:46:02+00:00" }, + { + "name": "scheb/2fa-bundle", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/scheb/2fa-bundle.git", + "reference": "ffd3e73ba2984a326ea554663de85e58ef221f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scheb/2fa-bundle/zipball/ffd3e73ba2984a326ea554663de85e58ef221f11", + "reference": "ffd3e73ba2984a326ea554663de85e58ef221f11", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "~8.0.0 || ~8.1.0", + "symfony/config": "^5.4 || ^6.0", + "symfony/dependency-injection": "^5.4 || ^6.0", + "symfony/event-dispatcher": "^5.4 || ^6.0", + "symfony/framework-bundle": "^5.4 || ^6.0", + "symfony/http-foundation": "^5.4 || ^6.0", + "symfony/http-kernel": "^5.4 || ^6.0", + "symfony/property-access": "^5.4 || ^6.0", + "symfony/security-bundle": "^5.4 || ^6.0", + "symfony/twig-bundle": "^5.4 || ^6.0" + }, + "conflict": { + "scheb/two-factor-bundle": "*" + }, + "suggest": { + "scheb/2fa-backup-code": "Emergency codes when you have no access to other methods", + "scheb/2fa-email": "Send codes by email", + "scheb/2fa-google-authenticator": "Google Authenticator support", + "scheb/2fa-totp": "Temporary one-time password (TOTP) support (Google Authenticator compatible)", + "scheb/2fa-trusted-device": "Trusted devices support" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Scheb\\TwoFactorBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Scheb", + "email": "me@christianscheb.de" + } + ], + "description": "A generic interface to implement two-factor authentication in Symfony applications", + "homepage": "https://github.com/scheb/2fa", + "keywords": [ + "2fa", + "Authentication", + "symfony", + "two-factor", + "two-step" + ], + "support": { + "source": "https://github.com/scheb/2fa-bundle/tree/v6.3.0" + }, + "time": "2022-09-01T17:42:58+00:00" + }, + { + "name": "scheb/2fa-google-authenticator", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/scheb/2fa-google-authenticator.git", + "reference": "fd0c2533f287d9687f9317fd48ff44d2e613e32a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scheb/2fa-google-authenticator/zipball/fd0c2533f287d9687f9317fd48ff44d2e613e32a", + "reference": "fd0c2533f287d9687f9317fd48ff44d2e613e32a", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^2.4", + "php": "~8.0.0 || ~8.1.0", + "scheb/2fa-bundle": "self.version", + "spomky-labs/otphp": "^10.0 || ^11.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Scheb\\TwoFactorBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Scheb", + "email": "me@christianscheb.de" + } + ], + "description": "Extends scheb/2fa-bundle with two-factor authentication using Google Authenticator", + "homepage": "https://github.com/scheb/2fa", + "keywords": [ + "2fa", + "Authentication", + "google-authenticator", + "symfony", + "two-factor", + "two-step" + ], + "support": { + "source": "https://github.com/scheb/2fa-google-authenticator/tree/v6.3.0" + }, + "time": "2022-08-01T17:19:20+00:00" + }, + { + "name": "spomky-labs/otphp", + "version": "11.1.0", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/otphp.git", + "reference": "4849ac1aa560bfc56c0d1534b0d72532da4665ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/4849ac1aa560bfc56c0d1534b0d72532da4665ab", + "reference": "4849ac1aa560bfc56c0d1534b0d72532da4665ab", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "paragonie/constant_time_encoding": "^2.0", + "php": "^8.1" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "infection/infection": "^0.26", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5.26", + "qossmic/deptrac-shim": "^1.0", + "rector/rector": "^0.14", + "symfony/phpunit-bridge": "^6.1", + "symplify/easy-coding-standard": "^11.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "OTPHP\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/otphp/contributors" + } + ], + "description": "A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator", + "homepage": "https://github.com/Spomky-Labs/otphp", + "keywords": [ + "FreeOTP", + "RFC 4226", + "RFC 6238", + "google authenticator", + "hotp", + "otp", + "totp" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/otphp/issues", + "source": "https://github.com/Spomky-Labs/otphp/tree/11.1.0" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2022-11-11T12:57:17+00:00" + }, { "name": "symfony/asset", "version": "v6.1.5", diff --git a/config/bundles.php b/config/bundles.php index 3bdd1cd..657ab67 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -11,4 +11,5 @@ return [ Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true], + Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true], ]; diff --git a/config/packages/scheb_2fa.yaml b/config/packages/scheb_2fa.yaml new file mode 100644 index 0000000..ad4716e --- /dev/null +++ b/config/packages/scheb_2fa.yaml @@ -0,0 +1,7 @@ +# See the configuration reference at https://symfony.com/bundles/SchebTwoFactorBundle/6.x/configuration.html +scheb_two_factor: + security_tokens: + - Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken + - Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken + google: + enabled: true \ No newline at end of file diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 7ab2cb8..c019c69 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -27,6 +27,9 @@ security: - enabled user_checker: App\Security\UserEnabledChecker login_throttling: true + two_factor: + auth_form_path: 2fa_login + check_path: 2fa_login_check # activate different ways to authenticate # https://symfony.com/doc/current/security.html#the-firewall @@ -37,9 +40,11 @@ security: # Easy way to control access for large sections of your site # Note: Only the *first* access control that matches will be used access_control: - - { path: ^/login, roles: PUBLIC_ACCESS } - - { path: ^/admin, roles: ROLE_ADMIN } - - { path: ^/, roles: ROLE_USER } + - { path: ^/logout, role: PUBLIC_ACCESS } + - { path: ^/login, roles: PUBLIC_ACCESS } + - { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS } + - { path: ^/admin, roles: ROLE_ADMIN } + - { path: ^/, roles: ROLE_USER } when@test: security: diff --git a/config/routes/scheb_2fa.yaml b/config/routes/scheb_2fa.yaml new file mode 100644 index 0000000..9a8ca66 --- /dev/null +++ b/config/routes/scheb_2fa.yaml @@ -0,0 +1,7 @@ +2fa_login: + path: /2fa + defaults: + _controller: "scheb_two_factor.form_controller::form" + +2fa_login_check: + path: /2fa_check diff --git a/migrations/Version20221125144505.php b/migrations/Version20221125144505.php new file mode 100644 index 0000000..21e6590 --- /dev/null +++ b/migrations/Version20221125144505.php @@ -0,0 +1,31 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20221125144505 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE users ADD totp_secret VARCHAR(255) DEFAULT NULL, CHANGE enabled enabled TINYINT(1) NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE users DROP totp_secret, CHANGE enabled enabled TINYINT(1) DEFAULT 1 NOT NULL'); + } +} diff --git a/package.json b/package.json index 33ce464..4ac63c5 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "bootstrap": "^5.2.2", "bootstrap-dark-5": "^1.1.3", "fork-awesome": "^1.2.0", + "qrcode": "^1.5.1", "sass": "^1.56.1", "sass-loader": "^13.2.0" } diff --git a/src/Controller/ProfileController.php b/src/Controller/ProfileController.php index 7d8e75f..fad9567 100644 --- a/src/Controller/ProfileController.php +++ b/src/Controller/ProfileController.php @@ -4,9 +4,12 @@ namespace App\Controller; use App\Entity\Users; use Doctrine\Persistence\ManagerRegistry; +use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Routing\Annotation\Route; @@ -61,4 +64,27 @@ class ProfileController extends AbstractController ]); return $this->redirectToRoute('app_profile'); } + + #[Route('/profile/totpEnable', name:'app_profile_totp_enable', methods:['POST'])] + public function enableTotp() { + return $this->redirectToRoute('app_profile'); + } + + #[Route('/profile/totp/config', methods: ['GET'])] + public function totpConfig(GoogleAuthenticatorInterface $totpInterface, ManagerRegistry $doctrine) { + /** + * @var Users $user + */ + $user = $this->getUser(); + $secret = $totpInterface->generateSecret(); + $user->setGoogleAuthenticatorSecret($secret); + $doctrine->getManager()->flush(); + + return new JsonResponse( + [ + 'secret' => $secret, + 'qrData' => $totpInterface->getQRContent($user), + ] + ); + } } diff --git a/src/Entity/Users.php b/src/Entity/Users.php index 54e7cd4..7532cae 100644 --- a/src/Entity/Users.php +++ b/src/Entity/Users.php @@ -6,11 +6,12 @@ use App\Repository\UsersRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; #[ORM\Entity(repositoryClass: UsersRepository::class)] -class Users implements UserInterface, PasswordAuthenticatedUserInterface +class Users implements UserInterface, PasswordAuthenticatedUserInterface, TwoFactorInterface { #[ORM\Id] #[ORM\GeneratedValue] @@ -38,6 +39,9 @@ class Users implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column] private ?bool $enabled = true; + #[ORM\Column(length: 255, nullable: true)] + private ?string $totpSecret = null; + public function __construct() { $this->webResetter = new ArrayCollection(); @@ -180,4 +184,27 @@ class Users implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + + public function isGoogleAuthenticatorEnabled(): bool + { + return $this->totpSecret != null; + } + + public function getGoogleAuthenticatorUsername(): string + { + return $this->username; + } + + public function getGoogleAuthenticatorSecret(): ?string + { + return $this->totpSecret; + } + + public function setGoogleAuthenticatorSecret(?string $totpSecret): self + { + $this->totpSecret = $totpSecret; + + return $this; + } + } diff --git a/symfony.lock b/symfony.lock index 3b6fe59..3f137ec 100644 --- a/symfony.lock +++ b/symfony.lock @@ -35,6 +35,19 @@ "migrations/.gitignore" ] }, + "scheb/2fa-bundle": { + "version": "6.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.0", + "ref": "1e6f68089146853a790b5da9946fc5974f6fcd49" + }, + "files": [ + "config/packages/scheb_2fa.yaml", + "config/routes/scheb_2fa.yaml" + ] + }, "symfony/console": { "version": "6.1", "recipe": { diff --git a/templates/pages/profile.html.twig b/templates/pages/profile.html.twig index 0a3d7cb..4af9dea 100644 --- a/templates/pages/profile.html.twig +++ b/templates/pages/profile.html.twig @@ -3,6 +3,7 @@ {% block content %} <div class="container"> <form method="post" action="{{ path('app_profile_update') }}" class="bg-dark border rounded-3 p-2 mb-2"> + <h2>Settings</h2> <div class="form-group"> <label for="inputName">Name</label> <input type="text" class="form-control" id="inputName" name="inputName" aria-describedby="inputName" placeholder="John Doe" value="{{ name }}"> @@ -12,6 +13,7 @@ </div> </form> <form method="post" action="{{ path('app_profile_password_change') }}" class="bg-dark border rounded-3 p-2 mb-2"> + <h2>Password</h2> <div class="form-group"> <label for="inputName">Password</label> <input type="password" class="form-control" id="inputPassword" name="inputPassword" aria-describedby="inputPassword" placeholder="Password" minlength="8" required> @@ -24,5 +26,18 @@ <button type="submit" class="btn btn-primary mt-2">Change Password</button> </div> </form> + <div class="bg-dark border rounded-3 p-2 mb-2"> + <h2>2FA</h2> + {% if app.user.googleAuthenticatorEnabled %} + <h4>2FA is already enabled.</h4> + <button class="btn btn-danger mb-3">Disable</button> + {% else %} + <button class="btn btn-primary" onclick="window.enable2FA(this)">Enable</button> + <div id="totpSetup" style="display: none"> + <img src="" alt="TOTP QR Code"> + <p>Alternative you can use this code: <code></code></p> + </div> + {% endif %} + </div> </div> {% endblock %} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 573c48d..f2b4cf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1469,7 +1469,7 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.1.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -1667,6 +1667,11 @@ call-bind@^1.0.0: function-bind "^1.1.1" get-intrinsic "^1.0.2" +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + camelcase@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" @@ -1736,6 +1741,15 @@ clean-webpack-plugin@^4.0.0: dependencies: del "^4.1.1" +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -2012,6 +2026,11 @@ debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + default-gateway@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" @@ -2057,6 +2076,11 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== +dijkstrajs@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.2.tgz#2e48c0d3b825462afe75ab4ad5e829c8ece36257" + integrity sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg== + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" @@ -2126,6 +2150,11 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== +encode-utf8@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" + integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -2343,7 +2372,7 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -find-up@^4.0.0: +find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -2396,6 +2425,11 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + get-intrinsic@^1.0.2: version "1.1.3" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" @@ -3214,6 +3248,11 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + postcss-calc@^8.2.3: version "8.2.4" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" @@ -3487,6 +3526,16 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +qrcode@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.1.tgz#0103f97317409f7bc91772ef30793a54cd59f0cb" + integrity sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg== + dependencies: + dijkstrajs "^1.0.1" + encode-utf8 "^1.0.3" + pngjs "^5.0.0" + yargs "^15.3.1" + qs@6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -3616,11 +3665,21 @@ renderkid@^3.0.0: lodash "^4.17.21" strip-ansi "^6.0.1" +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -3810,6 +3869,11 @@ serve-static@1.15.0: parseurl "~1.3.3" send "0.18.0" +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" @@ -3928,7 +3992,7 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -string-width@^4.2.3: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4297,6 +4361,11 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== + which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -4309,6 +4378,15 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -4319,6 +4397,11 @@ ws@^8.4.2: resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" @@ -4329,7 +4412,32 @@ yaml@^1.10.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^21.0.0: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" -- GitLab