Merge pull request #151 from SamR1/security-fixes

fix security issues
This commit is contained in:
Sam 2022-02-13 14:25:33 +01:00 committed by GitHub
commit 9522679eb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 331 additions and 137 deletions

View File

@ -77,36 +77,6 @@ class TestRegistration:
errors = selenium.find_element_by_class_name('error-message').text
assert 'Sorry, that user already exists.' in errors
def test_user_can_not_register_if_username_is_too_short(self, selenium):
user_name = random_string(2)
user_infos = {
'username': user_name,
'email': 'admin@example.com',
'password': 'p@ssw0rd',
'password_conf': 'p@ssw0rd',
}
register(selenium, user_infos)
assert selenium.current_url == URL
errors = selenium.find_element_by_class_name('error-message').text
assert 'Username: 3 to 12 characters required' in errors
def test_user_can_not_register_if_username_is_too_long(self, selenium):
user_name = random_string(13)
user_infos = {
'username': user_name,
'email': 'admin@example.com',
'password': 'p@ssw0rd',
'password_conf': 'p@ssw0rd',
}
register(selenium, user_infos)
assert selenium.current_url == URL
errors = selenium.find_element_by_class_name('error-message').text
assert 'Username: 3 to 12 characters required' in errors
def test_it_displays_error_if_passwords_do_not_match(self, selenium):
user_name = random_string()
user_infos = {
@ -121,18 +91,3 @@ class TestRegistration:
assert selenium.current_url == URL
errors = selenium.find_element_by_class_name('error-message').text
assert 'password and password confirmation don\'t match' in errors
def test_it_displays_error_if_password_is_too_short(self, selenium):
user_name = random_string()
user_infos = {
'username': user_name,
'email': f'{user_name}@example.com',
'password': 'p@ss',
'password_conf': 'p@ss',
}
register(selenium, user_infos)
assert selenium.current_url == URL
errors = selenium.find_element_by_class_name('error-message').text
assert 'Password: 8 characters required' in errors

View File

@ -3,7 +3,13 @@ import os
from importlib import import_module, reload
from typing import Any
from flask import Flask, Response, render_template, send_file
from flask import (
Flask,
Response,
render_template,
send_file,
send_from_directory,
)
from flask_bcrypt import Bcrypt
from flask_dramatiq import Dramatiq
from flask_migrate import Migrate
@ -112,12 +118,12 @@ def create_app() -> Flask:
def catch_all(path: str) -> Any:
# workaround to serve images (not in static directory)
if path.startswith('img/'):
return send_file(
os.path.join(
return send_from_directory(
directory=os.path.join(
app.root_path, # type: ignore
'dist',
path,
)
),
path=path,
)
else:
return render_template('index.html')

View File

@ -1 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"><link rel="stylesheet" href="/static/css/leaflet.css"><title>FitTrackee</title><link href="/static/css/admin.e77f8b26.css" rel="prefetch"><link href="/static/css/profile.8b668068.css" rel="prefetch"><link href="/static/css/reset.fc19709e.css" rel="prefetch"><link href="/static/css/statistics.2afdc8a9.css" rel="prefetch"><link href="/static/css/workouts.1bed04b1.css" rel="prefetch"><link href="/static/js/admin.5f46d0fe.js" rel="prefetch"><link href="/static/js/chunk-2d0c9189.c81458cc.js" rel="prefetch"><link href="/static/js/chunk-2d0cf391.020c75ea.js" rel="prefetch"><link href="/static/js/chunk-2d0da8f3.c8c3e7e8.js" rel="prefetch"><link href="/static/js/chunk-2d2248b6.d84473c1.js" rel="prefetch"><link href="/static/js/chunk-2d22523a.4b710d99.js" rel="prefetch"><link href="/static/js/profile.d25975e2.js" rel="prefetch"><link href="/static/js/reset.ca898ebe.js" rel="prefetch"><link href="/static/js/statistics.d03ca304.js" rel="prefetch"><link href="/static/js/workouts.ca40c08d.js" rel="prefetch"><link href="/static/css/app.2a651958.css" rel="preload" as="style"><link href="/static/js/app.b4ca8b9a.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.9e32143c.js" rel="preload" as="script"><link href="/static/css/app.2a651958.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.9e32143c.js"></script><script src="/static/js/app.b4ca8b9a.js"></script></body></html>
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"><link rel="stylesheet" href="/static/css/leaflet.css"><title>FitTrackee</title><link href="/static/css/admin.e77f8b26.css" rel="prefetch"><link href="/static/css/profile.8b668068.css" rel="prefetch"><link href="/static/css/reset.fc19709e.css" rel="prefetch"><link href="/static/css/statistics.2afdc8a9.css" rel="prefetch"><link href="/static/css/workouts.1bed04b1.css" rel="prefetch"><link href="/static/js/admin.5f46d0fe.js" rel="prefetch"><link href="/static/js/chunk-2d0c9189.c81458cc.js" rel="prefetch"><link href="/static/js/chunk-2d0cf391.020c75ea.js" rel="prefetch"><link href="/static/js/chunk-2d0da8f3.c8c3e7e8.js" rel="prefetch"><link href="/static/js/chunk-2d2248b6.d84473c1.js" rel="prefetch"><link href="/static/js/chunk-2d22523a.4b710d99.js" rel="prefetch"><link href="/static/js/profile.d25975e2.js" rel="prefetch"><link href="/static/js/reset.ca898ebe.js" rel="prefetch"><link href="/static/js/statistics.d03ca304.js" rel="prefetch"><link href="/static/js/workouts.ca40c08d.js" rel="prefetch"><link href="/static/css/app.f2234171.css" rel="preload" as="style"><link href="/static/js/app.ad2630ed.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.4605b41e.js" rel="preload" as="script"><link href="/static/css/app.f2234171.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.4605b41e.js"></script><script src="/static/js/app.ad2630ed.js"></script></body></html>

View File

@ -72,7 +72,7 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "/img/workouts/start.svg"
},
{
"revision": "7057a7518a8d7dea7e36686fce63f90a",
"revision": "9bf054725eec8a2ca4d9b1be2cbd785a",
"url": "/index.html"
},
{
@ -88,8 +88,8 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "/static/css/admin.e77f8b26.css"
},
{
"revision": "2bb508df8ba5c7d7dae2",
"url": "/static/css/app.2a651958.css"
"revision": "9c72219a46ba089d3797",
"url": "/static/css/app.f2234171.css"
},
{
"revision": "82c1118c918377daaa71a320ab8eea42",
@ -200,8 +200,8 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "/static/js/admin.5f46d0fe.js"
},
{
"revision": "2bb508df8ba5c7d7dae2",
"url": "/static/js/app.b4ca8b9a.js"
"revision": "9c72219a46ba089d3797",
"url": "/static/js/app.ad2630ed.js"
},
{
"revision": "bd7d183c9f68e5f4027d",
@ -224,8 +224,8 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "/static/js/chunk-2d22523a.4b710d99.js"
},
{
"revision": "5d586e72e98e86692a20",
"url": "/static/js/chunk-vendors.9e32143c.js"
"revision": "4b5b226c28a37969ec2f",
"url": "/static/js/chunk-vendors.4605b41e.js"
},
{
"revision": "00382d944a1bc6fca08b",

View File

@ -14,7 +14,7 @@
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
importScripts(
"/precache-manifest.52d5b42c4dad9b2fb5fdfae14e5703bb.js"
"/precache-manifest.d71a3616485ce0a8106a21b6c55baaa4.js"
);
workbox.core.setCacheNameDetails({prefix: "fittrackee_client"});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,17 @@
from unittest.mock import patch
import pytest
from flask import Flask
from fittrackee.users.exceptions import UserNotFoundException
from fittrackee.users.models import User
from fittrackee.users.utils import set_admin_rights
from fittrackee.users.utils import (
check_passwords,
check_username,
is_valid_email,
register_controls,
set_admin_rights,
)
from ..utils import random_string
@ -28,3 +36,179 @@ class TestSetAdminRights:
set_admin_rights(user_1_admin.username)
assert user_1_admin.admin is True
class TestIsValidEmail:
@pytest.mark.parametrize(
('input_email',),
[
('',),
('foo',),
('foo@',),
('@foo.fr',),
('foo@foo',),
('.',),
('./',),
],
)
def test_it_returns_false_if_email_is_invalid(
self, input_email: str
) -> None:
assert is_valid_email(input_email) is False
@pytest.mark.parametrize(
('input_email',),
[
('admin@example.com',),
('admin@test.example.com',),
('admin.site@test.example.com',),
('admin-site@test-example.com',),
],
)
def test_it_returns_true_if_email_is_valid(self, input_email: str) -> None:
assert is_valid_email(input_email) is True
class TestCheckPasswords:
def test_it_returns_error_message_string_if_passwords_do_not_match(
self,
) -> None:
assert check_passwords('password', 'pasword') == (
'password: password and password confirmation do not match\n'
)
@pytest.mark.parametrize(
('input_password_length',),
[
(0,),
(3,),
(7,),
],
)
def test_it_returns_error_message_string_if_password_length_is_below_8_characters( # noqa
self, input_password_length: int
) -> None:
password = random_string(input_password_length)
assert check_passwords(password, password) == (
'password: 8 characters required\n'
)
@pytest.mark.parametrize(
('input_password_length',),
[
(8,),
(10,),
],
)
def test_it_returns_empty_string_when_password_length_exceeds_7_characters(
self, input_password_length: int
) -> None:
password = random_string(input_password_length)
assert check_passwords(password, password) == ''
def test_it_returns_multiple_errors(self) -> None:
password = random_string(3)
password_conf = random_string(8)
assert check_passwords(password, password_conf) == (
'password: password and password confirmation do not match\n'
'password: 8 characters required\n'
)
class TestIsUsernameValid:
@pytest.mark.parametrize(
('input_username_length',),
[
(2,),
(13,),
],
)
def test_it_returns_error_message_when_username_length_is_invalid(
self, input_username_length: int
) -> None:
assert (
check_username(
username=random_string(input_username_length),
)
== 'username: 3 to 12 characters required\n'
)
@pytest.mark.parametrize(
('input_invalid_character',),
[
('.',),
('/',),
('$',),
],
)
def test_it_returns_error_message_when_username_has_invalid_character(
self, input_invalid_character: str
) -> None:
username = random_string() + input_invalid_character
assert check_username(username=username) == (
'username: only alphanumeric characters and the '
'underscore character "_" allowed\n'
)
def test_it_returns_empty_string_when_username_is_valid(self) -> None:
assert check_username(username=random_string()) == ''
def test_it_returns_multiple_errors(self) -> None:
username = random_string(1) + '.'
assert check_username(username=username) == (
'username: 3 to 12 characters required\n'
'username: only alphanumeric characters and the underscore '
'character "_" allowed\n'
)
class TestRegisterControls:
module_path = 'fittrackee.users.utils.'
valid_username = random_string()
valid_email = f'{random_string()}@example.com'
valid_password = random_string()
def test_it_calls_all_validators(self) -> None:
with patch(
self.module_path + 'check_passwords'
) as check_passwords_mock, patch(
self.module_path + 'check_username'
) as check_username_mock, patch(
self.module_path + 'is_valid_email'
) as is_valid_email_mock:
register_controls(
self.valid_username,
self.valid_email,
self.valid_password,
self.valid_password,
)
check_passwords_mock.assert_called_once_with(
self.valid_password, self.valid_password
)
check_username_mock.assert_called_once_with(self.valid_username)
is_valid_email_mock.assert_called_once_with(self.valid_email)
def test_it_returns_empty_string_when_inputs_are_valid(self) -> None:
assert (
register_controls(
self.valid_username,
self.valid_email,
self.valid_password,
self.valid_password,
)
== ''
)
def test_it_returns_multiple_errors_when_inputs_are_invalid(self) -> None:
invalid_username = random_string(2)
assert register_controls(
username=invalid_username,
email=invalid_username,
password=random_string(8),
password_conf=random_string(8),
) == (
'username: 3 to 12 characters required\n'
'email: valid email must be provided\n'
'password: password and password confirmation do not match\n'
)

View File

@ -37,6 +37,21 @@ def check_passwords(password: str, password_conf: str) -> str:
return ret
def check_username(username: str) -> str:
"""
Return if username is valid
"""
ret = ''
if not 2 < len(username) < 13:
ret += 'username: 3 to 12 characters required\n'
if not re.match(r'^[a-zA-Z0-9_]+$', username):
ret += (
'username: only alphanumeric characters and the '
'underscore character "_" allowed\n'
)
return ret
def register_controls(
username: str, email: str, password: str, password_conf: str
) -> str:
@ -45,9 +60,7 @@ def register_controls(
If not, it returns not empty string
"""
ret = ''
if not 2 < len(username) < 13:
ret += 'username: 3 to 12 characters required\n'
ret = check_username(username)
if not is_valid_email(email):
ret += 'email: valid email must be provided\n'
ret += check_passwords(password, password_conf)

View File

@ -14,6 +14,7 @@ from flask import (
)
from sqlalchemy import exc
from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.utils import secure_filename
from fittrackee import appLog, db
from fittrackee.responses import (
@ -829,7 +830,12 @@ def get_map_tile(s: str, z: str, x: str, y: str) -> Tuple[Response, int]:
Status codes are status codes returned by tile server
"""
url = current_app.config['TILE_SERVER']['URL'].format(s=s, z=z, x=x, y=y)
url = current_app.config['TILE_SERVER']['URL'].format(
s=secure_filename(s),
z=secure_filename(z),
x=secure_filename(x),
y=secure_filename(y),
)
headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:88.0)'}
response = requests.get(url, headers=headers)
return (

View File

@ -21,6 +21,9 @@
id="username"
:disabled="registration_disabled"
required
pattern="[a-zA-Z0-9_]+"
minlength="3"
maxlength="12"
@invalid="invalidateForm"
v-model="formData.username"
:placeholder="$t('user.USERNAME')"
@ -46,6 +49,7 @@
required
@invalid="invalidateForm"
type="password"
minlength="8"
v-model="formData.password"
:placeholder="
action === 'reset'
@ -58,6 +62,7 @@
id="confirm-password"
:disabled="registration_disabled"
type="password"
minlength="8"
required
@invalid="invalidateForm"
v-model="formData.password_conf"

View File

@ -17,13 +17,11 @@
"no selected file": "No selected file.",
"password: password and password confirmation do not match": "Password: password and password confirmation don't match.",
"provide a valid auth token": "Provide a valid auth token.",
"password: 8 characters required": "Password: 8 characters required.",
"sorry, that user already exists": "Sorry, that user already exists.",
"sport does not exist": "Sport does not exist.",
"signature expired, please log in again": "Signature expired. Please log in again.",
"successfully registered": "Successfully registered.",
"user does not exist": "User does not exist.",
"username: 3 to 12 characters required": "Username: 3 to 12 characters required.",
"you can not delete your account, no other user has admin rights": "You can not delete your account, no other user has admin rights.",
"you do not have permissions": "You do not have permissions."
},

View File

@ -17,13 +17,11 @@
"Network Error": "Erreur Réseau.",
"password: password and password confirmation do not match": "Mot de passe : les mots de passe saisis sont différents.",
"provide a valid auth token": "Merci de fournir un jeton de connexion valide.",
"password: 8 characters required": "Mot de passe : 8 caractères minimum.",
"sport does not exist": "Ce sport n'existe pas.",
"signature expired, please log in again": "Signature expirée. Merci de vous reconnecter.",
"sorry, that user already exists": "Désolé, cet utilisateur existe déjà.",
"successfully registered": "Inscription validée.",
"user does not exist": "L'utilisateur n'existe pas",
"username: 3 to 12 characters required": "Nom d'utilisateur : 3 à 12 caractères requis.",
"you can not delete your account, no other user has admin rights": "Vous ne pouvez pas supprimer votre compte, aucun autre utilisateur n'a des droits d'administration.",
"you do not have permissions": "Vous n'avez pas les permissions nécessaires."
},

View File

@ -1687,47 +1687,47 @@
semver "^6.1.0"
strip-ansi "^6.0.0"
"@vue/compiler-core@3.2.29":
version "3.2.29"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.29.tgz#b06097ab8ff0493177c68c5ea5b63d379a061097"
integrity sha512-RePZ/J4Ub3sb7atQw6V6Rez+/5LCRHGFlSetT3N4VMrejqJnNPXKUt5AVm/9F5MJriy2w/VudEIvgscCfCWqxw==
"@vue/compiler-core@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.31.tgz#d38f06c2cf845742403b523ab4596a3fda152e89"
integrity sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/shared" "3.2.29"
"@vue/shared" "3.2.31"
estree-walker "^2.0.2"
source-map "^0.6.1"
"@vue/compiler-dom@3.2.29":
version "3.2.29"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.29.tgz#ad0ead405bd2f2754161335aad9758aa12430715"
integrity sha512-y26vK5khdNS9L3ckvkqJk/78qXwWb75Ci8iYLb67AkJuIgyKhIOcR1E8RIt4mswlVCIeI9gQ+fmtdhaiTAtrBQ==
"@vue/compiler-dom@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz#b1b7dfad55c96c8cc2b919cd7eb5fd7e4ddbf00e"
integrity sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg==
dependencies:
"@vue/compiler-core" "3.2.29"
"@vue/shared" "3.2.29"
"@vue/compiler-core" "3.2.31"
"@vue/shared" "3.2.31"
"@vue/compiler-sfc@3.2.29":
version "3.2.29"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.29.tgz#f76d556cd5fca6a55a3ea84c88db1a2a53a36ead"
integrity sha512-X9+0dwsag2u6hSOP/XsMYqFti/edvYvxamgBgCcbSYuXx1xLZN+dS/GvQKM4AgGS4djqo0jQvWfIXdfZ2ET68g==
"@vue/compiler-sfc@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz#d02b29c3fe34d599a52c5ae1c6937b4d69f11c2f"
integrity sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.29"
"@vue/compiler-dom" "3.2.29"
"@vue/compiler-ssr" "3.2.29"
"@vue/reactivity-transform" "3.2.29"
"@vue/shared" "3.2.29"
"@vue/compiler-core" "3.2.31"
"@vue/compiler-dom" "3.2.31"
"@vue/compiler-ssr" "3.2.31"
"@vue/reactivity-transform" "3.2.31"
"@vue/shared" "3.2.31"
estree-walker "^2.0.2"
magic-string "^0.25.7"
postcss "^8.1.10"
source-map "^0.6.1"
"@vue/compiler-ssr@3.2.29":
version "3.2.29"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.29.tgz#37b15b32dcd2f6b410bb61fca3f37b1a92b7eb1e"
integrity sha512-LrvQwXlx66uWsB9/VydaaqEpae9xtmlUkeSKF6aPDbzx8M1h7ukxaPjNCAXuFd3fUHblcri8k42lfimHfzMICA==
"@vue/compiler-ssr@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz#4fa00f486c9c4580b40a4177871ebbd650ecb99c"
integrity sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw==
dependencies:
"@vue/compiler-dom" "3.2.29"
"@vue/shared" "3.2.29"
"@vue/compiler-dom" "3.2.31"
"@vue/shared" "3.2.31"
"@vue/component-compiler-utils@^3.1.0", "@vue/component-compiler-utils@^3.1.2":
version "3.3.0"
@ -1769,14 +1769,14 @@
resolved "https://registry.yarnpkg.com/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz#ceb924b4ecb3b9c43871c7a429a02f8423e621ab"
integrity sha512-LIZMuJk38pk9U9Ur4YzHjlIyMuxPlACdBIHH9/nGYVTsaGKOSnSuELiE8vS9wa+dJpIYspYUOqk+L1Q4pgHQHQ==
"@vue/reactivity-transform@3.2.29":
version "3.2.29"
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.29.tgz#a08d606e10016b7cf588d1a43dae4db2953f9354"
integrity sha512-YF6HdOuhdOw6KyRm59+3rML8USb9o8mYM1q+SH0G41K3/q/G7uhPnHGKvspzceD7h9J3VR1waOQ93CUZj7J7OA==
"@vue/reactivity-transform@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz#0f5b25c24e70edab2b613d5305c465b50fc00911"
integrity sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.29"
"@vue/shared" "3.2.29"
"@vue/compiler-core" "3.2.31"
"@vue/shared" "3.2.31"
estree-walker "^2.0.2"
magic-string "^0.25.7"
@ -1787,6 +1787,13 @@
dependencies:
"@vue/shared" "3.2.29"
"@vue/reactivity@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.31.tgz#fc90aa2cdf695418b79e534783aca90d63a46bbd"
integrity sha512-HVr0l211gbhpEKYr2hYe7hRsV91uIVGFYNHj73njbARVGHQvIojkImKMaZNDdoDZOIkMsBc9a1sMqR+WZwfSCw==
dependencies:
"@vue/shared" "3.2.31"
"@vue/runtime-core@3.2.29", "@vue/runtime-core@latest":
version "3.2.29"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.29.tgz#fb8577b2fcf52e8d967bd91cdf49ab9fb91f9417"
@ -1795,7 +1802,24 @@
"@vue/reactivity" "3.2.29"
"@vue/shared" "3.2.29"
"@vue/runtime-dom@3.2.29", "@vue/runtime-dom@latest":
"@vue/runtime-core@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.31.tgz#9d284c382f5f981b7a7b5971052a1dc4ef39ac7a"
integrity sha512-Kcog5XmSY7VHFEMuk4+Gap8gUssYMZ2+w+cmGI6OpZWYOEIcbE0TPzzPHi+8XTzAgx1w/ZxDFcXhZeXN5eKWsA==
dependencies:
"@vue/reactivity" "3.2.31"
"@vue/shared" "3.2.31"
"@vue/runtime-dom@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.31.tgz#79ce01817cb3caf2c9d923f669b738d2d7953eff"
integrity sha512-N+o0sICVLScUjfLG7u9u5XCjvmsexAiPt17GNnaWHJUfsKed5e85/A3SWgKxzlxx2SW/Hw7RQxzxbXez9PtY3g==
dependencies:
"@vue/runtime-core" "3.2.31"
"@vue/shared" "3.2.31"
csstype "^2.6.8"
"@vue/runtime-dom@latest":
version "3.2.29"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.29.tgz#35e9a2bf04ef80b86ac2ca0e7b2ceaccf1e18f01"
integrity sha512-YJgLQLwr+SQyORzTsBQLL5TT/5UiV83tEotqjL7F9aFDIQdFBTCwpkCFvX9jqwHoyi9sJqM9XtTrMcc8z/OjPA==
@ -1804,19 +1828,24 @@
"@vue/shared" "3.2.29"
csstype "^2.6.8"
"@vue/server-renderer@3.2.29":
version "3.2.29"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.29.tgz#ea6afa361b9c781a868c8da18c761f9b7bc89102"
integrity sha512-lpiYx7ciV7rWfJ0tPkoSOlLmwqBZ9FTmQm33S+T4g0j1fO/LmhJ9b9Ctl1o5xvIFVDk9QkSUWANZn7H2pXuxVw==
"@vue/server-renderer@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.31.tgz#201e9d6ce735847d5989403af81ef80960da7141"
integrity sha512-8CN3Zj2HyR2LQQBHZ61HexF5NReqngLT3oahyiVRfSSvak+oAvVmu8iNLSu6XR77Ili2AOpnAt1y8ywjjqtmkg==
dependencies:
"@vue/compiler-ssr" "3.2.29"
"@vue/shared" "3.2.29"
"@vue/compiler-ssr" "3.2.31"
"@vue/shared" "3.2.31"
"@vue/shared@3.2.29":
version "3.2.29"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.29.tgz#07dac7051117236431d2f737d16932aa38bbb925"
integrity sha512-BjNpU8OK6Z0LVzGUppEk0CMYm/hKDnZfYdjSmPOs0N+TR1cLKJAkDwW8ASZUvaaSLEi6d3hVM7jnWnX+6yWnHw==
"@vue/shared@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.31.tgz#c90de7126d833dcd3a4c7534d534be2fb41faa4e"
integrity sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ==
"@vue/test-utils@^2.0.0-0":
version "2.0.0-rc.18"
resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-2.0.0-rc.18.tgz#ff22b252424fe72e5462cbb3a8e7405cef11ffb6"
@ -2899,9 +2928,9 @@ chardet@^0.7.0:
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
chart.js@^3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.7.0.tgz#7a19c93035341df801d613993c2170a1fcf1d882"
integrity sha512-31gVuqqKp3lDIFmzpKIrBeum4OpZsQjSIAqlOpgjosHDJZlULtvwLEZKtEhIAZc7JMPaHlYMys40Qy9Mf+1AAg==
version "3.7.1"
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.7.1.tgz#0516f690c6a8680c6c707e31a4c1807a6f400ada"
integrity sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA==
chartjs-plugin-datalabels@^2.0.0:
version "2.0.0"
@ -9899,15 +9928,15 @@ vue@^2.6.11:
integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==
vue@^3.0.0:
version "3.2.29"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.29.tgz#3571b65dbd796d3a6347e2fd45a8e6e11c13d56a"
integrity sha512-cFIwr7LkbtCRanjNvh6r7wp2yUxfxeM2yPpDQpAfaaLIGZSrUmLbNiSze9nhBJt5MrZ68Iqt0O5scwAMEVxF+Q==
version "3.2.31"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.31.tgz#e0c49924335e9f188352816788a4cca10f817ce6"
integrity sha512-odT3W2tcffTiQCy57nOT93INw1auq5lYLLYtWpPYQQYQOOdHiqFct9Xhna6GJ+pJQaF67yZABraH47oywkJgFw==
dependencies:
"@vue/compiler-dom" "3.2.29"
"@vue/compiler-sfc" "3.2.29"
"@vue/runtime-dom" "3.2.29"
"@vue/server-renderer" "3.2.29"
"@vue/shared" "3.2.29"
"@vue/compiler-dom" "3.2.31"
"@vue/compiler-sfc" "3.2.31"
"@vue/runtime-dom" "3.2.31"
"@vue/server-renderer" "3.2.31"
"@vue/shared" "3.2.31"
vuex@^4.0.0-0:
version "4.0.2"