From 8b31fb36774af47ab963917e86d4b7d25245335b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Tue, 13 Sep 2022 13:43:16 +0200 Subject: [PATCH 001/111] Perform a daily trivy scan of latest image --- .github/workflows/trivy.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/trivy.yml diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml new file mode 100644 index 000000000..1619f7ce3 --- /dev/null +++ b/.github/workflows/trivy.yml @@ -0,0 +1,37 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: trivy + +on: + schedule: + - cron: '22 22 * * *' + +jobs: + scan: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Ensure lowercase name + run: echo REPOSITORY_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.7.1 + with: + image-ref: 'ghcr.io/${{ env.REPOSITORY_OWNER }}/dds-backend:latest' + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: 'trivy-results.sarif' From 85b0f001ea6f13122bb009f07d60ad47c655583a Mon Sep 17 00:00:00 2001 From: Ina Date: Tue, 13 Sep 2022 14:40:57 +0200 Subject: [PATCH 002/111] default should not call the function --- dds_web/database/models.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dds_web/database/models.py b/dds_web/database/models.py index 053797ae6..1b2569a22 100644 --- a/dds_web/database/models.py +++ b/dds_web/database/models.py @@ -239,7 +239,7 @@ class Project(db.Model): date_created = db.Column( db.DateTime(), nullable=True, - default=dds_web.utils.current_time(), + default=dds_web.utils.current_time, ) date_updated = db.Column(db.DateTime(), nullable=True) description = db.Column(db.Text) @@ -830,7 +830,7 @@ class Invite(db.Model): nonce = db.Column(db.LargeBinary(12), default=None) public_key = db.Column(db.LargeBinary(300), default=None) private_key = db.Column(db.LargeBinary(300), default=None) - created_at = db.Column(db.DateTime(), nullable=False, default=dds_web.utils.current_time()) + created_at = db.Column(db.DateTime(), nullable=False, default=dds_web.utils.current_time) @property def projects(self): @@ -969,7 +969,7 @@ class Version(db.Model): # Additional columns size_stored = db.Column(db.BigInteger, unique=False, nullable=False) time_uploaded = db.Column( - db.DateTime(), unique=False, nullable=False, default=dds_web.utils.current_time() + db.DateTime(), unique=False, nullable=False, default=dds_web.utils.current_time ) time_deleted = db.Column(db.DateTime(), unique=False, nullable=True, default=None) time_invoiced = db.Column(db.DateTime(), unique=False, nullable=True, default=None) @@ -995,7 +995,7 @@ class MOTD(db.Model): # Columns id = db.Column(db.Integer, primary_key=True, autoincrement=True) message = db.Column(db.Text, nullable=False, default=None) - date_created = db.Column(db.DateTime(), nullable=False, default=dds_web.utils.current_time()) + date_created = db.Column(db.DateTime(), nullable=False, default=dds_web.utils.current_time) active = db.Column(db.Boolean, nullable=False, default=True) @@ -1027,5 +1027,5 @@ class Usage(db.Model): usage = db.Column(db.Float, nullable=False, default=None) cost = db.Column(db.Float, nullable=False, default=None) time_collected = db.Column( - db.DateTime(), unique=False, nullable=False, default=dds_web.utils.current_time() + db.DateTime(), unique=False, nullable=False, default=dds_web.utils.current_time ) From 26b14bd87f0de74341d3465f917aebeb44b292a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Tue, 13 Sep 2022 14:46:21 +0200 Subject: [PATCH 003/111] Some cleanup, add category --- .github/workflows/trivy.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 1619f7ce3..a28d8d53f 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,8 +1,4 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - +--- name: trivy on: @@ -12,8 +8,8 @@ on: jobs: scan: permissions: - contents: read # for actions/checkout to fetch code - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + contents: read + security-events: write name: Build runs-on: ubuntu-latest steps: @@ -35,3 +31,4 @@ jobs: uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results.sarif' + category: trivy From 6ce74b8ef7a443099255372a56170c273a8047de Mon Sep 17 00:00:00 2001 From: Ina Date: Tue, 13 Sep 2022 14:48:29 +0200 Subject: [PATCH 004/111] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b971a2de..15dad5958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -140,3 +140,4 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - Add storage usage information in the Units listing table for Super Admin ([#1264](https://github.com/ScilifelabDataCentre/dds_web/pull/1264)) - New endpoint for setting project as busy / not busy ([#1266](https://github.com/ScilifelabDataCentre/dds_web/pull/1266)) - Check for if project busy before status change ([#1266](https://github.com/ScilifelabDataCentre/dds_web/pull/1266)) +- Bug fix: Default timestamps fixed ([#1271](https://github.com/ScilifelabDataCentre/dds_web/pull/1271)) From 769b30c4eaf28215af3e381b82ff953f2c983760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Tue, 13 Sep 2022 15:20:26 +0200 Subject: [PATCH 005/111] Prettier wants " instead of ' --- .github/workflows/trivy.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index a28d8d53f..7de31b4d4 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -3,7 +3,7 @@ name: trivy on: schedule: - - cron: '22 22 * * *' + - cron: "22 22 * * *" jobs: scan: @@ -17,18 +17,18 @@ jobs: uses: actions/checkout@v3 - name: Ensure lowercase name - run: echo REPOSITORY_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + run: echo REPOSITORY_OWNER=$(echo ${{ github.repository_owner }} | tr "[:upper:]" "[:lower:]") >> $GITHUB_ENV - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@0.7.1 with: - image-ref: 'ghcr.io/${{ env.REPOSITORY_OWNER }}/dds-backend:latest' - format: 'sarif' - output: 'trivy-results.sarif' - severity: 'CRITICAL,HIGH' + image-ref: "ghcr.io/${{ env.REPOSITORY_OWNER }}/dds-backend:latest" + format: "sarif" + output: "trivy-results.sarif" + severity: "CRITICAL,HIGH" - name: Upload Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@v2 with: - sarif_file: 'trivy-results.sarif' + sarif_file: "trivy-results.sarif" category: trivy From 2281f4b3d5a972e7d07443a0cd8fe9836586b872 Mon Sep 17 00:00:00 2001 From: Ina Date: Tue, 13 Sep 2022 16:24:20 +0200 Subject: [PATCH 006/111] tests --- requirements.txt | 1 + tests/test_login_web.py | 2 + tests/test_user_add.py | 901 ---------------------------------------- 3 files changed, 3 insertions(+), 901 deletions(-) delete mode 100644 tests/test_user_add.py diff --git a/requirements.txt b/requirements.txt index 8525ad882..3d17aef82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,6 +30,7 @@ Flask-Migrate==3.1.0 Flask-RESTful==0.3.9 Flask-SQLAlchemy==2.5.1 Flask-WTF==1.0.0 +freezegun==1.2.2 idna==3.3 itsdangerous==2.0.1 Jinja2==3.0.3 diff --git a/tests/test_login_web.py b/tests/test_login_web.py index 459cd750f..41195ee7a 100644 --- a/tests/test_login_web.py +++ b/tests/test_login_web.py @@ -1,6 +1,8 @@ import datetime import flask from http import HTTPStatus +import werkzeug +from typing import Dict from tests import UserAuth, USER_CREDENTIALS, DDSEndpoint, DEFAULT_HEADER diff --git a/tests/test_user_add.py b/tests/test_user_add.py deleted file mode 100644 index 384f65445..000000000 --- a/tests/test_user_add.py +++ /dev/null @@ -1,901 +0,0 @@ -import dds_web -import flask_mail -import http -import json -import sqlalchemy -from dds_web import db -from dds_web.database import models -import tests -import unittest - -existing_project = "public_project_id" -existing_project_2 = "second_public_project_id" -first_new_email = {"email": "first_test_email@mailtrap.io"} -first_new_user = {**first_new_email, "role": "Researcher"} -first_new_owner = {**first_new_email, "role": "Project Owner"} -first_new_user_existing_project = {**first_new_user, "project": "public_project_id"} -first_new_user_extra_args = {**first_new_user, "extra": "test"} -first_new_user_invalid_role = {**first_new_email, "role": "Invalid Role"} -first_new_user_invalid_email = {"email": "first_invalid_email", "role": first_new_user["role"]} -existing_invite = {"email": "existing_invite_email@mailtrap.io", "role": "Researcher"} -new_unit_admin = {"email": "new_unit_admin@mailtrap.io", "role": "Unit Admin"} -new_super_admin = {"email": "new_super_admin@mailtrap.io", "role": "Super Admin"} -new_unit_user = {"email": "new_unit_user@mailtrap.io", "role": "Unit Personnel"} -existing_research_user = {"email": "researchuser2@mailtrap.io", "role": "Researcher"} -existing_research_user_owner = {"email": "researchuser2@mailtrap.io", "role": "Project Owner"} -existing_research_user_to_existing_project = { - **existing_research_user, - "project": "public_project_id", -} -existing_research_user_to_nonexistent_proj = { - **existing_research_user, - "project": "not_a_project_id", -} -change_owner_existing_user = { - "email": "researchuser@mailtrap.io", - "role": "Project Owner", - "project": "public_project_id", -} -submit_with_same_ownership = { - **existing_research_user_owner, - "project": "second_public_project_id", -} - -# Inviting Users ################################################################# Inviting Users # -def test_add_user_with_researcher(client): - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client), - json=first_new_user, - ) - assert response.status_code == http.HTTPStatus.FORBIDDEN - invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() - assert invited_user is None - - -def test_add_user_with_unituser_no_role(client): - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - json=first_new_email, - ) - assert response.status_code == http.HTTPStatus.BAD_REQUEST - invited_user = models.Invite.query.filter_by(email=first_new_email["email"]).one_or_none() - assert invited_user is None - - -def test_add_user_with_unitadmin_with_extraargs(client): - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - json=first_new_user_extra_args, - ) - assert response.status_code == http.HTTPStatus.OK - invited_user = models.Invite.query.filter_by( - email=first_new_user_extra_args["email"] - ).one_or_none() - assert invited_user - - -def test_add_user_with_unitadmin_and_invalid_role(client): - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - json=first_new_user_invalid_role, - ) - assert response.status_code == http.HTTPStatus.BAD_REQUEST - invited_user = models.Invite.query.filter_by( - email=first_new_user_invalid_role["email"] - ).one_or_none() - assert invited_user is None - - -def test_add_user_with_unitadmin_and_invalid_email(client): - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - json=first_new_user_invalid_email, - ) - assert response.status_code == http.HTTPStatus.BAD_REQUEST - # An email is always sent when receiving the partial token - mock_mail_send.assert_called_once() - - invited_user = models.Invite.query.filter_by( - email=first_new_user_invalid_email["email"] - ).one_or_none() - assert invited_user is None - - -def test_add_user_with_unitadmin(client): - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: - token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=token, - json=first_new_user, - ) - # One mail sent for partial token and one for the invite - assert mock_mail_send.call_count == 2 - - assert response.status_code == http.HTTPStatus.OK - - invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() - assert invited_user - assert invited_user.email == first_new_user["email"] - assert invited_user.role == first_new_user["role"] - - assert invited_user.nonce is not None - assert invited_user.public_key is not None - assert invited_user.private_key is not None - assert invited_user.project_invite_keys == [] - - # Repeating the invite should not send a new invite: - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=token, - json=first_new_user, - ) - # No new mail should be sent for the token and neither for an invite - assert mock_mail_send.call_count == 0 - assert response.status_code == http.HTTPStatus.BAD_REQUEST - message = response.json.get("message") - assert "user was already added to the system" in message - - -def test_add_unit_user_with_unitadmin(client): - - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: - token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=token, - json=new_unit_user, - ) - # One mail sent for partial token and one for the invite - assert mock_mail_send.call_count == 2 - - assert response.status_code == http.HTTPStatus.OK - - invited_user = models.Invite.query.filter_by(email=new_unit_user["email"]).one_or_none() - assert invited_user - assert invited_user.email == new_unit_user["email"] - assert invited_user.role == new_unit_user["role"] - - assert invited_user.nonce is not None - assert invited_user.public_key is not None - assert invited_user.private_key is not None - - project_invite_keys = invited_user.project_invite_keys - number_of_asserted_projects = 0 - for project_invite_key in project_invite_keys: - if ( - project_invite_key.project.public_id == "public_project_id" - or project_invite_key.project.public_id == "unused_project_id" - or project_invite_key.project.public_id == "restricted_project_id" - or project_invite_key.project.public_id == "second_public_project_id" - or project_invite_key.project.public_id == "file_testing_project" - ): - number_of_asserted_projects += 1 - assert len(project_invite_keys) == number_of_asserted_projects - assert len(project_invite_keys) == len(invited_user.unit.projects) - assert len(project_invite_keys) == 5 - - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=token, - json=new_unit_user, - ) - # No new mail should be sent for the token and neither for an invite - assert mock_mail_send.call_count == 0 - - assert response.status_code == http.HTTPStatus.BAD_REQUEST - message = response.json.get("message") - assert "user was already added to the system" in message - - -def test_add_user_with_superadmin(client): - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: - token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=token, - json=first_new_user, - ) - # One mail sent for partial token and one for the invite - assert mock_mail_send.call_count == 2 - - assert response.status_code == http.HTTPStatus.OK - - invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() - assert invited_user - assert invited_user.email == first_new_user["email"] - assert invited_user.role == first_new_user["role"] - - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=token, - json=first_new_user, - ) - # No new mail should be sent for the token and neither for an invite - assert mock_mail_send.call_count == 0 - - assert response.status_code == http.HTTPStatus.BAD_REQUEST - message = response.json.get("message") - assert "user was already added to the system" in message - - -def test_add_user_existing_email_no_project(client): - invited_user = models.Invite.query.filter_by( - email=existing_invite["email"], role=existing_invite["role"] - ).one_or_none() - assert invited_user - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - json=existing_invite, - ) - assert response.status_code == http.HTTPStatus.BAD_REQUEST - - -def test_add_unitadmin_user_with_unitpersonnel_permission_denied(client): - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - json=new_unit_admin, - ) - assert response.status_code == http.HTTPStatus.FORBIDDEN - - invited_user = models.Invite.query.filter_by(email=new_unit_admin["email"]).one_or_none() - assert invited_user is None - - -# Add existing users to projects ################################# Add existing users to projects # -def test_add_existing_user_without_project(client): - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - json=existing_research_user, - ) - assert response.status_code == http.HTTPStatus.BAD_REQUEST - - -def test_research_user_cannot_add_existing_user_to_existing_project(client): - user_copy = existing_research_user_to_existing_project.copy() - project_id = user_copy.pop("project") - - project = models.Project.query.filter_by(public_id=project_id).one_or_none() - user = models.Email.query.filter_by( - email=existing_research_user_to_existing_project["email"] - ).one_or_none() - project_user_before_addition = models.ProjectUsers.query.filter( - sqlalchemy.and_( - models.ProjectUsers.user_id == user.user_id, - models.ProjectUsers.project_id == project.id, - ) - ).one_or_none() - assert project_user_before_addition is None - - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client), - query_string={"project": project_id}, - json=user_copy, - ) - assert response.status_code == http.HTTPStatus.FORBIDDEN - - project_user_after_addition = models.ProjectUsers.query.filter( - sqlalchemy.and_( - models.ProjectUsers.user_id == user.user_id, - models.ProjectUsers.project_id == project.id, - ) - ).one_or_none() - assert project_user_after_addition is None - - -# projectowner adds researchuser2 to projects[0] -def test_project_owner_can_add_existing_user_to_existing_project(client): - user_copy = existing_research_user_to_existing_project.copy() - project_id = user_copy.pop("project") - - project = models.Project.query.filter_by(public_id=project_id).one_or_none() - user = models.Email.query.filter_by( - email=existing_research_user_to_existing_project["email"] - ).one_or_none() - project_user_before_addition = models.ProjectUsers.query.filter( - sqlalchemy.and_( - models.ProjectUsers.user_id == user.user_id, - models.ProjectUsers.project_id == project.id, - ) - ).one_or_none() - assert project_user_before_addition is None - - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client), - query_string={"project": project_id}, - json=user_copy, - ) - assert response.status_code == http.HTTPStatus.OK - - project_user_after_addition = models.ProjectUsers.query.filter( - sqlalchemy.and_( - models.ProjectUsers.user_id == user.user_id, - models.ProjectUsers.project_id == project.id, - ) - ).one_or_none() - assert project_user_after_addition is not None - - -def test_add_existing_user_to_existing_project(client): - user_copy = existing_research_user_to_existing_project.copy() - project_id = user_copy.pop("project") - - project = models.Project.query.filter_by(public_id=project_id).one_or_none() - user = models.Email.query.filter_by( - email=existing_research_user_to_existing_project["email"] - ).one_or_none() - project_user_before_addition = models.ProjectUsers.query.filter( - sqlalchemy.and_( - models.ProjectUsers.user_id == user.user_id, - models.ProjectUsers.project_id == project.id, - ) - ).one_or_none() - assert project_user_before_addition is None - - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project_id}, - json=user_copy, - ) - assert response.status_code == http.HTTPStatus.OK - - project_user_after_addition = models.ProjectUsers.query.filter( - sqlalchemy.and_( - models.ProjectUsers.user_id == user.user_id, - models.ProjectUsers.project_id == project.id, - ) - ).one_or_none() - assert project_user_after_addition - - -def test_add_existing_user_to_existing_project_no_mail_flag(client): - "Test that an e-mail notification is not send when the --no-mail flag is used" - - user_copy = existing_research_user_to_existing_project.copy() - project_id = user_copy.pop("project") - new_status = {"new_status": "Available"} - user_copy["send_email"] = False - token = tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client) - - # make project available prior to test, otherwise an e-mail is never sent. - response = client.post( - tests.DDSEndpoint.PROJECT_STATUS, - headers=token, - query_string={"project": project_id}, - data=json.dumps(new_status), - content_type="application/json", - ) - - # Test mail sending is suppressed - - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: - with unittest.mock.patch.object( - dds_web.api.user.AddUser, "compose_and_send_email_to_user" - ) as mock_mail_func: - print(user_copy) - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=token, - query_string={"project": project_id}, - data=json.dumps(user_copy), - content_type="application/json", - ) - # assert that no mail is being sent. - assert mock_mail_func.called == False - assert mock_mail_send.call_count == 0 - - assert response.status_code == http.HTTPStatus.OK - assert "An e-mail notification has not been sent." in response.json["message"] - - -def test_add_existing_user_to_existing_project_after_release(client): - user_copy = existing_research_user_to_existing_project.copy() - project_id = user_copy.pop("project") - - project = models.Project.query.filter_by(public_id=project_id).one_or_none() - user = models.Email.query.filter_by( - email=existing_research_user_to_existing_project["email"] - ).one_or_none() - project_user_before_addition = models.ProjectUsers.query.filter( - sqlalchemy.and_( - models.ProjectUsers.user_id == user.user_id, - models.ProjectUsers.project_id == project.id, - ) - ).one_or_none() - assert project_user_before_addition is None - - # release project - response = client.post( - tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project_id}, - json={"new_status": "Available"}, - ) - assert response.status_code == http.HTTPStatus.OK - assert project.current_status == "Available" - - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project_id}, - json=user_copy, - ) - assert response.status_code == http.HTTPStatus.OK - - project_user_after_addition = models.ProjectUsers.query.filter( - sqlalchemy.and_( - models.ProjectUsers.user_id == user.user_id, - models.ProjectUsers.project_id == project.id, - ) - ).one_or_none() - assert project_user_after_addition - - -def test_add_existing_user_to_nonexistent_proj(client): - user_copy = existing_research_user_to_nonexistent_proj.copy() - project = user_copy.pop("project") - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project}, - json=user_copy, - ) - assert response.status_code == http.HTTPStatus.BAD_REQUEST - - -def test_existing_user_change_ownership(client): - project = models.Project.query.filter_by( - public_id=change_owner_existing_user["project"] - ).one_or_none() - user = models.Email.query.filter_by(email=change_owner_existing_user["email"]).one_or_none() - project_user = models.ProjectUsers.query.filter( - sqlalchemy.and_( - models.ProjectUsers.user_id == user.user_id, - models.ProjectUsers.project_id == project.id, - ) - ).one_or_none() - - assert not project_user.owner - - user_new_owner_status = change_owner_existing_user.copy() - project = user_new_owner_status.pop("project") - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project}, - json=user_new_owner_status, - ) - - assert response.status_code == http.HTTPStatus.OK - - db.session.refresh(project_user) - - assert project_user.owner - - -def test_existing_user_change_ownership_same_permissions(client): - user_same_ownership = submit_with_same_ownership.copy() - project = user_same_ownership.pop("project") - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project}, - json=user_same_ownership, - ) - assert response.status_code == http.HTTPStatus.FORBIDDEN - - -def test_add_existing_user_with_unsuitable_role(client): - user_with_unsuitable_role = existing_research_user_to_existing_project.copy() - user_with_unsuitable_role["role"] = "Unit Admin" - project = user_with_unsuitable_role.pop("project") - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project}, - json=user_with_unsuitable_role, - ) - assert response.status_code == http.HTTPStatus.FORBIDDEN - - -# Invite to project ########################################################### Invite to project # - - -def test_invite_with_project_by_unituser(client): - "Test that a new invite including a project can be created" - - project = existing_project - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project}, - json=first_new_user, - ) - assert response.status_code == http.HTTPStatus.OK - - invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() - assert invited_user - assert invited_user.email == first_new_user["email"] - assert invited_user.role == first_new_user["role"] - - assert invited_user.nonce is not None - assert invited_user.public_key is not None - assert invited_user.private_key is not None - - project_invite_keys = invited_user.project_invite_keys - assert len(project_invite_keys) == 1 - assert project_invite_keys[0].project.public_id == project - assert not project_invite_keys[0].owner - - -def test_add_project_to_existing_invite_by_unituser(client): - "Test that a project can be associated with an existing invite" - - # Create invite upfront - - project = existing_project - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - json=first_new_user, - ) - assert response.status_code == http.HTTPStatus.OK - - invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() - - # Check that the invite has no projects yet - - assert invited_user - assert len(invited_user.project_invite_keys) == 0 - - # Add project to existing invite - - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project}, - json=first_new_user, - ) - - assert response.status_code == http.HTTPStatus.OK - - # Check that the invite has now a project association - project_invite_keys = invited_user.project_invite_keys - assert len(project_invite_keys) == 1 - assert project_invite_keys[0].project.public_id == project - assert not project_invite_keys[0].owner - - -def test_update_project_to_existing_invite_by_unituser(client): - "Test that project ownership can be updated for an existing invite" - - # Create Invite upfront - - project = existing_project - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project}, - json=first_new_user, - ) - assert response.status_code == http.HTTPStatus.OK - - project_obj = models.Project.query.filter_by(public_id=existing_project).one_or_none() - invite_obj = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() - - project_invite = models.ProjectInviteKeys.query.filter( - sqlalchemy.and_( - models.ProjectInviteKeys.invite_id == invite_obj.id, - models.ProjectUserKeys.project_id == project_obj.id, - ) - ).one_or_none() - - assert project_invite - assert not project_invite.owner - - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project}, - json=first_new_owner, - ) - - assert response.status_code == http.HTTPStatus.OK - - db.session.refresh(project_invite) - - assert project_invite.owner - - -def test_invited_as_owner_and_researcher_to_different_project(client): - "Test that an invite can be owner of one project and researcher of another" - - # Create Invite upfront as owner - - project = existing_project - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project}, - json=first_new_owner, - ) - assert response.status_code == http.HTTPStatus.OK - - # Perform second invite as researcher - project2 = existing_project_2 - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project2}, - json=first_new_user, - ) - assert response.status_code == http.HTTPStatus.OK - - project_obj_owner = models.Project.query.filter_by(public_id=existing_project).one_or_none() - project_obj_not_owner = models.Project.query.filter_by( - public_id=existing_project_2 - ).one_or_none() - - invite_obj = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() - - project_invite_owner = models.ProjectInviteKeys.query.filter( - sqlalchemy.and_( - models.ProjectInviteKeys.invite_id == invite_obj.id, - models.ProjectInviteKeys.project_id == project_obj_owner.id, - ) - ).one_or_none() - - assert project_invite_owner - assert project_invite_owner.owner - - project_invite_not_owner = models.ProjectInviteKeys.query.filter( - sqlalchemy.and_( - models.ProjectInviteKeys.invite_id == invite_obj.id, - models.ProjectInviteKeys.project_id == project_obj_not_owner.id, - ) - ).one_or_none() - - assert project_invite_not_owner - assert not project_invite_not_owner.owner - - # Owner or not should not be stored on the invite - assert invite_obj.role == "Researcher" - - -def test_invite_to_project_by_project_owner(client): - "Test that a project owner can invite to its project" - - project = existing_project - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client), - query_string={"project": project}, - json=first_new_user, - ) - assert response.status_code == http.HTTPStatus.OK - - invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() - assert invited_user - assert invited_user.email == first_new_user["email"] - assert invited_user.role == first_new_user["role"] - - assert invited_user.nonce is not None - assert invited_user.public_key is not None - assert invited_user.private_key is not None - - project_invite_keys = invited_user.project_invite_keys - assert len(project_invite_keys) == 1 - assert project_invite_keys[0].project.public_id == project - assert not project_invite_keys[0].owner - - -def test_add_anyuser_to_project_with_superadmin(client): - """Super admins cannot invite to project.""" - project = existing_project - for x in [first_new_user, first_new_owner, new_unit_user, new_unit_admin]: - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), - query_string={"project": project}, - json=x, - ) - assert response.status_code == http.HTTPStatus.BAD_REQUEST - - # An invite should not have been created - invited_user = models.Invite.query.filter_by(email=x["email"]).one_or_none() - assert not invited_user - - -def test_add_unituser_and_admin_no_unit_with_superadmin(client): - """A super admin needs to specify a unit to be able to invite unit users.""" - project = existing_project - for x in [new_unit_user, new_unit_admin]: - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), - json=x, - ) - - assert "You need to specify a unit" in response.json["message"] - assert response.status_code == http.HTTPStatus.BAD_REQUEST - - invited_user = models.Invite.query.filter_by(email=x["email"]).one_or_none() - assert not invited_user - - -def test_add_researchuser_project_no_access_unit_admin_and_personnel(client): - """A unit admin and personnel should not be able to give access to a project - which they themselves have lost access to.""" - # Make sure the project exists - project = models.Project.query.filter_by(public_id=existing_project).one_or_none() - assert project - - for inviter in ["unitadmin", "unituser", "projectowner"]: - # Check that the unit admin has access to the project first - project_user_key = models.ProjectUserKeys.query.filter_by( - user_id=inviter, project_id=project.id - ).one_or_none() - assert project_user_key - - # Remove the project access (for test) - db.session.delete(project_user_key) - - # Make sure the project access does not exist now - project_user_key = models.ProjectUserKeys.query.filter_by( - user_id=inviter, project_id=project.id - ).one_or_none() - assert not project_user_key - - for x in [first_new_user, first_new_owner]: - # Make sure there is no ongoing invite - invited_user_before = models.Invite.query.filter_by(email=x["email"]).one_or_none() - if invited_user_before: - db.session.delete(invited_user_before) - invited_user_before = models.Invite.query.filter_by(email=x["email"]).one_or_none() - assert not invited_user_before - - # Attempt invite - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS[inviter]).token(client), - json=x, - query_string={"project": project.public_id}, - ) - - # The invite should still be done, but they can't invite to a project - invited_user = models.Invite.query.filter_by(email=x["email"]).one_or_none() - assert invited_user - - # Make sure there are no project v - assert not invited_user.project_invite_keys - - # There should be an error message - assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert "The user could not be added to the project(s)" in response.json["message"] - - # Verify ok error messages - assert "errors" in response.json - assert project.public_id in response.json["errors"] - assert ( - "You do not have access to the specified project." - in response.json["errors"][project.public_id] - ) - - -# Invite without email -def test_invite_without_email(client): - """The email is required.""" - user_no_email = first_new_user.copy() - user_no_email.pop("email") - - for inviter in ["superadmin", "unitadmin", "unituser"]: - # Attempt invite - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS[inviter]).token(client), - json=user_no_email, - # query_string={"project": existing_project}, - ) - - # There should be an error message - assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert "Email address required to add or invite." in response.json["message"] - - -# Invite super admin with unit admin -def test_invite_superadmin_as_unitadmin(client): - """A unit admin cannt invie a superadmin""" - # Attempt invite - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - json=new_super_admin, - ) - - assert response.status_code == http.HTTPStatus.FORBIDDEN - assert "You do not have the necessary permissions." in response.json["message"] - - -# Invite super admin and unit admin with unit personnel -def test_invite_superadmin_and_unitadmin_as_unitpersonnel(client): - """A unit personnel cannot invite a superadmin or unit admin""" - for invitee in [new_super_admin, new_unit_admin]: - # Attempt invite - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - json=invitee, - ) - - assert response.status_code == http.HTTPStatus.FORBIDDEN - assert "You do not have the necessary permissions." in response.json["message"] - - -# Invite super admin, unit admin or unit personnel -def test_invite_superadmin_and_unitadmin_and_unitpersonnel_as_projectowner(client): - """A project owner cannot invite a superadmin or unit admin or unit personnel.""" - for invitee in [new_super_admin, new_unit_admin, new_unit_user]: - # Attempt invite - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client), - json=invitee, - query_string={"project": existing_project}, - ) - - assert response.status_code == http.HTTPStatus.FORBIDDEN - assert "You do not have the necessary permissions." in response.json["message"] - - -def test_invite_unituser_as_superadmin_incorrect_unit(client): - """A valid unit is required for super admins to invite unit users.""" - for invitee in [new_unit_admin, new_unit_user]: - invite_with_invalid_unit = invitee.copy() - invite_with_invalid_unit["unit"] = "invalidunit" - - # Attempt invite - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), - json=invite_with_invalid_unit, - ) - - assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert "Invalid unit publid id." in response.json["message"] - - -def test_invite_unituser_with_valid_unit_as_superadmin(client): - """A unit user should be invited if the super admin provides a valid unit.""" - for invitee in [new_unit_admin, new_unit_user]: - valid_unit = models.Unit.query.filter_by(name="Unit 1").one_or_none() - assert valid_unit - - invite_with_valid_unit = invitee.copy() - invite_with_valid_unit["unit"] = valid_unit.public_id - - # Attempt invite - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), - json=invite_with_valid_unit, - ) - - new_invite = models.Invite.query.filter_by( - email=invite_with_valid_unit["email"] - ).one_or_none() - assert new_invite From 6c955f1b88a5cbbababa527d4ff4f9da7e65b434 Mon Sep 17 00:00:00 2001 From: Ina Date: Tue, 13 Sep 2022 16:24:45 +0200 Subject: [PATCH 007/111] freezegun added to requirements --- tests/api/test_user.py | 1022 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1022 insertions(+) create mode 100644 tests/api/test_user.py diff --git a/tests/api/test_user.py b/tests/api/test_user.py new file mode 100644 index 000000000..5759b7222 --- /dev/null +++ b/tests/api/test_user.py @@ -0,0 +1,1022 @@ +from datetime import datetime +from datetime import timedelta +from tracemalloc import start +import typing +from unittest import mock +import dds_web +import flask_mail +import http +import json +import sqlalchemy +from dds_web import db +from dds_web.database import models +from dds_web.utils import current_time +import tests +import unittest +import werkzeug +import time + +existing_project = "public_project_id" +existing_project_2 = "second_public_project_id" +first_new_email = {"email": "first_test_email@mailtrap.io"} +first_new_user = {**first_new_email, "role": "Researcher"} +first_new_owner = {**first_new_email, "role": "Project Owner"} +first_new_user_existing_project = {**first_new_user, "project": "public_project_id"} +first_new_user_extra_args = {**first_new_user, "extra": "test"} +first_new_user_invalid_role = {**first_new_email, "role": "Invalid Role"} +first_new_user_invalid_email = {"email": "first_invalid_email", "role": first_new_user["role"]} +existing_invite = {"email": "existing_invite_email@mailtrap.io", "role": "Researcher"} +new_unit_admin = {"email": "new_unit_admin@mailtrap.io", "role": "Unit Admin"} +new_super_admin = {"email": "new_super_admin@mailtrap.io", "role": "Super Admin"} +new_unit_user = {"email": "new_unit_user@mailtrap.io", "role": "Unit Personnel"} +existing_research_user = {"email": "researchuser2@mailtrap.io", "role": "Researcher"} +existing_research_user_owner = {"email": "researchuser2@mailtrap.io", "role": "Project Owner"} +existing_research_user_to_existing_project = { + **existing_research_user, + "project": "public_project_id", +} +existing_research_user_to_nonexistent_proj = { + **existing_research_user, + "project": "not_a_project_id", +} +change_owner_existing_user = { + "email": "researchuser@mailtrap.io", + "role": "Project Owner", + "project": "public_project_id", +} +submit_with_same_ownership = { + **existing_research_user_owner, + "project": "second_public_project_id", +} + +# AddUser ################################################################# AddUser # + + +def test_add_user_with_researcher(client): + """Researchers cannot invite other users.""" + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client), + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.FORBIDDEN + invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() + assert invited_user is None + + +def test_add_user_with_unituser_no_role(client): + """An ok invite requires a role to be specified.""" + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + json=first_new_email, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + invited_user = models.Invite.query.filter_by(email=first_new_email["email"]).one_or_none() + assert invited_user is None + + +def test_add_user_with_unitadmin_with_extraargs(client): + """Extra args should not be noticed when inviting users.""" + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + json=first_new_user_extra_args, + ) + assert response.status_code == http.HTTPStatus.OK + invited_user = models.Invite.query.filter_by( + email=first_new_user_extra_args["email"] + ).one_or_none() + assert invited_user + + +def test_add_user_with_unitadmin_and_invalid_role(client): + """An invalid role should result in a failed invite.""" + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + json=first_new_user_invalid_role, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + invited_user = models.Invite.query.filter_by( + email=first_new_user_invalid_role["email"] + ).one_or_none() + assert invited_user is None + + +def test_add_user_with_unitadmin_and_invalid_email(client): + """An invalid email should not be accepted.""" + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + json=first_new_user_invalid_email, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + # An email is always sent when receiving the partial token + mock_mail_send.assert_called_once() + + invited_user = models.Invite.query.filter_by( + email=first_new_user_invalid_email["email"] + ).one_or_none() + assert invited_user is None + + +def test_add_user_with_unitadmin(client): + """Add researcher as unit admin.""" + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=token, + json=first_new_user, + ) + # One mail sent for partial token and one for the invite + assert mock_mail_send.call_count == 2 + + assert response.status_code == http.HTTPStatus.OK + + invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() + assert invited_user + assert invited_user.email == first_new_user["email"] + assert invited_user.role == first_new_user["role"] + + assert invited_user.nonce is not None + assert invited_user.public_key is not None + assert invited_user.private_key is not None + assert invited_user.project_invite_keys == [] + + # Repeating the invite should not send a new invite: + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=token, + json=first_new_user, + ) + # No new mail should be sent for the token and neither for an invite + assert mock_mail_send.call_count == 0 + assert response.status_code == http.HTTPStatus.BAD_REQUEST + message = response.json.get("message") + assert "user was already added to the system" in message + + +def test_add_unit_user_with_unitadmin(client): + """Add unit user as unit admin.""" + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=token, + json=new_unit_user, + ) + # One mail sent for partial token and one for the invite + assert mock_mail_send.call_count == 2 + + assert response.status_code == http.HTTPStatus.OK + + invited_user = models.Invite.query.filter_by(email=new_unit_user["email"]).one_or_none() + assert invited_user + assert invited_user.email == new_unit_user["email"] + assert invited_user.role == new_unit_user["role"] + + assert invited_user.nonce is not None + assert invited_user.public_key is not None + assert invited_user.private_key is not None + + project_invite_keys = invited_user.project_invite_keys + number_of_asserted_projects = 0 + for project_invite_key in project_invite_keys: + if ( + project_invite_key.project.public_id == "public_project_id" + or project_invite_key.project.public_id == "unused_project_id" + or project_invite_key.project.public_id == "restricted_project_id" + or project_invite_key.project.public_id == "second_public_project_id" + or project_invite_key.project.public_id == "file_testing_project" + ): + number_of_asserted_projects += 1 + assert len(project_invite_keys) == number_of_asserted_projects + assert len(project_invite_keys) == len(invited_user.unit.projects) + assert len(project_invite_keys) == 5 + + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=token, + json=new_unit_user, + ) + # No new mail should be sent for the token and neither for an invite + assert mock_mail_send.call_count == 0 + + assert response.status_code == http.HTTPStatus.BAD_REQUEST + message = response.json.get("message") + assert "user was already added to the system" in message + + +def test_add_user_with_superadmin(client): + """Adding users as super admin should work.""" + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=token, + json=first_new_user, + ) + # One mail sent for partial token and one for the invite + assert mock_mail_send.call_count == 2 + + assert response.status_code == http.HTTPStatus.OK + + invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() + assert invited_user + assert invited_user.email == first_new_user["email"] + assert invited_user.role == first_new_user["role"] + + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=token, + json=first_new_user, + ) + # No new mail should be sent for the token and neither for an invite + assert mock_mail_send.call_count == 0 + + assert response.status_code == http.HTTPStatus.BAD_REQUEST + message = response.json.get("message") + assert "user was already added to the system" in message + + +def test_add_user_existing_email_no_project(client): + """Granting an existing user access to a project requires a project id.""" + invited_user = models.Invite.query.filter_by( + email=existing_invite["email"], role=existing_invite["role"] + ).one_or_none() + assert invited_user + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + json=existing_invite, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + + +def test_add_unitadmin_user_with_unitpersonnel_permission_denied(client): + """Unit admins cannot be invited as unit personnel.""" + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + json=new_unit_admin, + ) + assert response.status_code == http.HTTPStatus.FORBIDDEN + + invited_user = models.Invite.query.filter_by(email=new_unit_admin["email"]).one_or_none() + assert invited_user is None + + +# -- Add existing users to projects ################################# Add existing users to projects # +def test_add_existing_user_without_project(client): + """Project required if inviting user to project.""" + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + json=existing_research_user, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + + +def test_research_user_cannot_add_existing_user_to_existing_project(client): + """Research user cannot add other users to project.""" + user_copy = existing_research_user_to_existing_project.copy() + project_id = user_copy.pop("project") + + project = models.Project.query.filter_by(public_id=project_id).one_or_none() + user = models.Email.query.filter_by( + email=existing_research_user_to_existing_project["email"] + ).one_or_none() + project_user_before_addition = models.ProjectUsers.query.filter( + sqlalchemy.and_( + models.ProjectUsers.user_id == user.user_id, + models.ProjectUsers.project_id == project.id, + ) + ).one_or_none() + assert project_user_before_addition is None + + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client), + query_string={"project": project_id}, + json=user_copy, + ) + assert response.status_code == http.HTTPStatus.FORBIDDEN + + project_user_after_addition = models.ProjectUsers.query.filter( + sqlalchemy.and_( + models.ProjectUsers.user_id == user.user_id, + models.ProjectUsers.project_id == project.id, + ) + ).one_or_none() + assert project_user_after_addition is None + + +# projectowner adds researchuser2 to projects[0] +def test_project_owner_can_add_existing_user_to_existing_project(client): + """Project owners can add users to existing projects.""" + user_copy = existing_research_user_to_existing_project.copy() + project_id = user_copy.pop("project") + + project = models.Project.query.filter_by(public_id=project_id).one_or_none() + user = models.Email.query.filter_by( + email=existing_research_user_to_existing_project["email"] + ).one_or_none() + project_user_before_addition = models.ProjectUsers.query.filter( + sqlalchemy.and_( + models.ProjectUsers.user_id == user.user_id, + models.ProjectUsers.project_id == project.id, + ) + ).one_or_none() + assert project_user_before_addition is None + + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client), + query_string={"project": project_id}, + json=user_copy, + ) + assert response.status_code == http.HTTPStatus.OK + + project_user_after_addition = models.ProjectUsers.query.filter( + sqlalchemy.and_( + models.ProjectUsers.user_id == user.user_id, + models.ProjectUsers.project_id == project.id, + ) + ).one_or_none() + assert project_user_after_addition is not None + + +def test_add_existing_user_to_existing_project(client): + """Unit user can invite users to project.""" + user_copy = existing_research_user_to_existing_project.copy() + project_id = user_copy.pop("project") + + project = models.Project.query.filter_by(public_id=project_id).one_or_none() + user = models.Email.query.filter_by( + email=existing_research_user_to_existing_project["email"] + ).one_or_none() + project_user_before_addition = models.ProjectUsers.query.filter( + sqlalchemy.and_( + models.ProjectUsers.user_id == user.user_id, + models.ProjectUsers.project_id == project.id, + ) + ).one_or_none() + assert project_user_before_addition is None + + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project_id}, + json=user_copy, + ) + assert response.status_code == http.HTTPStatus.OK + + project_user_after_addition = models.ProjectUsers.query.filter( + sqlalchemy.and_( + models.ProjectUsers.user_id == user.user_id, + models.ProjectUsers.project_id == project.id, + ) + ).one_or_none() + assert project_user_after_addition + + +def test_add_existing_user_to_existing_project_no_mail_flag(client): + "Test that an e-mail notification is not send when the --no-mail flag is used" + + user_copy = existing_research_user_to_existing_project.copy() + project_id = user_copy.pop("project") + new_status = {"new_status": "Available"} + user_copy["send_email"] = False + token = tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client) + + # make project available prior to test, otherwise an e-mail is never sent. + response = client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=token, + query_string={"project": project_id}, + data=json.dumps(new_status), + content_type="application/json", + ) + + # Test mail sending is suppressed + + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + with unittest.mock.patch.object( + dds_web.api.user.AddUser, "compose_and_send_email_to_user" + ) as mock_mail_func: + print(user_copy) + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=token, + query_string={"project": project_id}, + data=json.dumps(user_copy), + content_type="application/json", + ) + # assert that no mail is being sent. + assert mock_mail_func.called == False + assert mock_mail_send.call_count == 0 + + assert response.status_code == http.HTTPStatus.OK + assert "An e-mail notification has not been sent." in response.json["message"] + + +def test_add_existing_user_to_existing_project_after_release(client): + """User should be able to be added after project status release.""" + user_copy = existing_research_user_to_existing_project.copy() + project_id = user_copy.pop("project") + + project = models.Project.query.filter_by(public_id=project_id).one_or_none() + user = models.Email.query.filter_by( + email=existing_research_user_to_existing_project["email"] + ).one_or_none() + project_user_before_addition = models.ProjectUsers.query.filter( + sqlalchemy.and_( + models.ProjectUsers.user_id == user.user_id, + models.ProjectUsers.project_id == project.id, + ) + ).one_or_none() + assert project_user_before_addition is None + + # release project + response = client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project_id}, + json={"new_status": "Available"}, + ) + assert response.status_code == http.HTTPStatus.OK + assert project.current_status == "Available" + + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project_id}, + json=user_copy, + ) + assert response.status_code == http.HTTPStatus.OK + + project_user_after_addition = models.ProjectUsers.query.filter( + sqlalchemy.and_( + models.ProjectUsers.user_id == user.user_id, + models.ProjectUsers.project_id == project.id, + ) + ).one_or_none() + assert project_user_after_addition + + +def test_add_existing_user_to_nonexistent_proj(client): + """Adding user to non existent project should fail.""" + user_copy = existing_research_user_to_nonexistent_proj.copy() + project = user_copy.pop("project") + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project}, + json=user_copy, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + + +def test_existing_user_change_ownership(client): + """Change user role in project to project owner.""" + project = models.Project.query.filter_by( + public_id=change_owner_existing_user["project"] + ).one_or_none() + user = models.Email.query.filter_by(email=change_owner_existing_user["email"]).one_or_none() + project_user = models.ProjectUsers.query.filter( + sqlalchemy.and_( + models.ProjectUsers.user_id == user.user_id, + models.ProjectUsers.project_id == project.id, + ) + ).one_or_none() + + assert not project_user.owner + + user_new_owner_status = change_owner_existing_user.copy() + project = user_new_owner_status.pop("project") + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project}, + json=user_new_owner_status, + ) + + assert response.status_code == http.HTTPStatus.OK + + db.session.refresh(project_user) + + assert project_user.owner + + +def test_existing_user_change_ownership_same_permissions(client): + """Try to change role in project to same role.""" + user_same_ownership = submit_with_same_ownership.copy() + project = user_same_ownership.pop("project") + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project}, + json=user_same_ownership, + ) + assert response.status_code == http.HTTPStatus.FORBIDDEN + + +def test_add_existing_user_with_unsuitable_role(client): + """Cannot add unit admins to projects. They have access to all.""" + user_with_unsuitable_role = existing_research_user_to_existing_project.copy() + user_with_unsuitable_role["role"] = "Unit Admin" + project = user_with_unsuitable_role.pop("project") + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project}, + json=user_with_unsuitable_role, + ) + assert response.status_code == http.HTTPStatus.FORBIDDEN + + +# Invite to project ########################################################### Invite to project # + + +def test_invite_with_project_by_unituser(client): + "Test that a new invite including a project can be created" + project = existing_project + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project}, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.OK + + invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() + assert invited_user + assert invited_user.email == first_new_user["email"] + assert invited_user.role == first_new_user["role"] + + assert invited_user.nonce is not None + assert invited_user.public_key is not None + assert invited_user.private_key is not None + + project_invite_keys = invited_user.project_invite_keys + assert len(project_invite_keys) == 1 + assert project_invite_keys[0].project.public_id == project + assert not project_invite_keys[0].owner + + +def test_add_project_to_existing_invite_by_unituser(client): + "Test that a project can be associated with an existing invite" + + # Create invite upfront + + project = existing_project + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.OK + + invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() + + # Check that the invite has no projects yet + + assert invited_user + assert len(invited_user.project_invite_keys) == 0 + + # Add project to existing invite + + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project}, + json=first_new_user, + ) + + assert response.status_code == http.HTTPStatus.OK + + # Check that the invite has now a project association + project_invite_keys = invited_user.project_invite_keys + assert len(project_invite_keys) == 1 + assert project_invite_keys[0].project.public_id == project + assert not project_invite_keys[0].owner + + +def test_update_project_to_existing_invite_by_unituser(client): + "Test that project ownership can be updated for an existing invite" + + # Create Invite upfront + + project = existing_project + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project}, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.OK + + project_obj = models.Project.query.filter_by(public_id=existing_project).one_or_none() + invite_obj = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() + + project_invite = models.ProjectInviteKeys.query.filter( + sqlalchemy.and_( + models.ProjectInviteKeys.invite_id == invite_obj.id, + models.ProjectUserKeys.project_id == project_obj.id, + ) + ).one_or_none() + + assert project_invite + assert not project_invite.owner + + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project}, + json=first_new_owner, + ) + + assert response.status_code == http.HTTPStatus.OK + + db.session.refresh(project_invite) + + assert project_invite.owner + + +def test_invited_as_owner_and_researcher_to_different_project(client): + "Test that an invite can be owner of one project and researcher of another" + + # Create Invite upfront as owner + + project = existing_project + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project}, + json=first_new_owner, + ) + assert response.status_code == http.HTTPStatus.OK + + # Perform second invite as researcher + project2 = existing_project_2 + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project2}, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.OK + + project_obj_owner = models.Project.query.filter_by(public_id=existing_project).one_or_none() + project_obj_not_owner = models.Project.query.filter_by( + public_id=existing_project_2 + ).one_or_none() + + invite_obj = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() + + project_invite_owner = models.ProjectInviteKeys.query.filter( + sqlalchemy.and_( + models.ProjectInviteKeys.invite_id == invite_obj.id, + models.ProjectInviteKeys.project_id == project_obj_owner.id, + ) + ).one_or_none() + + assert project_invite_owner + assert project_invite_owner.owner + + project_invite_not_owner = models.ProjectInviteKeys.query.filter( + sqlalchemy.and_( + models.ProjectInviteKeys.invite_id == invite_obj.id, + models.ProjectInviteKeys.project_id == project_obj_not_owner.id, + ) + ).one_or_none() + + assert project_invite_not_owner + assert not project_invite_not_owner.owner + + # Owner or not should not be stored on the invite + assert invite_obj.role == "Researcher" + + +def test_invite_to_project_by_project_owner(client): + "Test that a project owner can invite to its project" + + project = existing_project + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client), + query_string={"project": project}, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.OK + + invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() + assert invited_user + assert invited_user.email == first_new_user["email"] + assert invited_user.role == first_new_user["role"] + + assert invited_user.nonce is not None + assert invited_user.public_key is not None + assert invited_user.private_key is not None + + project_invite_keys = invited_user.project_invite_keys + assert len(project_invite_keys) == 1 + assert project_invite_keys[0].project.public_id == project + assert not project_invite_keys[0].owner + + +def test_add_anyuser_to_project_with_superadmin(client): + """Super admins cannot invite to project.""" + project = existing_project + for x in [first_new_user, first_new_owner, new_unit_user, new_unit_admin]: + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), + query_string={"project": project}, + json=x, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + + # An invite should not have been created + invited_user = models.Invite.query.filter_by(email=x["email"]).one_or_none() + assert not invited_user + + +def test_add_unituser_and_admin_no_unit_with_superadmin(client): + """A super admin needs to specify a unit to be able to invite unit users.""" + project = existing_project + for x in [new_unit_user, new_unit_admin]: + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), + json=x, + ) + + assert "You need to specify a unit" in response.json["message"] + assert response.status_code == http.HTTPStatus.BAD_REQUEST + + invited_user = models.Invite.query.filter_by(email=x["email"]).one_or_none() + assert not invited_user + + +def test_add_researchuser_project_no_access_unit_admin_and_personnel(client): + """A unit admin and personnel should not be able to give access to a project + which they themselves have lost access to.""" + # Make sure the project exists + project = models.Project.query.filter_by(public_id=existing_project).one_or_none() + assert project + + for inviter in ["unitadmin", "unituser", "projectowner"]: + # Check that the unit admin has access to the project first + project_user_key = models.ProjectUserKeys.query.filter_by( + user_id=inviter, project_id=project.id + ).one_or_none() + assert project_user_key + + # Remove the project access (for test) + db.session.delete(project_user_key) + + # Make sure the project access does not exist now + project_user_key = models.ProjectUserKeys.query.filter_by( + user_id=inviter, project_id=project.id + ).one_or_none() + assert not project_user_key + + for x in [first_new_user, first_new_owner]: + # Make sure there is no ongoing invite + invited_user_before = models.Invite.query.filter_by(email=x["email"]).one_or_none() + if invited_user_before: + db.session.delete(invited_user_before) + invited_user_before = models.Invite.query.filter_by(email=x["email"]).one_or_none() + assert not invited_user_before + + # Attempt invite + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS[inviter]).token(client), + json=x, + query_string={"project": project.public_id}, + ) + + # The invite should still be done, but they can't invite to a project + invited_user = models.Invite.query.filter_by(email=x["email"]).one_or_none() + assert invited_user + + # Make sure there are no project v + assert not invited_user.project_invite_keys + + # There should be an error message + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "The user could not be added to the project(s)" in response.json["message"] + + # Verify ok error messages + assert "errors" in response.json + assert project.public_id in response.json["errors"] + assert ( + "You do not have access to the specified project." + in response.json["errors"][project.public_id] + ) + + +# Invite without email +def test_invite_without_email(client): + """The email is required.""" + user_no_email = first_new_user.copy() + user_no_email.pop("email") + + for inviter in ["superadmin", "unitadmin", "unituser"]: + # Attempt invite + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS[inviter]).token(client), + json=user_no_email, + # query_string={"project": existing_project}, + ) + + # There should be an error message + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Email address required to add or invite." in response.json["message"] + + +# Invite super admin with unit admin +def test_invite_superadmin_as_unitadmin(client): + """A unit admin cannt invie a superadmin""" + # Attempt invite + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + json=new_super_admin, + ) + + assert response.status_code == http.HTTPStatus.FORBIDDEN + assert "You do not have the necessary permissions." in response.json["message"] + + +# Invite super admin and unit admin with unit personnel +def test_invite_superadmin_and_unitadmin_as_unitpersonnel(client): + """A unit personnel cannot invite a superadmin or unit admin""" + for invitee in [new_super_admin, new_unit_admin]: + # Attempt invite + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + json=invitee, + ) + + assert response.status_code == http.HTTPStatus.FORBIDDEN + assert "You do not have the necessary permissions." in response.json["message"] + + +# Invite super admin, unit admin or unit personnel +def test_invite_superadmin_and_unitadmin_and_unitpersonnel_as_projectowner(client): + """A project owner cannot invite a superadmin or unit admin or unit personnel.""" + for invitee in [new_super_admin, new_unit_admin, new_unit_user]: + # Attempt invite + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client), + json=invitee, + query_string={"project": existing_project}, + ) + + assert response.status_code == http.HTTPStatus.FORBIDDEN + assert "You do not have the necessary permissions." in response.json["message"] + + +def test_invite_unituser_as_superadmin_incorrect_unit(client): + """A valid unit is required for super admins to invite unit users.""" + for invitee in [new_unit_admin, new_unit_user]: + invite_with_invalid_unit = invitee.copy() + invite_with_invalid_unit["unit"] = "invalidunit" + + # Attempt invite + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), + json=invite_with_invalid_unit, + ) + + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Invalid unit publid id." in response.json["message"] + + +def test_invite_unituser_with_valid_unit_as_superadmin(client): + """A unit user should be invited if the super admin provides a valid unit.""" + for invitee in [new_unit_admin, new_unit_user]: + valid_unit = models.Unit.query.filter_by(name="Unit 1").one_or_none() + assert valid_unit + + invite_with_valid_unit = invitee.copy() + invite_with_valid_unit["unit"] = valid_unit.public_id + + # Attempt invite + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), + json=invite_with_valid_unit, + ) + + new_invite = models.Invite.query.filter_by( + email=invite_with_valid_unit["email"] + ).one_or_none() + assert new_invite + + +# -- timestamp + + +def test_invite_users_should_have_different_timestamps(client): + """Invites should not get the same timestamps in the database.""" + # Current time + real_time = current_time() + + # Set initial time + new_time_initial = datetime(year=2022, month=9, day=12, hour=15, minute=49, second=10) + assert real_time != new_time_initial + + # Use freezegun + import freezegun + with freezegun.freeze_time(new_time_initial): + start_time = current_time() + assert start_time == new_time_initial + + # Invite researcher + researcher_info = {"role": "Researcher", "email": "newresearcher@test.com"} + new_time_1 = new_time_initial + timedelta(days=1) + with freezegun.freeze_time(new_time_1): + response: werkzeug.test.WrapperTestResponse = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), + json=researcher_info + ) + assert response.status_code == http.HTTPStatus.OK + + # Check invite created time + researcher_invite: models.Invite = models.Invite.query.filter_by(email=researcher_info["email"], role=researcher_info["role"]).one_or_none() + assert researcher_invite + assert new_time_initial != researcher_invite.created_at == new_time_1 + + # Invite Unit Personnel + unit: models.Unit = models.Unit.query.first() + assert unit + + unitpersonnel_info = {"role": "Unit Personnel", "email": "newunitpersonnel@test.com", "unit": unit.public_id} + new_time_2 = new_time_1 + timedelta(days=1) + with freezegun.freeze_time(new_time_2): + response: werkzeug.test.WrapperTestResponse = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), + json=unitpersonnel_info + ) + assert response.status_code == http.HTTPStatus.OK + + # Check invite created time + unitpersonnel_invite: models.Invite = models.Invite.query.filter_by(email=unitpersonnel_info["email"], role=unitpersonnel_info["role"]).one_or_none() + assert unitpersonnel_invite + assert unitpersonnel_invite.created_at == new_time_2 and unitpersonnel_invite.created_at not in [new_time_initial, new_time_1] + + # Invite Unit Admin + unit: models.Unit = models.Unit.query.first() + assert unit + + unitadmin_info = {"role": "Unit Admin", "email": "newunitadmin@test.com", "unit": unit.public_id} + new_time_3 = new_time_2 + timedelta(days=1, hours=3) + with freezegun.freeze_time(new_time_3): + response: werkzeug.test.WrapperTestResponse = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), + json=unitadmin_info + ) + assert response.status_code == http.HTTPStatus.OK + + # Check invite created time + unitadmin_invite: models.Invite = models.Invite.query.filter_by(email=unitadmin_info["email"], role=unitadmin_info["role"]).one_or_none() + assert unitadmin_invite + assert unitadmin_invite.created_at == new_time_3 and unitadmin_invite.created_at not in [new_time_initial, new_time_1, new_time_2] + + # Invite Super Admin + superadmin_info = {"role": "Super Admin", "email": "newsuperadmin@test.com"} + new_time_4 = new_time_3 + timedelta(days=1, hours=6) + with freezegun.freeze_time(new_time_4): + response: werkzeug.test.WrapperTestResponse = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), + json=superadmin_info + ) + assert response.status_code == http.HTTPStatus.OK + + # Check invite created time + superadmin_invite: models.Invite = models.Invite.query.filter_by(email=superadmin_info["email"], role=superadmin_info["role"]).one_or_none() + assert superadmin_invite + assert superadmin_invite.created_at == new_time_4 and superadmin_invite.created_at not in [new_time_initial, new_time_1, new_time_2, new_time_3] + + + + + From 073bffe119079b722cbaa2c31445dec8d14a6bee Mon Sep 17 00:00:00 2001 From: Ina Date: Tue, 13 Sep 2022 16:25:09 +0200 Subject: [PATCH 008/111] black --- tests/api/test_user.py | 70 +++++++++++++++++++++++++++-------------- tests/test_login_web.py | 2 +- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index 5759b7222..420dfe683 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -942,10 +942,11 @@ def test_invite_users_should_have_different_timestamps(client): # Use freezegun import freezegun + with freezegun.freeze_time(new_time_initial): start_time = current_time() assert start_time == new_time_initial - + # Invite researcher researcher_info = {"role": "Researcher", "email": "newresearcher@test.com"} new_time_1 = new_time_initial + timedelta(days=1) @@ -953,12 +954,14 @@ def test_invite_users_should_have_different_timestamps(client): response: werkzeug.test.WrapperTestResponse = client.post( tests.DDSEndpoint.USER_ADD, headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), - json=researcher_info + json=researcher_info, ) assert response.status_code == http.HTTPStatus.OK - + # Check invite created time - researcher_invite: models.Invite = models.Invite.query.filter_by(email=researcher_info["email"], role=researcher_info["role"]).one_or_none() + researcher_invite: models.Invite = models.Invite.query.filter_by( + email=researcher_info["email"], role=researcher_info["role"] + ).one_or_none() assert researcher_invite assert new_time_initial != researcher_invite.created_at == new_time_1 @@ -966,39 +969,58 @@ def test_invite_users_should_have_different_timestamps(client): unit: models.Unit = models.Unit.query.first() assert unit - unitpersonnel_info = {"role": "Unit Personnel", "email": "newunitpersonnel@test.com", "unit": unit.public_id} + unitpersonnel_info = { + "role": "Unit Personnel", + "email": "newunitpersonnel@test.com", + "unit": unit.public_id, + } new_time_2 = new_time_1 + timedelta(days=1) with freezegun.freeze_time(new_time_2): response: werkzeug.test.WrapperTestResponse = client.post( tests.DDSEndpoint.USER_ADD, headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), - json=unitpersonnel_info + json=unitpersonnel_info, ) assert response.status_code == http.HTTPStatus.OK - + # Check invite created time - unitpersonnel_invite: models.Invite = models.Invite.query.filter_by(email=unitpersonnel_info["email"], role=unitpersonnel_info["role"]).one_or_none() + unitpersonnel_invite: models.Invite = models.Invite.query.filter_by( + email=unitpersonnel_info["email"], role=unitpersonnel_info["role"] + ).one_or_none() assert unitpersonnel_invite - assert unitpersonnel_invite.created_at == new_time_2 and unitpersonnel_invite.created_at not in [new_time_initial, new_time_1] + assert ( + unitpersonnel_invite.created_at == new_time_2 + and unitpersonnel_invite.created_at not in [new_time_initial, new_time_1] + ) # Invite Unit Admin unit: models.Unit = models.Unit.query.first() assert unit - unitadmin_info = {"role": "Unit Admin", "email": "newunitadmin@test.com", "unit": unit.public_id} + unitadmin_info = { + "role": "Unit Admin", + "email": "newunitadmin@test.com", + "unit": unit.public_id, + } new_time_3 = new_time_2 + timedelta(days=1, hours=3) with freezegun.freeze_time(new_time_3): response: werkzeug.test.WrapperTestResponse = client.post( tests.DDSEndpoint.USER_ADD, headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), - json=unitadmin_info + json=unitadmin_info, ) assert response.status_code == http.HTTPStatus.OK - + # Check invite created time - unitadmin_invite: models.Invite = models.Invite.query.filter_by(email=unitadmin_info["email"], role=unitadmin_info["role"]).one_or_none() + unitadmin_invite: models.Invite = models.Invite.query.filter_by( + email=unitadmin_info["email"], role=unitadmin_info["role"] + ).one_or_none() assert unitadmin_invite - assert unitadmin_invite.created_at == new_time_3 and unitadmin_invite.created_at not in [new_time_initial, new_time_1, new_time_2] + assert unitadmin_invite.created_at == new_time_3 and unitadmin_invite.created_at not in [ + new_time_initial, + new_time_1, + new_time_2, + ] # Invite Super Admin superadmin_info = {"role": "Super Admin", "email": "newsuperadmin@test.com"} @@ -1007,16 +1029,18 @@ def test_invite_users_should_have_different_timestamps(client): response: werkzeug.test.WrapperTestResponse = client.post( tests.DDSEndpoint.USER_ADD, headers=tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client), - json=superadmin_info + json=superadmin_info, ) assert response.status_code == http.HTTPStatus.OK - + # Check invite created time - superadmin_invite: models.Invite = models.Invite.query.filter_by(email=superadmin_info["email"], role=superadmin_info["role"]).one_or_none() + superadmin_invite: models.Invite = models.Invite.query.filter_by( + email=superadmin_info["email"], role=superadmin_info["role"] + ).one_or_none() assert superadmin_invite - assert superadmin_invite.created_at == new_time_4 and superadmin_invite.created_at not in [new_time_initial, new_time_1, new_time_2, new_time_3] - - - - - + assert superadmin_invite.created_at == new_time_4 and superadmin_invite.created_at not in [ + new_time_initial, + new_time_1, + new_time_2, + new_time_3, + ] diff --git a/tests/test_login_web.py b/tests/test_login_web.py index 41195ee7a..5da4ab349 100644 --- a/tests/test_login_web.py +++ b/tests/test_login_web.py @@ -2,7 +2,7 @@ import flask from http import HTTPStatus import werkzeug -from typing import Dict +from typing import Dict from tests import UserAuth, USER_CREDENTIALS, DDSEndpoint, DEFAULT_HEADER From d44f882c7f5a8ebe26c9992ad2d8a543e6d0cc20 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 14 Sep 2022 08:39:43 +0200 Subject: [PATCH 009/111] bump werkzeug to 2.0.3 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8525ad882..2c8528a81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -62,6 +62,6 @@ tzdata==2021.5 tzlocal==4.1 urllib3==1.26.8 visitor==0.1.3 -Werkzeug==2.0.2 +Werkzeug==2.0.3 wrapt==1.13.3 WTForms==3.0.1 From c0abca468600d2c64ce16b88585b998a5c3be2dc Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 14 Sep 2022 11:35:38 +0200 Subject: [PATCH 010/111] change to alpine --- Dockerfiles/backend.Dockerfile | 6 ++++-- Dockerfiles/cli.Dockerfile | 6 ++++-- requirements.txt | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Dockerfiles/backend.Dockerfile b/Dockerfiles/backend.Dockerfile index 999a1c321..8ae8e4e1c 100644 --- a/Dockerfiles/backend.Dockerfile +++ b/Dockerfiles/backend.Dockerfile @@ -3,10 +3,12 @@ ############################# # Set official image -- parent image -FROM python:latest as base +FROM python:3.10-alpine as base # Install some necessary systems packages -RUN apt-get update && apt-get upgrade -y +RUN apk update && apk upgrade +RUN apk add g++ gcc musl-dev libffi-dev +RUN apk add jpeg-dev zlib-dev libjpeg # Copy the content to a code folder in container COPY ./requirements.txt /code/requirements.txt diff --git a/Dockerfiles/cli.Dockerfile b/Dockerfiles/cli.Dockerfile index e57106dc3..8eb8563d2 100644 --- a/Dockerfiles/cli.Dockerfile +++ b/Dockerfiles/cli.Dockerfile @@ -1,6 +1,8 @@ -FROM python:latest as base +FROM python:3.10-alpine as base -RUN apt-get update && apt-get upgrade -y && apt-get install git +RUN apk update && apk upgrade +RUN apk add g++ gcc musl-dev libffi-dev +RUN apk add --no-cache git RUN git clone https://github.com/ScilifelabDataCentre/dds_cli /code WORKDIR /code diff --git a/requirements.txt b/requirements.txt index 2c8528a81..e545e59ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,7 +41,7 @@ MarkupSafe==2.0.1 marshmallow==3.14.1 marshmallow-sqlalchemy==0.27.0 packaging==21.3 -Pillow==9.0.1 +Pillow==9.0.1 # required by qrcode pycparser==2.21 PyMySQL==1.0.2 PyNaCl==1.5.0 From 7d26eab3a3d6a4c82109bb4b7361f0e6bd434309 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 14 Sep 2022 11:39:20 +0200 Subject: [PATCH 011/111] forgot an apt-get --- Dockerfiles/backend.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfiles/backend.Dockerfile b/Dockerfiles/backend.Dockerfile index 8ae8e4e1c..38eac52ab 100644 --- a/Dockerfiles/backend.Dockerfile +++ b/Dockerfiles/backend.Dockerfile @@ -27,7 +27,7 @@ ENV PYTHONPATH /code ################### FROM base as test RUN pip3 install -r /code/tests/requirements-test.txt -RUN apt-get install -y mariadb-client +RUN apk add mariadb-client ################### ## BUILD FRONTEND From bbbd25f203e94262e7a96796e52c01ab957a5436 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 14 Sep 2022 11:51:08 +0200 Subject: [PATCH 012/111] sh instead of bash --- .gitpod.yml | 2 +- docker-compose.yml | 4 ++-- tests/docker-compose-test-interactive.yml | 2 +- tests/docker-compose-test.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitpod.yml b/.gitpod.yml index 27b6c251a..6daaf0ff0 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -17,7 +17,7 @@ tasks: command: > gp await-port 5000 && echo -e "\033[1;31mUse the dds cli in this terminal window\033[0m\n\033[0;33me.g.: dds auth login\033[0m" && - docker exec -it dds_cli bash + docker exec -it dds_cli sh ports: - port: 5000 # backend diff --git a/docker-compose.yml b/docker-compose.yml index ece3acba6..11bb9a3a9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,7 +49,7 @@ services: context: ./ target: base working_dir: /code - command: bash -c "flask db upgrade && flask init-db $$DB_TYPE && flask run -h 0.0.0.0 -p 5000" + command: sh -c "flask db upgrade && flask init-db $$DB_TYPE && flask run -h 0.0.0.0 -p 5000" environment: - DDS_APP_CONFIG=/code/dds_web/sensitive/dds_app.cfg - FLASK_ENV=development @@ -129,7 +129,7 @@ services: cli: container_name: dds_cli - command: bash -c "pip install --upgrade -e . && tail -f /dev/null" + command: sh -c "pip install --upgrade -e . && tail -f /dev/null" build: dockerfile: Dockerfiles/cli.Dockerfile context: ./ diff --git a/tests/docker-compose-test-interactive.yml b/tests/docker-compose-test-interactive.yml index badb23354..e6b64fa73 100644 --- a/tests/docker-compose-test-interactive.yml +++ b/tests/docker-compose-test-interactive.yml @@ -9,7 +9,7 @@ services: dockerfile: Dockerfiles/backend.Dockerfile context: ./ target: test - command: bash + command: sh restart: "no" stdin_open: true tty: true diff --git a/tests/docker-compose-test.yml b/tests/docker-compose-test.yml index 63ecc27ee..1394c1026 100644 --- a/tests/docker-compose-test.yml +++ b/tests/docker-compose-test.yml @@ -9,7 +9,7 @@ services: dockerfile: Dockerfiles/backend.Dockerfile context: ./ target: test - command: bash -c "COVERAGE_FILE=./coverage/.coverage pytest --color=yes $DDS_PYTEST_ARGS --cov=./dds_web --cov-report=xml:coverage/report.xml" + command: sh -c "COVERAGE_FILE=./coverage/.coverage pytest --color=yes $DDS_PYTEST_ARGS --cov=./dds_web --cov-report=xml:coverage/report.xml" restart: "no" volumes: - type: bind From 6df06c7357a1f79464701cd7f58a45a0a238e6fd Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 14 Sep 2022 12:51:13 +0200 Subject: [PATCH 013/111] add timezone --- Dockerfiles/backend.Dockerfile | 4 ++++ requirements.txt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Dockerfiles/backend.Dockerfile b/Dockerfiles/backend.Dockerfile index 38eac52ab..39eaa3d10 100644 --- a/Dockerfiles/backend.Dockerfile +++ b/Dockerfiles/backend.Dockerfile @@ -7,9 +7,13 @@ FROM python:3.10-alpine as base # Install some necessary systems packages RUN apk update && apk upgrade +RUN apk add tzdata RUN apk add g++ gcc musl-dev libffi-dev RUN apk add jpeg-dev zlib-dev libjpeg +# Set time zone +ENV TZ="America/New_York" + # Copy the content to a code folder in container COPY ./requirements.txt /code/requirements.txt diff --git a/requirements.txt b/requirements.txt index e545e59ef..269e3fdd8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ alembic==1.7.6 aniso8601==9.0.1 -APScheduler==3.8.1 +APScheduler==3.9.1 argon2-cffi==21.3.0 argon2-cffi-bindings==21.2.0 Authlib==0.15.5 From b189c6de96ee978453e7ebb18022e8ae23e8fe69 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 14 Sep 2022 12:55:12 +0200 Subject: [PATCH 014/111] comments --- Dockerfiles/backend.Dockerfile | 9 +++++++-- Dockerfiles/cli.Dockerfile | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Dockerfiles/backend.Dockerfile b/Dockerfiles/backend.Dockerfile index 39eaa3d10..d62fb0475 100644 --- a/Dockerfiles/backend.Dockerfile +++ b/Dockerfiles/backend.Dockerfile @@ -5,13 +5,18 @@ # Set official image -- parent image FROM python:3.10-alpine as base -# Install some necessary systems packages +# Update and upgrade RUN apk update && apk upgrade -RUN apk add tzdata + +# Install required dependencies... +# ...Some for build RUN apk add g++ gcc musl-dev libffi-dev + +# ...Some for requirements RUN apk add jpeg-dev zlib-dev libjpeg # Set time zone +RUN apk add tzdata ENV TZ="America/New_York" # Copy the content to a code folder in container diff --git a/Dockerfiles/cli.Dockerfile b/Dockerfiles/cli.Dockerfile index 8eb8563d2..c43a04c8b 100644 --- a/Dockerfiles/cli.Dockerfile +++ b/Dockerfiles/cli.Dockerfile @@ -1,6 +1,10 @@ +# Set official image FROM python:3.10-alpine as base +# Update and upgrade RUN apk update && apk upgrade + +# Install dependencies for build and requirements RUN apk add g++ gcc musl-dev libffi-dev RUN apk add --no-cache git RUN git clone https://github.com/ScilifelabDataCentre/dds_cli /code From 9145d250c5a4e1ee1f0e73692a695b460c98e76b Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 14 Sep 2022 13:46:20 +0200 Subject: [PATCH 015/111] changelog for 1272 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15dad5958..3126a45bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -141,3 +141,4 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - New endpoint for setting project as busy / not busy ([#1266](https://github.com/ScilifelabDataCentre/dds_web/pull/1266)) - Check for if project busy before status change ([#1266](https://github.com/ScilifelabDataCentre/dds_web/pull/1266)) - Bug fix: Default timestamps fixed ([#1271](https://github.com/ScilifelabDataCentre/dds_web/pull/1271)) +- Change docker image to alpine ([#1272](https://github.com/ScilifelabDataCentre/dds_web/pull/1272)) From 118944606369296cb5527578f122fbfb2001b54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Wed, 14 Sep 2022 13:50:04 +0200 Subject: [PATCH 016/111] from bash to sh --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 81fd5a77a..9b2adad39 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ Requires that dds_cli is checked out in `../dds_cli` (otherwise adapt the volume 2. Inject into the `dds_cli` container: ```bash - docker exec -it dds_cli /bin/bash + docker exec -it dds_cli /bin/sh ``` Then you can freely use the dds cli component against the local development setup in the active CLI. @@ -237,7 +237,7 @@ docker-compose -f docker-compose.yml -f tests/docker-compose-test-interactive.ym Then in a new terminal, shell into the container and run pytest: ```bash -docker exec -it dds_backend /bin/bash +docker exec -it dds_backend /bin/sh ``` ```bash From 2c89d86ecbc2034f03236c77e9afc3b43563dbc9 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 14 Sep 2022 14:02:14 +0200 Subject: [PATCH 017/111] timezone to utc --- Dockerfiles/backend.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfiles/backend.Dockerfile b/Dockerfiles/backend.Dockerfile index d62fb0475..954690583 100644 --- a/Dockerfiles/backend.Dockerfile +++ b/Dockerfiles/backend.Dockerfile @@ -17,7 +17,7 @@ RUN apk add jpeg-dev zlib-dev libjpeg # Set time zone RUN apk add tzdata -ENV TZ="America/New_York" +ENV TZ="UCT" # Copy the content to a code folder in container COPY ./requirements.txt /code/requirements.txt From 8d29d9e691c1f5f7e0f88baa6ced4d07752f68d7 Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Wed, 14 Sep 2022 15:59:53 +0200 Subject: [PATCH 018/111] fix cost calculation --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 7dfd94de6..1e1a5d78f 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -550,7 +550,7 @@ def project_usage(project): cost_gbhour = 0.09 / (30 * 24) # Save file cost to project info and increase total unit cost - cost += bhours / 1e9 * cost_gbhour + cost = (bhours / 1e9) * cost_gbhour return bhours, cost From 7c55d6c5ab02e2499dbe23e02e10e73c544efa74 Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Wed, 14 Sep 2022 16:28:00 +0200 Subject: [PATCH 019/111] code rearrangements --- dds_web/api/project.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 1e1a5d78f..1bd40111b 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -533,6 +533,8 @@ def format_project_dict(self, current_user): @staticmethod def project_usage(project): + # Calculate approximate cost per gbhour: kr per gb per month / (days * hours) + cost_gbhour = 0.09 / (30 * 24) bhours = 0.0 cost = 0.0 @@ -541,16 +543,13 @@ def project_usage(project): time_deleted = v.time_deleted if v.time_deleted else dds_web.utils.current_time() time_uploaded = v.time_uploaded - # Calculate BHours + # Calculate and accumulate BHours for all versions in project bhours += dds_web.utils.calculate_bytehours( minuend=time_deleted, subtrahend=time_uploaded, size_bytes=v.size_stored ) - # Calculate approximate cost per gbhour: kr per gb per month / (days * hours) - cost_gbhour = 0.09 / (30 * 24) - - # Save file cost to project info and increase total unit cost - cost = (bhours / 1e9) * cost_gbhour + # Save file cost to project info and increase total unit cost + cost = (bhours / 1e9) * cost_gbhour return bhours, cost From fb5ea9255e3666d1d492d0d7211b79b68e122cbd Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 08:50:36 +0200 Subject: [PATCH 020/111] testing adding docker-security-warning-yml --- .github/workflows/dockerhub.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index b5fd6e7fd..6739ede21 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -33,8 +33,30 @@ jobs: images: | scilifelabdatacentre/dds-backend ghcr.io/scilifelabdatacentre/dds-backend + - name: Ensure lowercase name + run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + - name: Build for scan + uses: docker/build-push-action@v3 + with: + file: Dockerfiles/backend.Dockerfile + context: . + push: false + target: production + tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.7.1 + with: + image-ref: 'ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}' + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH' + - name: Upload Trivy scan results to Github Security tab + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: 'trivy-results.sarif' + category: trivy-build - name: Publish image - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: file: Dockerfiles/backend.Dockerfile context: . From 7cb19e5073e22f6446a603135cef2d2485293099 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 08:52:15 +0200 Subject: [PATCH 021/111] changed versions --- .github/workflows/dockerhub.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 6739ede21..7e61b09a8 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -16,19 +16,19 @@ jobs: - name: Check out the repo uses: actions/checkout@v2 - name: Log in to Docker Hub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Log in to Github Container Repository - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Docker metadata id: meta - uses: docker/metadata-action@v3 + uses: docker/metadata-action@v4 with: images: | scilifelabdatacentre/dds-backend From 90f5b6473464a358313c2028c96ad289addcb39b Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 08:53:43 +0200 Subject: [PATCH 022/111] prettier --- .github/workflows/dockerhub.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 7e61b09a8..69ddcb3b7 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -46,14 +46,14 @@ jobs: - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@0.7.1 with: - image-ref: 'ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}' - format: 'sarif' - output: 'trivy-results.sarif' - severity: 'CRITICAL,HIGH' + image-ref: "ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}" + format: "sarif" + output: "trivy-results.sarif" + severity: "CRITICAL,HIGH" - name: Upload Trivy scan results to Github Security tab uses: github/codeql-action/upload-sarif@v2 with: - sarif_file: 'trivy-results.sarif' + sarif_file: "trivy-results.sarif" category: trivy-build - name: Publish image uses: docker/build-push-action@v3 From 2f011d13438572a4deeb02ddf8b52ad7736c21e8 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 09:06:11 +0200 Subject: [PATCH 023/111] on pr --- .github/workflows/dockerhub.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 69ddcb3b7..d86597772 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -1,6 +1,7 @@ --- name: Publish Docker Image on: + pull_request: push: branches: - master From fc029bd271c01b17176fd72428d7a12c14f1f2e4 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 10:07:17 +0200 Subject: [PATCH 024/111] run on edit --- .github/workflows/dockerhub.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index d86597772..f347575a4 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -2,6 +2,7 @@ name: Publish Docker Image on: pull_request: + types: [opened, edited, reopened] push: branches: - master From b73c7eeeadf26f2209ff9fac3c8bed8b704cfcb5 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 10:26:07 +0200 Subject: [PATCH 025/111] new action --- .github/workflows/dockerhub.yml | 134 ++++++++++++------------ .github/workflows/publish_and_trivy.yml | 105 +++++++++++++++++++ .github/workflows/trivy.yml | 58 +++++----- 3 files changed, 201 insertions(+), 96 deletions(-) create mode 100644 .github/workflows/publish_and_trivy.yml diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index f347575a4..089914d8c 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -1,67 +1,67 @@ ---- -name: Publish Docker Image -on: - pull_request: - types: [opened, edited, reopened] - push: - branches: - - master - - dev - release: - types: [published] -jobs: - push_to_registry: - if: github.repository == 'ScilifelabDataCentre/dds_web' - name: Push Docker image to Docker Hub - runs-on: ubuntu-latest - steps: - - name: Check out the repo - uses: actions/checkout@v2 - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Log in to Github Container Repository - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Docker metadata - id: meta - uses: docker/metadata-action@v4 - with: - images: | - scilifelabdatacentre/dds-backend - ghcr.io/scilifelabdatacentre/dds-backend - - name: Ensure lowercase name - run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV - - name: Build for scan - uses: docker/build-push-action@v3 - with: - file: Dockerfiles/backend.Dockerfile - context: . - push: false - target: production - tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@0.7.1 - with: - image-ref: "ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}" - format: "sarif" - output: "trivy-results.sarif" - severity: "CRITICAL,HIGH" - - name: Upload Trivy scan results to Github Security tab - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: "trivy-results.sarif" - category: trivy-build - - name: Publish image - uses: docker/build-push-action@v3 - with: - file: Dockerfiles/backend.Dockerfile - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} +# --- +# name: Publish Docker Image +# on: +# pull_request: +# types: [opened, edited, reopened] +# push: +# branches: +# - master +# - dev +# release: +# types: [published] +# jobs: +# push_to_registry: +# if: github.repository == 'ScilifelabDataCentre/dds_web' +# name: Push Docker image to Docker Hub +# runs-on: ubuntu-latest +# steps: +# - name: Check out the repo +# uses: actions/checkout@v2 +# - name: Log in to Docker Hub +# uses: docker/login-action@v2 +# with: +# username: ${{ secrets.DOCKERHUB_USERNAME }} +# password: ${{ secrets.DOCKERHUB_TOKEN }} +# - name: Log in to Github Container Repository +# uses: docker/login-action@v2 +# with: +# registry: ghcr.io +# username: ${{ github.actor }} +# password: ${{ secrets.GITHUB_TOKEN }} +# - name: Docker metadata +# id: meta +# uses: docker/metadata-action@v4 +# with: +# images: | +# scilifelabdatacentre/dds-backend +# ghcr.io/scilifelabdatacentre/dds-backend +# - name: Ensure lowercase name +# run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV +# - name: Build for scan +# uses: docker/build-push-action@v3 +# with: +# file: Dockerfiles/backend.Dockerfile +# context: . +# push: false +# target: production +# tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} +# - name: Run Trivy vulnerability scanner +# uses: aquasecurity/trivy-action@0.7.1 +# with: +# image-ref: "ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}" +# format: "sarif" +# output: "trivy-results.sarif" +# severity: "CRITICAL,HIGH" +# - name: Upload Trivy scan results to Github Security tab +# uses: github/codeql-action/upload-sarif@v2 +# with: +# sarif_file: "trivy-results.sarif" +# category: trivy-build +# - name: Publish image +# uses: docker/build-push-action@v3 +# with: +# file: Dockerfiles/backend.Dockerfile +# context: . +# push: true +# tags: ${{ steps.meta.outputs.tags }} +# labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/publish_and_trivy.yml b/.github/workflows/publish_and_trivy.yml new file mode 100644 index 000000000..ba8d00bdb --- /dev/null +++ b/.github/workflows/publish_and_trivy.yml @@ -0,0 +1,105 @@ +--- +# Build and publish docker image(s) +# The created image will be checked by trivy and security issues will be published to the repositorys security tab +# Publishing to Dockerhub requires a username and token +# as the secrets DOCKERHUB_USERNAME and DOCKERHUB_TOKEN +# remember to change repo-name and set dockerfile(s)/image name(s) in the matrix + +name: Publish Docker Image +on: + # generate image whenever there is a push to the listed branches + push: + branches: + - master + - dev + # !!! Remove this when testing is done + pull_request: + types: [opened, edited, reopened] + # generate images for releases, using the tag name + # the newest one will be latest as well + release: + types: [published] +jobs: + push_to_registry: + # only generate images when in the named repo + # (to avoid running the action in forks) + if: github.repository == 'scilifelabdatacentre/dds_web' + name: Publish Docker Image + runs-on: ubuntu-latest + # Cancel earlier job if there is a new one for the same branch/release + concurrency: + group: ${{ github.ref }}-docker-build + cancel-in-progress: true + # Define the images/tags to build; will run in parallell + strategy: + matrix: + include: + - dockerfile: Dockerfiles/Dockerfile.backend + # Should this be dds_web-backend? + images: | + docker.io/scilifelabdatacentre/dds-backend + ghcr.io/scilifelabdatacentre/dds-backend + permissions: + contents: read + packages: write + security-events: write + steps: + - name: Check out the repo + uses: actions/checkout@v3 + # only needed when publishing to Dockerhub + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + # available in scilifelabdatacentre, ask admin for help + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + # only needed when publishing to Github (ghcr.io) + - name: Log in to Github Container Repository + uses: docker/login-action@v2 + with: + registry: ghcr.io + # will run as the user who triggered the action, using its token + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Docker Meta + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ matrix.images }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + - name: Ensure lowercase name + run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + - name: Build for scan + uses: docker/build-push-action@v3 + with: + file: ${{ matrix.dockerfile }} + context: . + push: false + target: production + tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.7.1 + with: + image-ref: 'ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}' + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH' + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: 'trivy-results.sarif' + category: trivy-build + - name: Build and Publish + uses: docker/build-push-action@v3 + with: + file: ${{ matrix.dockerfile }} + context: . + push: true + # Set to wanted target, or remove if you do not use targets + target: production + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 7de31b4d4..e01a793f0 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,34 +1,34 @@ ---- -name: trivy +# --- +# name: trivy -on: - schedule: - - cron: "22 22 * * *" +# on: +# schedule: +# - cron: "22 22 * * *" -jobs: - scan: - permissions: - contents: read - security-events: write - name: Build - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 +# jobs: +# scan: +# permissions: +# contents: read +# security-events: write +# name: Build +# runs-on: ubuntu-latest +# steps: +# - name: Checkout code +# uses: actions/checkout@v3 - - name: Ensure lowercase name - run: echo REPOSITORY_OWNER=$(echo ${{ github.repository_owner }} | tr "[:upper:]" "[:lower:]") >> $GITHUB_ENV +# - name: Ensure lowercase name +# run: echo REPOSITORY_OWNER=$(echo ${{ github.repository_owner }} | tr "[:upper:]" "[:lower:]") >> $GITHUB_ENV - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@0.7.1 - with: - image-ref: "ghcr.io/${{ env.REPOSITORY_OWNER }}/dds-backend:latest" - format: "sarif" - output: "trivy-results.sarif" - severity: "CRITICAL,HIGH" +# - name: Run Trivy vulnerability scanner +# uses: aquasecurity/trivy-action@0.7.1 +# with: +# image-ref: "ghcr.io/${{ env.REPOSITORY_OWNER }}/dds-backend:latest" +# format: "sarif" +# output: "trivy-results.sarif" +# severity: "CRITICAL,HIGH" - - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: "trivy-results.sarif" - category: trivy +# - name: Upload Trivy scan results to GitHub Security tab +# uses: github/codeql-action/upload-sarif@v2 +# with: +# sarif_file: "trivy-results.sarif" +# category: trivy From fa442d114c68aee65be6907fb1c533eb8223bf39 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 10:28:04 +0200 Subject: [PATCH 026/111] new time and running on dev --- .github/workflows/trivy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 7de31b4d4..7a48ea344 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -3,7 +3,7 @@ name: trivy on: schedule: - - cron: "22 22 * * *" + - cron: "30 10 * * *" jobs: scan: @@ -22,7 +22,7 @@ jobs: - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@0.7.1 with: - image-ref: "ghcr.io/${{ env.REPOSITORY_OWNER }}/dds-backend:latest" + image-ref: "ghcr.io/${{ env.REPOSITORY_OWNER }}/dds-backend:dev" format: "sarif" output: "trivy-results.sarif" severity: "CRITICAL,HIGH" From a677ef917090e9d397b32c8cd9398582389545d0 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 10:31:27 +0200 Subject: [PATCH 027/111] change in minutes --- .github/workflows/trivy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 7a48ea344..c92331d7c 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -3,7 +3,7 @@ name: trivy on: schedule: - - cron: "30 10 * * *" + - cron: "40 10 * * *" jobs: scan: From d401f22569a2b78ba428a8938ecf7d049da4393c Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 10:34:24 +0200 Subject: [PATCH 028/111] change in time --- .github/workflows/trivy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index c92331d7c..9f160b8ab 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -3,7 +3,7 @@ name: trivy on: schedule: - - cron: "40 10 * * *" + - cron: "51 10 * * *" jobs: scan: From 47dd34c30170f374ea499eef9b2b07e7d335a6e3 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 10:36:57 +0200 Subject: [PATCH 029/111] prettier --- .github/workflows/publish_and_trivy.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish_and_trivy.yml b/.github/workflows/publish_and_trivy.yml index ba8d00bdb..b22363938 100644 --- a/.github/workflows/publish_and_trivy.yml +++ b/.github/workflows/publish_and_trivy.yml @@ -12,7 +12,7 @@ on: branches: - master - dev - # !!! Remove this when testing is done + # !!! Remove this when testing is done pull_request: types: [opened, edited, reopened] # generate images for releases, using the tag name @@ -84,14 +84,14 @@ jobs: - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@0.7.1 with: - image-ref: 'ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}' - format: 'sarif' - output: 'trivy-results.sarif' - severity: 'CRITICAL,HIGH' + image-ref: "ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}" + format: "sarif" + output: "trivy-results.sarif" + severity: "CRITICAL,HIGH" - name: Upload Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@v2 with: - sarif_file: 'trivy-results.sarif' + sarif_file: "trivy-results.sarif" category: trivy-build - name: Build and Publish uses: docker/build-push-action@v3 @@ -102,4 +102,4 @@ jobs: # Set to wanted target, or remove if you do not use targets target: production tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file + labels: ${{ steps.meta.outputs.labels }} From b7db5157d8aae09f731445f7b0e6a00e75cfb746 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 10:43:18 +0200 Subject: [PATCH 030/111] may be complaining --- .github/workflows/publish_and_trivy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish_and_trivy.yml b/.github/workflows/publish_and_trivy.yml index b22363938..8179d87b3 100644 --- a/.github/workflows/publish_and_trivy.yml +++ b/.github/workflows/publish_and_trivy.yml @@ -76,7 +76,7 @@ jobs: - name: Build for scan uses: docker/build-push-action@v3 with: - file: ${{ matrix.dockerfile }} + file: Dockerfiles/backend.Dockerfile context: . push: false target: production @@ -96,7 +96,7 @@ jobs: - name: Build and Publish uses: docker/build-push-action@v3 with: - file: ${{ matrix.dockerfile }} + file: Dockerfiles/backend.Dockerfile context: . push: true # Set to wanted target, or remove if you do not use targets From a386877edd34feccd6c60cbd6f60856b61d10276 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 10:47:03 +0200 Subject: [PATCH 031/111] not production --- .github/workflows/publish_and_trivy.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/publish_and_trivy.yml b/.github/workflows/publish_and_trivy.yml index 8179d87b3..94c94f483 100644 --- a/.github/workflows/publish_and_trivy.yml +++ b/.github/workflows/publish_and_trivy.yml @@ -100,6 +100,5 @@ jobs: context: . push: true # Set to wanted target, or remove if you do not use targets - target: production tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} From 1a739dbcecfda5e702e0597d111a5d91efe84dd0 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 10:47:25 +0200 Subject: [PATCH 032/111] remove comment --- .github/workflows/publish_and_trivy.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/publish_and_trivy.yml b/.github/workflows/publish_and_trivy.yml index 94c94f483..68f3fd40e 100644 --- a/.github/workflows/publish_and_trivy.yml +++ b/.github/workflows/publish_and_trivy.yml @@ -99,6 +99,5 @@ jobs: file: Dockerfiles/backend.Dockerfile context: . push: true - # Set to wanted target, or remove if you do not use targets tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} From 2f3f929c4f642a1dacb723a2bc6c387313967442 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 10:54:58 +0200 Subject: [PATCH 033/111] change differences --- .github/workflows/dockerhub.yml | 133 ++++++++++++------------ .github/workflows/publish_and_trivy.yml | 5 +- 2 files changed, 69 insertions(+), 69 deletions(-) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 089914d8c..ab43f6a6e 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -1,67 +1,66 @@ -# --- -# name: Publish Docker Image -# on: -# pull_request: -# types: [opened, edited, reopened] -# push: -# branches: -# - master -# - dev -# release: -# types: [published] -# jobs: -# push_to_registry: -# if: github.repository == 'ScilifelabDataCentre/dds_web' -# name: Push Docker image to Docker Hub -# runs-on: ubuntu-latest -# steps: -# - name: Check out the repo -# uses: actions/checkout@v2 -# - name: Log in to Docker Hub -# uses: docker/login-action@v2 -# with: -# username: ${{ secrets.DOCKERHUB_USERNAME }} -# password: ${{ secrets.DOCKERHUB_TOKEN }} -# - name: Log in to Github Container Repository -# uses: docker/login-action@v2 -# with: -# registry: ghcr.io -# username: ${{ github.actor }} -# password: ${{ secrets.GITHUB_TOKEN }} -# - name: Docker metadata -# id: meta -# uses: docker/metadata-action@v4 -# with: -# images: | -# scilifelabdatacentre/dds-backend -# ghcr.io/scilifelabdatacentre/dds-backend -# - name: Ensure lowercase name -# run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV -# - name: Build for scan -# uses: docker/build-push-action@v3 -# with: -# file: Dockerfiles/backend.Dockerfile -# context: . -# push: false -# target: production -# tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} -# - name: Run Trivy vulnerability scanner -# uses: aquasecurity/trivy-action@0.7.1 -# with: -# image-ref: "ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}" -# format: "sarif" -# output: "trivy-results.sarif" -# severity: "CRITICAL,HIGH" -# - name: Upload Trivy scan results to Github Security tab -# uses: github/codeql-action/upload-sarif@v2 -# with: -# sarif_file: "trivy-results.sarif" -# category: trivy-build -# - name: Publish image -# uses: docker/build-push-action@v3 -# with: -# file: Dockerfiles/backend.Dockerfile -# context: . -# push: true -# tags: ${{ steps.meta.outputs.tags }} -# labels: ${{ steps.meta.outputs.labels }} +--- +name: Publish Docker Image +on: + pull_request: + types: [opened, edited, reopened] + push: + branches: + - master + - dev + release: + types: [published] +jobs: + push_to_registry: + if: github.repository == 'ScilifelabDataCentre/dds_web' + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v2 + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Log in to Github Container Repository + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + scilifelabdatacentre/dds-backend + ghcr.io/scilifelabdatacentre/dds-backend + - name: Ensure lowercase name + run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + - name: Build for scan + uses: docker/build-push-action@v3 + with: + file: Dockerfiles/backend.Dockerfile + context: . + push: false + tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.7.1 + with: + image-ref: "ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}" + format: "sarif" + output: "trivy-results.sarif" + severity: "CRITICAL,HIGH" + - name: Upload Trivy scan results to Github Security tab + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: "trivy-results.sarif" + category: trivy-build + - name: Publish image + uses: docker/build-push-action@v3 + with: + file: Dockerfiles/backend.Dockerfile + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/publish_and_trivy.yml b/.github/workflows/publish_and_trivy.yml index 68f3fd40e..0975a970f 100644 --- a/.github/workflows/publish_and_trivy.yml +++ b/.github/workflows/publish_and_trivy.yml @@ -65,7 +65,9 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: ${{ matrix.images }} + images: | + scilifelabdatacentre/dds-backend + ghcr.io/scilifelabdatacentre/dds-backend tags: | type=ref,event=branch type=semver,pattern={{version}} @@ -79,7 +81,6 @@ jobs: file: Dockerfiles/backend.Dockerfile context: . push: false - target: production tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@0.7.1 From b3434694ed8f354bf74a2f5fda5f5ea5a3c790d8 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 10:55:31 +0200 Subject: [PATCH 034/111] commend action out --- .github/workflows/dockerhub.yml | 132 ++++++++++++++++---------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index ab43f6a6e..93f0c84ba 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -1,66 +1,66 @@ ---- -name: Publish Docker Image -on: - pull_request: - types: [opened, edited, reopened] - push: - branches: - - master - - dev - release: - types: [published] -jobs: - push_to_registry: - if: github.repository == 'ScilifelabDataCentre/dds_web' - name: Push Docker image to Docker Hub - runs-on: ubuntu-latest - steps: - - name: Check out the repo - uses: actions/checkout@v2 - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Log in to Github Container Repository - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Docker metadata - id: meta - uses: docker/metadata-action@v4 - with: - images: | - scilifelabdatacentre/dds-backend - ghcr.io/scilifelabdatacentre/dds-backend - - name: Ensure lowercase name - run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV - - name: Build for scan - uses: docker/build-push-action@v3 - with: - file: Dockerfiles/backend.Dockerfile - context: . - push: false - tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@0.7.1 - with: - image-ref: "ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}" - format: "sarif" - output: "trivy-results.sarif" - severity: "CRITICAL,HIGH" - - name: Upload Trivy scan results to Github Security tab - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: "trivy-results.sarif" - category: trivy-build - - name: Publish image - uses: docker/build-push-action@v3 - with: - file: Dockerfiles/backend.Dockerfile - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} +# --- +# name: Publish Docker Image +# on: +# pull_request: +# types: [opened, edited, reopened] +# push: +# branches: +# - master +# - dev +# release: +# types: [published] +# jobs: +# push_to_registry: +# if: github.repository == 'ScilifelabDataCentre/dds_web' +# name: Push Docker image to Docker Hub +# runs-on: ubuntu-latest +# steps: +# - name: Check out the repo +# uses: actions/checkout@v2 +# - name: Log in to Docker Hub +# uses: docker/login-action@v2 +# with: +# username: ${{ secrets.DOCKERHUB_USERNAME }} +# password: ${{ secrets.DOCKERHUB_TOKEN }} +# - name: Log in to Github Container Repository +# uses: docker/login-action@v2 +# with: +# registry: ghcr.io +# username: ${{ github.actor }} +# password: ${{ secrets.GITHUB_TOKEN }} +# - name: Docker metadata +# id: meta +# uses: docker/metadata-action@v4 +# with: +# images: | +# scilifelabdatacentre/dds-backend +# ghcr.io/scilifelabdatacentre/dds-backend +# - name: Ensure lowercase name +# run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV +# - name: Build for scan +# uses: docker/build-push-action@v3 +# with: +# file: Dockerfiles/backend.Dockerfile +# context: . +# push: false +# tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} +# - name: Run Trivy vulnerability scanner +# uses: aquasecurity/trivy-action@0.7.1 +# with: +# image-ref: "ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}" +# format: "sarif" +# output: "trivy-results.sarif" +# severity: "CRITICAL,HIGH" +# - name: Upload Trivy scan results to Github Security tab +# uses: github/codeql-action/upload-sarif@v2 +# with: +# sarif_file: "trivy-results.sarif" +# category: trivy-build +# - name: Publish image +# uses: docker/build-push-action@v3 +# with: +# file: Dockerfiles/backend.Dockerfile +# context: . +# push: true +# tags: ${{ steps.meta.outputs.tags }} +# labels: ${{ steps.meta.outputs.labels }} From 9d28131623a6d8cefb62fd1788b56a83d6065650 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 10:58:42 +0200 Subject: [PATCH 035/111] changing file --- .github/workflows/dockerhub.yml | 132 +++++++-------- .github/workflows/publish_and_trivy.yml | 206 ++++++++++++------------ 2 files changed, 169 insertions(+), 169 deletions(-) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 93f0c84ba..ab43f6a6e 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -1,66 +1,66 @@ -# --- -# name: Publish Docker Image -# on: -# pull_request: -# types: [opened, edited, reopened] -# push: -# branches: -# - master -# - dev -# release: -# types: [published] -# jobs: -# push_to_registry: -# if: github.repository == 'ScilifelabDataCentre/dds_web' -# name: Push Docker image to Docker Hub -# runs-on: ubuntu-latest -# steps: -# - name: Check out the repo -# uses: actions/checkout@v2 -# - name: Log in to Docker Hub -# uses: docker/login-action@v2 -# with: -# username: ${{ secrets.DOCKERHUB_USERNAME }} -# password: ${{ secrets.DOCKERHUB_TOKEN }} -# - name: Log in to Github Container Repository -# uses: docker/login-action@v2 -# with: -# registry: ghcr.io -# username: ${{ github.actor }} -# password: ${{ secrets.GITHUB_TOKEN }} -# - name: Docker metadata -# id: meta -# uses: docker/metadata-action@v4 -# with: -# images: | -# scilifelabdatacentre/dds-backend -# ghcr.io/scilifelabdatacentre/dds-backend -# - name: Ensure lowercase name -# run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV -# - name: Build for scan -# uses: docker/build-push-action@v3 -# with: -# file: Dockerfiles/backend.Dockerfile -# context: . -# push: false -# tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} -# - name: Run Trivy vulnerability scanner -# uses: aquasecurity/trivy-action@0.7.1 -# with: -# image-ref: "ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}" -# format: "sarif" -# output: "trivy-results.sarif" -# severity: "CRITICAL,HIGH" -# - name: Upload Trivy scan results to Github Security tab -# uses: github/codeql-action/upload-sarif@v2 -# with: -# sarif_file: "trivy-results.sarif" -# category: trivy-build -# - name: Publish image -# uses: docker/build-push-action@v3 -# with: -# file: Dockerfiles/backend.Dockerfile -# context: . -# push: true -# tags: ${{ steps.meta.outputs.tags }} -# labels: ${{ steps.meta.outputs.labels }} +--- +name: Publish Docker Image +on: + pull_request: + types: [opened, edited, reopened] + push: + branches: + - master + - dev + release: + types: [published] +jobs: + push_to_registry: + if: github.repository == 'ScilifelabDataCentre/dds_web' + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v2 + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Log in to Github Container Repository + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + scilifelabdatacentre/dds-backend + ghcr.io/scilifelabdatacentre/dds-backend + - name: Ensure lowercase name + run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + - name: Build for scan + uses: docker/build-push-action@v3 + with: + file: Dockerfiles/backend.Dockerfile + context: . + push: false + tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.7.1 + with: + image-ref: "ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}" + format: "sarif" + output: "trivy-results.sarif" + severity: "CRITICAL,HIGH" + - name: Upload Trivy scan results to Github Security tab + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: "trivy-results.sarif" + category: trivy-build + - name: Publish image + uses: docker/build-push-action@v3 + with: + file: Dockerfiles/backend.Dockerfile + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/publish_and_trivy.yml b/.github/workflows/publish_and_trivy.yml index 0975a970f..cd50a976d 100644 --- a/.github/workflows/publish_and_trivy.yml +++ b/.github/workflows/publish_and_trivy.yml @@ -1,104 +1,104 @@ ---- -# Build and publish docker image(s) -# The created image will be checked by trivy and security issues will be published to the repositorys security tab -# Publishing to Dockerhub requires a username and token -# as the secrets DOCKERHUB_USERNAME and DOCKERHUB_TOKEN -# remember to change repo-name and set dockerfile(s)/image name(s) in the matrix +# --- +# # Build and publish docker image(s) +# # The created image will be checked by trivy and security issues will be published to the repositorys security tab +# # Publishing to Dockerhub requires a username and token +# # as the secrets DOCKERHUB_USERNAME and DOCKERHUB_TOKEN +# # remember to change repo-name and set dockerfile(s)/image name(s) in the matrix -name: Publish Docker Image -on: - # generate image whenever there is a push to the listed branches - push: - branches: - - master - - dev - # !!! Remove this when testing is done - pull_request: - types: [opened, edited, reopened] - # generate images for releases, using the tag name - # the newest one will be latest as well - release: - types: [published] -jobs: - push_to_registry: - # only generate images when in the named repo - # (to avoid running the action in forks) - if: github.repository == 'scilifelabdatacentre/dds_web' - name: Publish Docker Image - runs-on: ubuntu-latest - # Cancel earlier job if there is a new one for the same branch/release - concurrency: - group: ${{ github.ref }}-docker-build - cancel-in-progress: true - # Define the images/tags to build; will run in parallell - strategy: - matrix: - include: - - dockerfile: Dockerfiles/Dockerfile.backend - # Should this be dds_web-backend? - images: | - docker.io/scilifelabdatacentre/dds-backend - ghcr.io/scilifelabdatacentre/dds-backend - permissions: - contents: read - packages: write - security-events: write - steps: - - name: Check out the repo - uses: actions/checkout@v3 - # only needed when publishing to Dockerhub - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - # available in scilifelabdatacentre, ask admin for help - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - # only needed when publishing to Github (ghcr.io) - - name: Log in to Github Container Repository - uses: docker/login-action@v2 - with: - registry: ghcr.io - # will run as the user who triggered the action, using its token - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Docker Meta - id: meta - uses: docker/metadata-action@v4 - with: - images: | - scilifelabdatacentre/dds-backend - ghcr.io/scilifelabdatacentre/dds-backend - tags: | - type=ref,event=branch - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - - name: Ensure lowercase name - run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV - - name: Build for scan - uses: docker/build-push-action@v3 - with: - file: Dockerfiles/backend.Dockerfile - context: . - push: false - tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@0.7.1 - with: - image-ref: "ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}" - format: "sarif" - output: "trivy-results.sarif" - severity: "CRITICAL,HIGH" - - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: "trivy-results.sarif" - category: trivy-build - - name: Build and Publish - uses: docker/build-push-action@v3 - with: - file: Dockerfiles/backend.Dockerfile - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} +# name: Publish Docker Image +# on: +# # generate image whenever there is a push to the listed branches +# push: +# branches: +# - master +# - dev +# # !!! Remove this when testing is done +# pull_request: +# types: [opened, edited, reopened] +# # generate images for releases, using the tag name +# # the newest one will be latest as well +# release: +# types: [published] +# jobs: +# push_to_registry: +# # only generate images when in the named repo +# # (to avoid running the action in forks) +# if: github.repository == 'scilifelabdatacentre/dds_web' +# name: Publish Docker Image +# runs-on: ubuntu-latest +# # Cancel earlier job if there is a new one for the same branch/release +# concurrency: +# group: ${{ github.ref }}-docker-build +# cancel-in-progress: true +# # Define the images/tags to build; will run in parallell +# strategy: +# matrix: +# include: +# - dockerfile: Dockerfiles/Dockerfile.backend +# # Should this be dds_web-backend? +# images: | +# docker.io/scilifelabdatacentre/dds-backend +# ghcr.io/scilifelabdatacentre/dds-backend +# permissions: +# contents: read +# packages: write +# security-events: write +# steps: +# - name: Check out the repo +# uses: actions/checkout@v3 +# # only needed when publishing to Dockerhub +# - name: Log in to Docker Hub +# uses: docker/login-action@v2 +# with: +# # available in scilifelabdatacentre, ask admin for help +# username: ${{ secrets.DOCKERHUB_USERNAME }} +# password: ${{ secrets.DOCKERHUB_TOKEN }} +# # only needed when publishing to Github (ghcr.io) +# - name: Log in to Github Container Repository +# uses: docker/login-action@v2 +# with: +# registry: ghcr.io +# # will run as the user who triggered the action, using its token +# username: ${{ github.actor }} +# password: ${{ secrets.GITHUB_TOKEN }} +# - name: Docker Meta +# id: meta +# uses: docker/metadata-action@v4 +# with: +# images: | +# scilifelabdatacentre/dds-backend +# ghcr.io/scilifelabdatacentre/dds-backend +# tags: | +# type=ref,event=branch +# type=semver,pattern={{version}} +# type=semver,pattern={{major}}.{{minor}} +# type=semver,pattern={{major}} +# - name: Ensure lowercase name +# run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV +# - name: Build for scan +# uses: docker/build-push-action@v3 +# with: +# file: Dockerfiles/backend.Dockerfile +# context: . +# push: false +# tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} +# - name: Run Trivy vulnerability scanner +# uses: aquasecurity/trivy-action@0.7.1 +# with: +# image-ref: "ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}" +# format: "sarif" +# output: "trivy-results.sarif" +# severity: "CRITICAL,HIGH" +# - name: Upload Trivy scan results to GitHub Security tab +# uses: github/codeql-action/upload-sarif@v2 +# with: +# sarif_file: "trivy-results.sarif" +# category: trivy-build +# - name: Build and Publish +# uses: docker/build-push-action@v3 +# with: +# file: Dockerfiles/backend.Dockerfile +# context: . +# push: true +# tags: ${{ steps.meta.outputs.tags }} +# labels: ${{ steps.meta.outputs.labels }} From 08cc8ffb25696b18650913e5eb9e5a5c3710abe3 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 11:03:34 +0200 Subject: [PATCH 036/111] remove nonworking action --- .github/workflows/publish_and_trivy.yml | 104 ------------------------ 1 file changed, 104 deletions(-) delete mode 100644 .github/workflows/publish_and_trivy.yml diff --git a/.github/workflows/publish_and_trivy.yml b/.github/workflows/publish_and_trivy.yml deleted file mode 100644 index cd50a976d..000000000 --- a/.github/workflows/publish_and_trivy.yml +++ /dev/null @@ -1,104 +0,0 @@ -# --- -# # Build and publish docker image(s) -# # The created image will be checked by trivy and security issues will be published to the repositorys security tab -# # Publishing to Dockerhub requires a username and token -# # as the secrets DOCKERHUB_USERNAME and DOCKERHUB_TOKEN -# # remember to change repo-name and set dockerfile(s)/image name(s) in the matrix - -# name: Publish Docker Image -# on: -# # generate image whenever there is a push to the listed branches -# push: -# branches: -# - master -# - dev -# # !!! Remove this when testing is done -# pull_request: -# types: [opened, edited, reopened] -# # generate images for releases, using the tag name -# # the newest one will be latest as well -# release: -# types: [published] -# jobs: -# push_to_registry: -# # only generate images when in the named repo -# # (to avoid running the action in forks) -# if: github.repository == 'scilifelabdatacentre/dds_web' -# name: Publish Docker Image -# runs-on: ubuntu-latest -# # Cancel earlier job if there is a new one for the same branch/release -# concurrency: -# group: ${{ github.ref }}-docker-build -# cancel-in-progress: true -# # Define the images/tags to build; will run in parallell -# strategy: -# matrix: -# include: -# - dockerfile: Dockerfiles/Dockerfile.backend -# # Should this be dds_web-backend? -# images: | -# docker.io/scilifelabdatacentre/dds-backend -# ghcr.io/scilifelabdatacentre/dds-backend -# permissions: -# contents: read -# packages: write -# security-events: write -# steps: -# - name: Check out the repo -# uses: actions/checkout@v3 -# # only needed when publishing to Dockerhub -# - name: Log in to Docker Hub -# uses: docker/login-action@v2 -# with: -# # available in scilifelabdatacentre, ask admin for help -# username: ${{ secrets.DOCKERHUB_USERNAME }} -# password: ${{ secrets.DOCKERHUB_TOKEN }} -# # only needed when publishing to Github (ghcr.io) -# - name: Log in to Github Container Repository -# uses: docker/login-action@v2 -# with: -# registry: ghcr.io -# # will run as the user who triggered the action, using its token -# username: ${{ github.actor }} -# password: ${{ secrets.GITHUB_TOKEN }} -# - name: Docker Meta -# id: meta -# uses: docker/metadata-action@v4 -# with: -# images: | -# scilifelabdatacentre/dds-backend -# ghcr.io/scilifelabdatacentre/dds-backend -# tags: | -# type=ref,event=branch -# type=semver,pattern={{version}} -# type=semver,pattern={{major}}.{{minor}} -# type=semver,pattern={{major}} -# - name: Ensure lowercase name -# run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV -# - name: Build for scan -# uses: docker/build-push-action@v3 -# with: -# file: Dockerfiles/backend.Dockerfile -# context: . -# push: false -# tags: ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }} -# - name: Run Trivy vulnerability scanner -# uses: aquasecurity/trivy-action@0.7.1 -# with: -# image-ref: "ghcr.io/${{ env.IMAGE_REPOSITORY }}:sha-${{ github.sha }}" -# format: "sarif" -# output: "trivy-results.sarif" -# severity: "CRITICAL,HIGH" -# - name: Upload Trivy scan results to GitHub Security tab -# uses: github/codeql-action/upload-sarif@v2 -# with: -# sarif_file: "trivy-results.sarif" -# category: trivy-build -# - name: Build and Publish -# uses: docker/build-push-action@v3 -# with: -# file: Dockerfiles/backend.Dockerfile -# context: . -# push: true -# tags: ${{ steps.meta.outputs.tags }} -# labels: ${{ steps.meta.outputs.labels }} From 930729f7ea69284b8d219ec0530b5673b948e6c6 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 11:04:35 +0200 Subject: [PATCH 037/111] trivy cron --- .github/workflows/trivy.yml | 59 ++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index c4efd6c62..7d573bc8f 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,33 +1,30 @@ -# --- -# name: trivy +name: trivy +on: + schedule: + - cron: "51 10 * * *" +jobs: + scan: + permissions: + contents: read + security-events: write + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Ensure lowercase name + run: echo REPOSITORY_OWNER=$(echo ${{ github.repository_owner }} | tr "[:upper:]" "[:lower:]") >> $GITHUB_ENV -# on: -# schedule: -# - cron: "51 10 * * *" + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.7.1 + with: + image-ref: "ghcr.io/${{ env.REPOSITORY_OWNER }}/dds-backend:dev" + format: "sarif" + output: "trivy-results.sarif" + severity: "CRITICAL,HIGH" -# jobs: -# scan: -# permissions: -# contents: read -# security-events: write -# name: Build -# runs-on: ubuntu-latest -# steps: -# - name: Checkout code -# uses: actions/checkout@v3 -# - name: Ensure lowercase name -# run: echo REPOSITORY_OWNER=$(echo ${{ github.repository_owner }} | tr "[:upper:]" "[:lower:]") >> $GITHUB_ENV - -# - name: Run Trivy vulnerability scanner -# uses: aquasecurity/trivy-action@0.7.1 -# with: -# image-ref: "ghcr.io/${{ env.REPOSITORY_OWNER }}/dds-backend:dev" -# format: "sarif" -# output: "trivy-results.sarif" -# severity: "CRITICAL,HIGH" - -# - name: Upload Trivy scan results to GitHub Security tab -# uses: github/codeql-action/upload-sarif@v2 -# with: -# sarif_file: "trivy-results.sarif" -# category: trivy + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: "trivy-results.sarif" + category: trivy From 985806e5fafc205a0653f78d627cc12eb993bab0 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 11:06:03 +0200 Subject: [PATCH 038/111] schedule trivy for every 15 min --- .github/workflows/trivy.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 9f160b8ab..e6f893acc 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,10 +1,7 @@ ---- name: trivy - on: schedule: - - cron: "51 10 * * *" - + - cron: "*/15 * * * *" jobs: scan: permissions: From 98db4b269e38a41127850e31fa51406c2fe8d3d7 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 11:16:25 +0200 Subject: [PATCH 039/111] change in requirements mako --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 208f519e0..0057cc6b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ Jinja2==3.0.3 jmespath==0.10.0 jwcrypto==1.0 limits==2.3.2 -Mako==1.1.6 +Mako==1.2.2 MarkupSafe==2.0.1 marshmallow==3.14.1 marshmallow-sqlalchemy==0.27.0 From 0d74f39a0460bfa1aee66759f0f1cd5562cb7cd4 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 11:17:01 +0200 Subject: [PATCH 040/111] change back --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0057cc6b0..208f519e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ Jinja2==3.0.3 jmespath==0.10.0 jwcrypto==1.0 limits==2.3.2 -Mako==1.2.2 +Mako==1.1.6 MarkupSafe==2.0.1 marshmallow==3.14.1 marshmallow-sqlalchemy==0.27.0 From 6b940108398de6fe58cf9935c5902600b2633cd4 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 11:18:05 +0200 Subject: [PATCH 041/111] mako version change --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 208f519e0..0057cc6b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ Jinja2==3.0.3 jmespath==0.10.0 jwcrypto==1.0 limits==2.3.2 -Mako==1.1.6 +Mako==1.2.2 MarkupSafe==2.0.1 marshmallow==3.14.1 marshmallow-sqlalchemy==0.27.0 From 816394080b1a3d7ae7345cb5522f63125e85d3a8 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 11:33:59 +0200 Subject: [PATCH 042/111] change action file name --- .github/workflows/{dockerhub.yml => publish_and_trivyscan.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{dockerhub.yml => publish_and_trivyscan.yml} (100%) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/publish_and_trivyscan.yml similarity index 100% rename from .github/workflows/dockerhub.yml rename to .github/workflows/publish_and_trivyscan.yml From 0da00b5ec556efbbe3d4a26b7401bd852f945c8f Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 11:35:01 +0200 Subject: [PATCH 043/111] change name of action --- .github/workflows/publish_and_trivyscan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_and_trivyscan.yml b/.github/workflows/publish_and_trivyscan.yml index ab43f6a6e..2f80d219a 100644 --- a/.github/workflows/publish_and_trivyscan.yml +++ b/.github/workflows/publish_and_trivyscan.yml @@ -1,5 +1,5 @@ --- -name: Publish Docker Image +name: Publish Docker Image and run Trivy Security Scan on: pull_request: types: [opened, edited, reopened] From 25c5655b3bac154cb05af30a2c0605030adba704 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 12:42:55 +0200 Subject: [PATCH 044/111] npm update --- dds_web/static/package-lock.json | 3040 +++++++++++------------------- 1 file changed, 1127 insertions(+), 1913 deletions(-) diff --git a/dds_web/static/package-lock.json b/dds_web/static/package-lock.json index a35e8613c..c30d40bcf 100644 --- a/dds_web/static/package-lock.json +++ b/dds_web/static/package-lock.json @@ -30,33 +30,33 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", "dev": true, "dependencies": { - "@babel/highlight": "^7.16.7" + "@babel/highlight": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -102,13 +102,13 @@ "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "node_modules/@babel/highlight/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "engines": { "node": ">=4" @@ -126,6 +126,23 @@ "node": ">=4" } }, + "node_modules/@csstools/selector-specificity": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", + "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2", + "postcss-selector-parser": "^6.0.10" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -191,35 +208,14 @@ } }, "node_modules/@popperjs/core": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz", - "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==", + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, - "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "dependencies": { - "defer-to-connect": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -433,7 +429,7 @@ "node_modules/arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -451,7 +447,7 @@ "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, "engines": { "node": ">=0.8" @@ -469,7 +465,7 @@ "node_modules/async-foreach": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==", "dev": true, "engines": { "node": "*" @@ -478,18 +474,28 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, "node_modules/autoprefixer": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", - "integrity": "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==", + "version": "10.4.11", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.11.tgz", + "integrity": "sha512-5lHp6DgRodxlBLSkzHOTcufWFflH1ewfy2hvFQyjrblBFlP/0Yh4O/Wrg4ow8WRlN3AAUFFLAQwX8hTptzqVHg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], "dependencies": { - "browserslist": "^4.19.1", - "caniuse-lite": "^1.0.30001297", - "fraction.js": "^4.1.2", + "browserslist": "^4.21.3", + "caniuse-lite": "^1.0.30001399", + "fraction.js": "^4.2.0", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -500,10 +506,6 @@ "engines": { "node": "^10 || ^12 || >=14" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, "peerDependencies": { "postcss": "^8.1.0" } @@ -511,7 +513,7 @@ "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "dev": true, "engines": { "node": "*" @@ -532,7 +534,7 @@ "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, "dependencies": { "tweetnacl": "^0.14.3" @@ -639,32 +641,37 @@ } }, "node_modules/browserslist": { - "version": "4.19.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.3.tgz", - "integrity": "sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==", + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], "dependencies": { - "caniuse-lite": "^1.0.30001312", - "electron-to-chromium": "^1.4.71", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" }, "bin": { "browserslist": "cli.js" }, "engines": { "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" } }, "node_modules/bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", "dev": true, "engines": { "node": ">= 0.8" @@ -699,48 +706,6 @@ "node": ">= 10" } }, - "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -790,19 +755,25 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001312", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", - "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==", + "version": "1.0.30001400", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001400.tgz", + "integrity": "sha512-Mv659Hn65Z4LgZdJ7ge5JTVbE3rqbJaaXgW5LEI9/tOaXclfIZ8DW7D7FCWWWmWiiPS7AC48S8kf3DApSxQdgA==", "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true }, "node_modules/chalk": { @@ -857,12 +828,6 @@ "node": ">=10" } }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -909,27 +874,6 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/clone-regexp": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", - "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", - "dev": true, - "dependencies": { - "is-regexp": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -958,9 +902,9 @@ } }, "node_modules/colord": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz", - "integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", "dev": true }, "node_modules/combined-stream": { @@ -1026,7 +970,7 @@ "node_modules/compression/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, "node_modules/compression/node_modules/safe-buffer": { @@ -1038,48 +982,19 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/configstore/node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true }, "node_modules/content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", "dev": true, "engines": { "node": ">= 0.6" @@ -1088,7 +1003,7 @@ "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true }, "node_modules/cosmiconfig": { @@ -1139,19 +1054,10 @@ "node": ">= 8" } }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/css-functions-list": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.0.1.tgz", - "integrity": "sha512-PriDuifDt4u4rkDgnqRCLnjfMatufLmWNfQnGCq34xZwpY3oabwhB9SqRBmuvWUgndbemCFlKqg+nO7C2q0SBw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", + "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", "dev": true, "engines": { "node": ">=12.22" @@ -1172,7 +1078,7 @@ "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, "dependencies": { "assert-plus": "^1.0.0" @@ -1182,26 +1088,26 @@ } }, "node_modules/datatables.net": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.11.5.tgz", - "integrity": "sha512-nlFst2xfwSWaQgaOg5sXVG3cxYC0tH8E8d65289w9ROgF2TmLULOOpcdMpyxxUim/qEwVSEem42RjkTWEpr3eA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.12.1.tgz", + "integrity": "sha512-e6XAMUoV41JdQPS/r9YRfRcmTPcCVvyZbWI+xog1Zg+kjVliMQbEkvWK5XFItmi64Cvwg+IqsZbTUJ1KSY3umA==", "dependencies": { "jquery": ">=1.7" } }, "node_modules/datatables.net-bs5": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-1.11.5.tgz", - "integrity": "sha512-1zyh972GtuK1uAb9h8nP3jJ7f/3UgCDq69LAaZS2bVd4mEHECJ6vrZLacxrkOHOs/q/H3v5sEMeZ46vXz8ox4w==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-1.12.1.tgz", + "integrity": "sha512-CcQCImfmH4YZk7I0aC0kTiNPyfHJ2ueGgOh/kFB9KqsZD8bNJy2A88gC6hn9A7TbmmenOL+K3Q1ti7G8yqi8SQ==", "dependencies": { "datatables.net": ">=1.11.3", "jquery": ">=1.7" } }, "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -1218,7 +1124,7 @@ "node_modules/decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -1227,7 +1133,7 @@ "node_modules/decamelize-keys": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "integrity": "sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==", "dev": true, "dependencies": { "decamelize": "^1.1.0", @@ -1240,24 +1146,12 @@ "node_modules/decamelize-keys/node_modules/map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -1267,28 +1161,26 @@ "node": ">=4.0.0" } }, - "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", "dev": true, "dependencies": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "engines": { "node": ">=0.4.0" @@ -1297,13 +1189,13 @@ "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "dev": true, "engines": { "node": ">= 0.6" @@ -1330,28 +1222,10 @@ "node": ">=8" } }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, "dependencies": { "jsbn": "~0.1.0", @@ -1359,9 +1233,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.75", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz", - "integrity": "sha512-LxgUNeu3BVU7sXaKjUDD9xivocQLxFtq6wgERrutdY/yIOps3ODOZExK1jg8DTEg4U8TUCb5MLGeWFOYuxjF3Q==", + "version": "1.4.251", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.251.tgz", + "integrity": "sha512-k4o4cFrWPv4SoJGGAydd07GmlRVzmeDIJ6MaEChTUjk4Dmomn189tCicSzil2oyvbPoGgg2suwPDNWq4gWRhoQ==", "dev": true }, "node_modules/emoji-regex": { @@ -1414,31 +1288,34 @@ } }, "node_modules/es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.2.tgz", + "integrity": "sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.2", "get-symbol-description": "^1.0.0", "has": "^1.0.3", - "has-symbols": "^1.0.2", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", + "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -1473,19 +1350,10 @@ "node": ">=6" } }, - "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "engines": { "node": ">=0.8.0" @@ -1528,7 +1396,7 @@ "node_modules/execa/node_modules/path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "dev": true, "engines": { "node": ">=4" @@ -1546,7 +1414,7 @@ "node_modules/execa/node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, "dependencies": { "shebang-regex": "^1.0.0" @@ -1558,7 +1426,7 @@ "node_modules/execa/node_modules/shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true, "engines": { "node": ">=0.10.0" @@ -1576,18 +1444,6 @@ "which": "bin/which" } }, - "node_modules/execall": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", - "integrity": "sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==", - "dev": true, - "dependencies": { - "clone-regexp": "^2.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1597,7 +1453,7 @@ "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "dev": true, "engines": [ "node >=0.6.0" @@ -1610,9 +1466,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -1634,17 +1490,20 @@ "node_modules/fast-url-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=", + "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", "dev": true, "dependencies": { "punycode": "^1.3.2" } }, "node_modules/fastest-levenshtein": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", - "dev": true + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } }, "node_modules/fastq": { "version": "1.13.0", @@ -1706,15 +1565,15 @@ } }, "node_modules/flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "dev": true, "engines": { "node": "*" @@ -1735,9 +1594,9 @@ } }, "node_modules/fraction.js": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.3.tgz", - "integrity": "sha512-pUHWWt6vHzZZiQJcM6S/0PXfS+g6FM4BF5rj9wZyreivhQPdsh5PpE25VtSNxq80wHS5RfY51Ii+8Z0Zl/pmzg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", "dev": true, "engines": { "node": "*" @@ -1748,9 +1607,9 @@ } }, "node_modules/fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", @@ -1776,7 +1635,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, "node_modules/fsevents": { @@ -1799,6 +1658,33 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gauge": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", @@ -1841,14 +1727,14 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1857,7 +1743,7 @@ "node_modules/get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", "dev": true, "engines": { "node": ">=0.10.0" @@ -1894,22 +1780,22 @@ "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, "dependencies": { "assert-plus": "^1.0.0" } }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, @@ -1932,30 +1818,6 @@ "node": ">= 6" } }, - "node_modules/global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/global-dirs/node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -2017,17 +1879,17 @@ "node_modules/globjoin": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", - "integrity": "sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", "dev": true }, "node_modules/globule": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.3.tgz", - "integrity": "sha512-mb1aYtDbIjTu4ShMB85m3UzjX9BVKe9WCzsnfMSZk+K5GpIbBOexgg4PPCt5eHDEG5/ZQAUX2Kct02zfiPLsKg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", + "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", "dev": true, "dependencies": { "glob": "~7.1.1", - "lodash": "~4.17.10", + "lodash": "^4.17.21", "minimatch": "~3.0.2" }, "engines": { @@ -2066,38 +1928,16 @@ "node": "*" } }, - "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", "dev": true, "engines": { "node": ">=4" @@ -2139,9 +1979,9 @@ } }, "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2156,6 +1996,18 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -2186,18 +2038,9 @@ "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -2211,12 +2054,15 @@ } }, "node_modules/html-tags": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", - "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", + "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/http-cache-semantics": { @@ -2242,7 +2088,7 @@ "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "dev": true, "dependencies": { "assert-plus": "^1.0.0", @@ -2255,9 +2101,9 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "dependencies": { "agent-base": "6", @@ -2270,7 +2116,7 @@ "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "dev": true, "dependencies": { "ms": "^2.0.0" @@ -2301,7 +2147,7 @@ "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, "node_modules/import-fresh": { @@ -2341,7 +2187,7 @@ "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "engines": { "node": ">=0.8.19" @@ -2365,7 +2211,7 @@ "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "dependencies": { "once": "^1.3.0", @@ -2399,15 +2245,15 @@ } }, "node_modules/ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, "node_modules/is-bigint": { @@ -2451,9 +2297,9 @@ } }, "node_modules/is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.6.tgz", + "integrity": "sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q==", "dev": true, "engines": { "node": ">= 0.4" @@ -2462,22 +2308,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -2519,7 +2353,7 @@ "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "engines": { "node": ">=0.10.0" @@ -2546,26 +2380,10 @@ "node": ">=0.10.0" } }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "dev": true }, "node_modules/is-negative-zero": { @@ -2580,18 +2398,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2602,9 +2408,9 @@ } }, "node_modules/is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" @@ -2616,28 +2422,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -2668,20 +2456,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-regexp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", - "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2689,7 +2471,7 @@ "node_modules/is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "dev": true, "engines": { "node": ">=0.10.0" @@ -2728,7 +2510,7 @@ "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, "node_modules/is-weakref": { @@ -2755,34 +2537,28 @@ "node": ">=8" } }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, "node_modules/jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.1.tgz", + "integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" }, "node_modules/js-base64": { "version": "2.6.4", @@ -2799,13 +2575,7 @@ "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true }, "node_modules/json-parse-better-errors": { @@ -2835,7 +2605,7 @@ "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true }, "node_modules/jsonfile": { @@ -2865,15 +2635,6 @@ "node": ">=0.6.0" } }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.0" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -2884,27 +2645,15 @@ } }, "node_modules/known-css-properties": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.24.0.tgz", - "integrity": "sha512-RTSoaUAfLvpR357vWzAz/50Q/BmHfmE6ETSWfutT0AJiw10e6CmcdYRQJlLRd95B53D0Y2aD1jSxD3V3ySF+PA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.25.0.tgz", + "integrity": "sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA==", "dev": true }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lilconfig": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", "dev": true, "engines": { "node": ">=10" @@ -2919,7 +2668,7 @@ "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", "dev": true, "dependencies": { "graceful-fs": "^4.1.2", @@ -2934,7 +2683,7 @@ "node_modules/load-json-file/node_modules/pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, "engines": { "node": ">=4" @@ -2961,18 +2710,9 @@ "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -2985,30 +2725,6 @@ "node": ">=10" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/make-fetch-happen": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", @@ -3061,7 +2777,7 @@ "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", "dev": true, "engines": { "node": ">= 0.10.0" @@ -3103,48 +2819,39 @@ } }, "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" } }, "node_modules/mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "dependencies": { - "mime-db": "1.51.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -3187,9 +2894,9 @@ } }, "node_modules/minipass": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", - "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", "dev": true, "dependencies": { "yallist": "^4.0.0" @@ -3295,15 +3002,15 @@ "dev": true }, "node_modules/nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", + "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", "dev": true }, "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -3352,25 +3059,24 @@ } }, "node_modules/node-gyp/node_modules/are-we-there-yet": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz", - "integrity": "sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", "dev": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/node-gyp/node_modules/gauge": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.2.tgz", - "integrity": "sha512-aSPRm2CvA9R8QyU5eXMFPd+cYkyxLsXHd2l5/FOH2V/eml//M04G6KZOmTap07O1PvEwNcl2NndyLfK8g3QrKA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1", "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", @@ -3381,34 +3087,34 @@ "wide-align": "^1.1.5" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/node-gyp/node_modules/npmlog": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.1.tgz", - "integrity": "sha512-BTHDvY6nrRHuRfyjt1MAufLxYdVXZfd099H4+i1f0lPywNQyI4foeNXJRObB/uy+TYqUW0vAD9gbdSOXPst7Eg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", "dev": true, "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", - "gauge": "^4.0.0", + "gauge": "^4.0.3", "set-blocking": "^2.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", "dev": true }, "node_modules/node-sass": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-7.0.1.tgz", - "integrity": "sha512-uMy+Xt29NlqKCFdFRZyXKOTqGt+QaKHexv9STj2WeLottnlqZEEWx6Bj0MXNthmFRRdM/YwyNo/8Tr46TOM0jQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-7.0.3.tgz", + "integrity": "sha512-8MIlsY/4dXUkJDYht9pIWBhMil3uHmE8b/AdJPjmFn1nBx9X9BASzfzmsCy0uCCb8eqI3SYYzVPDswWqSx7gjw==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -3424,7 +3130,7 @@ "node-gyp": "^8.4.1", "npmlog": "^5.0.0", "request": "^2.88.0", - "sass-graph": "4.0.0", + "sass-graph": "^4.0.1", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" }, @@ -3436,9 +3142,9 @@ } }, "node_modules/nodemon": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", - "integrity": "sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz", + "integrity": "sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -3448,10 +3154,10 @@ "minimatch": "^3.0.4", "pstree.remy": "^1.1.8", "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.5", - "update-notifier": "^5.1.0" + "undefsafe": "^2.0.5" }, "bin": { "nodemon": "bin/nodemon.js" @@ -3476,7 +3182,7 @@ "node_modules/nodemon/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "engines": { "node": ">=4" @@ -3545,27 +3251,12 @@ "node_modules/normalize-range": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/normalize-selector": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/normalize-selector/-/normalize-selector-0.2.0.tgz", - "integrity": "sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=", - "dev": true - }, - "node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/npm-run-all": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", @@ -3629,7 +3320,7 @@ "node_modules/npm-run-all/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "node_modules/npm-run-all/node_modules/cross-spawn": { @@ -3651,7 +3342,7 @@ "node_modules/npm-run-all/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "engines": { "node": ">=4" @@ -3660,7 +3351,7 @@ "node_modules/npm-run-all/node_modules/path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "dev": true, "engines": { "node": ">=4" @@ -3678,7 +3369,7 @@ "node_modules/npm-run-all/node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, "dependencies": { "shebang-regex": "^1.0.0" @@ -3690,7 +3381,7 @@ "node_modules/npm-run-all/node_modules/shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true, "engines": { "node": ">=0.10.0" @@ -3723,7 +3414,7 @@ "node_modules/npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", "dev": true, "dependencies": { "path-key": "^2.0.0" @@ -3735,7 +3426,7 @@ "node_modules/npm-run-path/node_modules/path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "dev": true, "engines": { "node": ">=4" @@ -3765,16 +3456,16 @@ "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3790,14 +3481,14 @@ } }, "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, "engines": { @@ -3819,25 +3510,16 @@ "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "dependencies": { "wrappy": "1" } }, - "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", "dev": true, "engines": { "node": ">=4" @@ -3894,54 +3576,6 @@ "node": ">=6" } }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json/node_modules/registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/package-json/node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3957,7 +3591,7 @@ "node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, "dependencies": { "error-ex": "^1.3.1", @@ -3979,7 +3613,7 @@ "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -3988,7 +3622,7 @@ "node_modules/path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", "dev": true }, "node_modules/path-key": { @@ -4024,7 +3658,7 @@ "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, "node_modules/picocolors": { @@ -4060,28 +3694,34 @@ "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/postcss": { - "version": "8.4.7", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.7.tgz", - "integrity": "sha512-L9Ye3r6hkkCeOETQX6iOaWZgjp3LL6Lpqm6EtgbKrgqGGteRMNb9vzBfRL96YOSu8o7x3MfIH9Mo5cPJFGrW6A==", + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], "dependencies": { - "nanoid": "^3.3.1", + "nanoid": "^3.3.4", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, "engines": { "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" } }, "node_modules/postcss-cli": { @@ -4126,12 +3766,12 @@ } }, "node_modules/postcss-load-config": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.3.tgz", - "integrity": "sha512-5EYgaM9auHGtO//ljHH+v/aC/TQ5LHXtL7bQajNAUBKUVKiYE8rYpFms7+V26D9FncaGe2zwCoPQsFKb5zF/Hw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", "dev": true, "dependencies": { - "lilconfig": "^2.0.4", + "lilconfig": "^2.0.5", "yaml": "^1.10.2" }, "engines": { @@ -4142,9 +3782,13 @@ "url": "https://opencollective.com/postcss/" }, "peerDependencies": { + "postcss": ">=8.0.9", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { + "postcss": { + "optional": true + }, "ts-node": { "optional": true } @@ -4153,7 +3797,7 @@ "node_modules/postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", "dev": true }, "node_modules/postcss-reporter": { @@ -4179,7 +3823,7 @@ "node_modules/postcss-resolve-nested-selector": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", - "integrity": "sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4=", + "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", "dev": true }, "node_modules/postcss-safe-parser": { @@ -4199,25 +3843,31 @@ } }, "node_modules/postcss-scss": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.3.tgz", - "integrity": "sha512-j4KxzWovfdHsyxwl1BxkUal/O4uirvHgdzMKS1aWJBAV0qh2qj5qAZqpeBfVUYGWv+4iK9Az7SPyZ4fyNju1uA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.5.tgz", + "integrity": "sha512-F7xpB6TrXyqUh3GKdyB4Gkp3QL3DDW1+uI+gxx/oJnUt/qXI4trj5OGlp9rOKdoABGULuqtqeG+3HEVQk4DjmA==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + } + ], "engines": { "node": ">=12.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, "peerDependencies": { "postcss": "^8.3.3" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz", - "integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==", + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -4242,19 +3892,10 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", "dev": true, "engines": { "node": ">= 0.8" @@ -4269,7 +3910,7 @@ "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "dev": true }, "node_modules/promise-retry": { @@ -4286,9 +3927,9 @@ } }, "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, "node_modules/pstree.remy": { @@ -4310,21 +3951,9 @@ "node_modules/punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true }, - "node_modules/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "dependencies": { - "escape-goat": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/purgecss": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-4.1.3.tgz", @@ -4381,7 +4010,7 @@ "node_modules/range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", "dev": true, "engines": { "node": ">= 0.6" @@ -4405,7 +4034,7 @@ "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "dev": true, "dependencies": { "pify": "^2.3.0" @@ -4414,7 +4043,7 @@ "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", "dev": true, "dependencies": { "load-json-file": "^4.0.0", @@ -4553,7 +4182,7 @@ "node_modules/read-pkg/node_modules/pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, "engines": { "node": ">=4" @@ -4607,6 +4236,23 @@ "node": ">=8" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/registry-auth-token": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", @@ -4620,7 +4266,7 @@ "node_modules/registry-url": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", "dev": true, "dependencies": { "rc": "^1.0.1" @@ -4664,7 +4310,7 @@ "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "engines": { "node": ">=0.10.0" @@ -4680,12 +4326,12 @@ } }, "node_modules/resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", "dev": true, "dependencies": { - "is-core-module": "^2.8.1", + "is-core-module": "^2.9.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -4705,19 +4351,10 @@ "node": ">=8" } }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, "engines": { "node": ">= 4" @@ -4798,14 +4435,14 @@ "dev": true }, "node_modules/sass-graph": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.0.tgz", - "integrity": "sha512-WSO/MfXqKH7/TS8RdkCX3lVkPFQzCgbqdGsmSKq6tlPU+GpGEsa/5aW18JqItnqh+lPtcjifqdZ/VmiILkKckQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.1.tgz", + "integrity": "sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==", "dev": true, "dependencies": { "glob": "^7.0.0", "lodash": "^4.17.11", - "scss-tokenizer": "^0.3.0", + "scss-tokenizer": "^0.4.3", "yargs": "^17.2.1" }, "bin": { @@ -4816,19 +4453,19 @@ } }, "node_modules/scss-tokenizer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.3.0.tgz", - "integrity": "sha512-14Zl9GcbBvOT9057ZKjpz5yPOyUWG2ojd9D5io28wHRYsOrs7U95Q+KNL87+32p8rc+LvDpbu/i9ZYjM9Q+FsQ==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", + "integrity": "sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==", "dev": true, "dependencies": { - "js-base64": "^2.4.3", - "source-map": "^0.7.1" + "js-base64": "^2.4.9", + "source-map": "^0.7.3" } }, "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4840,31 +4477,10 @@ "node": ">=10" } }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/semver-diff/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/serve": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.2.tgz", - "integrity": "sha512-71R6fKvNgKrqARAag6lYJNnxDzpH7DCNrMuvPY5PLVaC2PDhJsGTj/34o4o4tPWhTuLgEXqvgnAWbATQ9zGZTQ==", + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.4.tgz", + "integrity": "sha512-Lj8rhXmphJCRQVv5qwu0NQZ2h+0MrRyRJxDZu5y3qLH2i/XY6a0FPj/VmjMUdkJb672MBfE8hJ274PU6JzBd0Q==", "dev": true, "dependencies": { "@zeit/schemas": "2.6.0", @@ -4968,13 +4584,13 @@ "node_modules/serve/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "node_modules/serve/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "engines": { "node": ">=4" @@ -4995,7 +4611,7 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, "node_modules/shebang-command": { @@ -5039,11 +4655,32 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/simple-update-notifier": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", + "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } }, "node_modules/slash": { "version": "4.0.0", @@ -5085,12 +4722,12 @@ } }, "node_modules/socks": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", - "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz", + "integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==", "dev": true, "dependencies": { - "ip": "^1.1.5", + "ip": "^2.0.0", "smart-buffer": "^4.2.0" }, "engines": { @@ -5099,23 +4736,23 @@ } }, "node_modules/socks-proxy-agent": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", - "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", "dev": true, "dependencies": { "agent-base": "^6.0.2", - "debug": "^4.3.1", - "socks": "^2.6.1" + "debug": "^4.3.3", + "socks": "^2.6.2" }, "engines": { "node": ">= 10" } }, "node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "dev": true, "engines": { "node": ">= 8" @@ -5157,20 +4794,11 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", "dev": true }, - "node_modules/specificity": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", - "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", - "dev": true, - "bin": { - "specificity": "bin/specificity" - } - }, "node_modules/sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", @@ -5288,26 +4916,28 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5328,7 +4958,7 @@ "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "engines": { "node": ">=4" @@ -5337,7 +4967,7 @@ "node_modules/strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", "dev": true, "engines": { "node": ">=0.10.0" @@ -5358,7 +4988,7 @@ "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, "engines": { "node": ">=0.10.0" @@ -5367,48 +4997,45 @@ "node_modules/style-search": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", - "integrity": "sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", "dev": true }, "node_modules/stylelint": { - "version": "14.5.3", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.5.3.tgz", - "integrity": "sha512-omHETL+kGHR+fCXFK1SkZD/A+emCP9esggAdWEl8GPjTNeyRYj+H6uetRDcU+7E451zwWiUYGVAX+lApsAZgsQ==", + "version": "14.11.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.11.0.tgz", + "integrity": "sha512-OTLjLPxpvGtojEfpESWM8Ir64Z01E89xsisaBMUP/ngOx1+4VG2DPRcUyCCiin9Rd3kPXPsh/uwHd9eqnvhsYA==", "dev": true, "dependencies": { + "@csstools/selector-specificity": "^2.0.2", "balanced-match": "^2.0.0", - "colord": "^2.9.2", + "colord": "^2.9.3", "cosmiconfig": "^7.0.1", - "css-functions-list": "^3.0.1", - "debug": "^4.3.3", - "execall": "^2.0.0", + "css-functions-list": "^3.1.0", + "debug": "^4.3.4", "fast-glob": "^3.2.11", - "fastest-levenshtein": "^1.0.12", + "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^6.0.1", - "get-stdin": "^8.0.0", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", - "html-tags": "^3.1.0", + "html-tags": "^3.2.0", "ignore": "^5.2.0", "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.24.0", + "known-css-properties": "^0.25.0", "mathml-tag-names": "^2.1.3", "meow": "^9.0.0", - "micromatch": "^4.0.4", + "micromatch": "^4.0.5", "normalize-path": "^3.0.0", - "normalize-selector": "^0.2.0", "picocolors": "^1.0.0", - "postcss": "^8.4.6", + "postcss": "^8.4.16", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.9", + "postcss-selector-parser": "^6.0.10", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", - "specificity": "^0.4.1", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "style-search": "^0.1.0", @@ -5416,7 +5043,7 @@ "svg-tags": "^1.0.0", "table": "^6.8.0", "v8-compile-cache": "^2.3.0", - "write-file-atomic": "^4.0.1" + "write-file-atomic": "^4.0.2" }, "bin": { "stylelint": "bin/stylelint.js" @@ -5442,12 +5069,12 @@ } }, "node_modules/stylelint-config-recommended": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-6.0.0.tgz", - "integrity": "sha512-ZorSSdyMcxWpROYUvLEMm0vSZud2uB7tX1hzBZwvVY9SV/uly4AvvJPPhCcymZL3fcQhEQG5AELmrxWqtmzacw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-7.0.0.tgz", + "integrity": "sha512-yGn84Bf/q41J4luis1AZ95gj0EQwRX8lWmGmBwkwBNSkpGSpl66XcPTulxGa/Z91aPoNGuIGBmFkcM1MejMo9Q==", "dev": true, "peerDependencies": { - "stylelint": "^14.0.0" + "stylelint": "^14.4.0" } }, "node_modules/stylelint-config-recommended-scss": { @@ -5464,16 +5091,25 @@ "stylelint": "^14.0.0" } }, + "node_modules/stylelint-config-recommended-scss/node_modules/stylelint-config-recommended": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-6.0.0.tgz", + "integrity": "sha512-ZorSSdyMcxWpROYUvLEMm0vSZud2uB7tX1hzBZwvVY9SV/uly4AvvJPPhCcymZL3fcQhEQG5AELmrxWqtmzacw==", + "dev": true, + "peerDependencies": { + "stylelint": "^14.0.0" + } + }, "node_modules/stylelint-config-standard": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-24.0.0.tgz", - "integrity": "sha512-+RtU7fbNT+VlNbdXJvnjc3USNPZRiRVp/d2DxOF/vBDDTi0kH5RX2Ny6errdtZJH3boO+bmqIYEllEmok4jiuw==", + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-25.0.0.tgz", + "integrity": "sha512-21HnP3VSpaT1wFjFvv9VjvOGDtAviv47uTp3uFmzcN+3Lt+RYRv6oAplLaV51Kf792JSxJ6svCJh/G18E9VnCA==", "dev": true, "dependencies": { - "stylelint-config-recommended": "^6.0.0" + "stylelint-config-recommended": "^7.0.0" }, "peerDependencies": { - "stylelint": "^14.0.0" + "stylelint": "^14.4.0" } }, "node_modules/stylelint-config-standard-scss": { @@ -5489,22 +5125,43 @@ "stylelint": "^14.0.0" } }, + "node_modules/stylelint-config-standard-scss/node_modules/stylelint-config-recommended": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-6.0.0.tgz", + "integrity": "sha512-ZorSSdyMcxWpROYUvLEMm0vSZud2uB7tX1hzBZwvVY9SV/uly4AvvJPPhCcymZL3fcQhEQG5AELmrxWqtmzacw==", + "dev": true, + "peerDependencies": { + "stylelint": "^14.0.0" + } + }, + "node_modules/stylelint-config-standard-scss/node_modules/stylelint-config-standard": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-24.0.0.tgz", + "integrity": "sha512-+RtU7fbNT+VlNbdXJvnjc3USNPZRiRVp/d2DxOF/vBDDTi0kH5RX2Ny6errdtZJH3boO+bmqIYEllEmok4jiuw==", + "dev": true, + "dependencies": { + "stylelint-config-recommended": "^6.0.0" + }, + "peerDependencies": { + "stylelint": "^14.0.0" + } + }, "node_modules/stylelint-config-twbs-bootstrap": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/stylelint-config-twbs-bootstrap/-/stylelint-config-twbs-bootstrap-3.0.1.tgz", - "integrity": "sha512-XvFWKO0aveUy/RziVa5osYaOXAX6CePt8YypnRwKdrMtZWkgP85xnrkYBEpS2fmKl2WN79NCdSQFLU/UFoP7hw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/stylelint-config-twbs-bootstrap/-/stylelint-config-twbs-bootstrap-3.2.1.tgz", + "integrity": "sha512-lfVEx+tW2SHMTYoifXIdB086FgQdQarQGt1vDqkEiuGYEtKeGjf41hovotEijw4t1RSyWuhkfxF8GzUZajTXjQ==", "dev": true, "dependencies": { "stylelint-config-recess-order": "^3.0.0", - "stylelint-config-standard": "^24.0.0", + "stylelint-config-standard": "^25.0.0", "stylelint-config-standard-scss": "^3.0.0", - "stylelint-scss": "^4.1.0" + "stylelint-scss": "^4.2.0" }, "engines": { "node": ">=12" }, "peerDependencies": { - "stylelint": "^14.1.0" + "stylelint": "^14.4.0" } }, "node_modules/stylelint-order": { @@ -5521,9 +5178,9 @@ } }, "node_modules/stylelint-scss": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.1.0.tgz", - "integrity": "sha512-BNYTo7MMamhFOlcaAWp2dMpjg6hPyM/FFqfDIYzmYVLMmQJqc8lWRIiTqP4UX5bresj9Vo0dKC6odSh43VP2NA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.3.0.tgz", + "integrity": "sha512-GvSaKCA3tipzZHoz+nNO7S02ZqOsdBzMiCx9poSmLlb3tdJlGddEX/8QzCOD8O7GQan9bjsvLMsO5xiw6IhhIQ==", "dev": true, "dependencies": { "lodash": "^4.17.21", @@ -5533,7 +5190,7 @@ "postcss-value-parser": "^4.1.0" }, "peerDependencies": { - "stylelint": "^14.0.0" + "stylelint": "^14.5.1" } }, "node_modules/stylelint/node_modules/array-union": { @@ -5551,18 +5208,6 @@ "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", "dev": true }, - "node_modules/stylelint/node_modules/get-stdin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", - "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/stylelint/node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -5605,9 +5250,9 @@ } }, "node_modules/supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", "dev": true, "dependencies": { "has-flag": "^4.0.0", @@ -5632,7 +5277,7 @@ "node_modules/svg-tags": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", "dev": true }, "node_modules/table": { @@ -5652,9 +5297,9 @@ } }, "node_modules/table/node_modules/ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", @@ -5696,15 +5341,6 @@ "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==", "dev": true }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5732,7 +5368,7 @@ "node_modules/touch/node_modules/nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", "dev": true, "dependencies": { "abbrev": "1" @@ -5787,7 +5423,7 @@ "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, "dependencies": { "safe-buffer": "^5.0.1" @@ -5799,7 +5435,7 @@ "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "dev": true }, "node_modules/type-fest": { @@ -5814,24 +5450,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" }, "funding": { @@ -5862,18 +5489,6 @@ "imurmurhash": "^0.1.4" } }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -5883,6 +5498,32 @@ "node": ">= 10.0.0" } }, + "node_modules/update-browserslist-db": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", + "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/update-check": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.2.tgz", @@ -5893,43 +5534,6 @@ "registry-url": "3.1.0" } }, - "node_modules/update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dev": true, - "dependencies": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5948,22 +5552,10 @@ "node": ">=6" } }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, "node_modules/uuid": { @@ -5995,7 +5587,7 @@ "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "engines": { "node": ">= 0.8" @@ -6004,7 +5596,7 @@ "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "dev": true, "engines": [ "node >=0.6.0" @@ -6087,29 +5679,20 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, "node_modules/write-file-atomic": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.1.tgz", - "integrity": "sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" - } - }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true, - "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/y18n": { @@ -6137,9 +5720,9 @@ } }, "node_modules/yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", "dev": true, "dependencies": { "cliui": "^7.0.2", @@ -6164,9 +5747,9 @@ } }, "node_modules/yargs/node_modules/yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "engines": { "node": ">=12" @@ -6175,27 +5758,27 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", "dev": true, "requires": { - "@babel/highlight": "^7.16.7" + "@babel/highlight": "^7.18.6" } }, "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", "dev": true }, "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -6232,13 +5815,13 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, "supports-color": { @@ -6252,6 +5835,13 @@ } } }, + "@csstools/selector-specificity": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", + "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "dev": true, + "requires": {} + }, "@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -6305,24 +5895,9 @@ } }, "@popperjs/core": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz", - "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==" - }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" - } + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" }, "@tootallnate/once": { "version": "1.1.2", @@ -6483,7 +6058,7 @@ "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "dev": true }, "asn1": { @@ -6498,7 +6073,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true }, "astral-regex": { @@ -6510,24 +6085,24 @@ "async-foreach": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==", "dev": true }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, "autoprefixer": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", - "integrity": "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==", + "version": "10.4.11", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.11.tgz", + "integrity": "sha512-5lHp6DgRodxlBLSkzHOTcufWFflH1ewfy2hvFQyjrblBFlP/0Yh4O/Wrg4ow8WRlN3AAUFFLAQwX8hTptzqVHg==", "dev": true, "requires": { - "browserslist": "^4.19.1", - "caniuse-lite": "^1.0.30001297", - "fraction.js": "^4.1.2", + "browserslist": "^4.21.3", + "caniuse-lite": "^1.0.30001399", + "fraction.js": "^4.2.0", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -6536,7 +6111,7 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "dev": true }, "aws4": { @@ -6554,7 +6129,7 @@ "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, "requires": { "tweetnacl": "^0.14.3" @@ -6630,22 +6205,21 @@ } }, "browserslist": { - "version": "4.19.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.3.tgz", - "integrity": "sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==", + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001312", - "electron-to-chromium": "^1.4.71", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" } }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", "dev": true }, "cacache": { @@ -6674,38 +6248,6 @@ "unique-filename": "^1.1.1" } }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } - } - }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -6740,15 +6282,15 @@ } }, "caniuse-lite": { - "version": "1.0.30001312", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", - "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==", + "version": "1.0.30001400", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001400.tgz", + "integrity": "sha512-Mv659Hn65Z4LgZdJ7ge5JTVbE3rqbJaaXgW5LEI9/tOaXclfIZ8DW7D7FCWWWmWiiPS7AC48S8kf3DApSxQdgA==", "dev": true }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true }, "chalk": { @@ -6783,12 +6325,6 @@ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -6823,24 +6359,6 @@ "wrap-ansi": "^7.0.0" } }, - "clone-regexp": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", - "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", - "dev": true, - "requires": { - "is-regexp": "^2.0.0" - } - }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -6863,9 +6381,9 @@ "dev": true }, "colord": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz", - "integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", "dev": true }, "combined-stream": { @@ -6919,7 +6437,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, "safe-buffer": { @@ -6933,53 +6451,25 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - } - } - }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true }, "content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", "dev": true }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true }, "cosmiconfig": { @@ -7020,16 +6510,10 @@ "which": "^2.0.1" } }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, "css-functions-list": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.0.1.tgz", - "integrity": "sha512-PriDuifDt4u4rkDgnqRCLnjfMatufLmWNfQnGCq34xZwpY3oabwhB9SqRBmuvWUgndbemCFlKqg+nO7C2q0SBw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", + "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", "dev": true }, "cssesc": { @@ -7041,33 +6525,33 @@ "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, "requires": { "assert-plus": "^1.0.0" } }, "datatables.net": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.11.5.tgz", - "integrity": "sha512-nlFst2xfwSWaQgaOg5sXVG3cxYC0tH8E8d65289w9ROgF2TmLULOOpcdMpyxxUim/qEwVSEem42RjkTWEpr3eA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.12.1.tgz", + "integrity": "sha512-e6XAMUoV41JdQPS/r9YRfRcmTPcCVvyZbWI+xog1Zg+kjVliMQbEkvWK5XFItmi64Cvwg+IqsZbTUJ1KSY3umA==", "requires": { "jquery": ">=1.7" } }, "datatables.net-bs5": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-1.11.5.tgz", - "integrity": "sha512-1zyh972GtuK1uAb9h8nP3jJ7f/3UgCDq69LAaZS2bVd4mEHECJ6vrZLacxrkOHOs/q/H3v5sEMeZ46vXz8ox4w==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-1.12.1.tgz", + "integrity": "sha512-CcQCImfmH4YZk7I0aC0kTiNPyfHJ2ueGgOh/kFB9KqsZD8bNJy2A88gC6hn9A7TbmmenOL+K3Q1ti7G8yqi8SQ==", "requires": { "datatables.net": ">=1.11.3", "jquery": ">=1.7" } }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -7076,13 +6560,13 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true }, "decamelize-keys": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "integrity": "sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==", "dev": true, "requires": { "decamelize": "^1.1.0", @@ -7092,57 +6576,43 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", "dev": true } } }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", "dev": true, "requires": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "dev": true }, "dependency-graph": { @@ -7160,25 +6630,10 @@ "path-type": "^4.0.0" } }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "requires": { - "is-obj": "^2.0.0" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, "requires": { "jsbn": "~0.1.0", @@ -7186,9 +6641,9 @@ } }, "electron-to-chromium": { - "version": "1.4.75", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz", - "integrity": "sha512-LxgUNeu3BVU7sXaKjUDD9xivocQLxFtq6wgERrutdY/yIOps3ODOZExK1jg8DTEg4U8TUCb5MLGeWFOYuxjF3Q==", + "version": "1.4.251", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.251.tgz", + "integrity": "sha512-k4o4cFrWPv4SoJGGAydd07GmlRVzmeDIJ6MaEChTUjk4Dmomn189tCicSzil2oyvbPoGgg2suwPDNWq4gWRhoQ==", "dev": true }, "emoji-regex": { @@ -7238,31 +6693,34 @@ } }, "es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.2.tgz", + "integrity": "sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ==", "dev": true, "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.2", "get-symbol-description": "^1.0.0", "has": "^1.0.3", - "has-symbols": "^1.0.2", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", + "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" } }, "es-to-primitive": { @@ -7282,16 +6740,10 @@ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true - }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, "execa": { @@ -7325,7 +6777,7 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "dev": true }, "semver": { @@ -7337,7 +6789,7 @@ "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -7346,7 +6798,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true }, "which": { @@ -7360,15 +6812,6 @@ } } }, - "execall": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", - "integrity": "sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==", - "dev": true, - "requires": { - "clone-regexp": "^2.1.0" - } - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7378,7 +6821,7 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "dev": true }, "fast-deep-equal": { @@ -7388,9 +6831,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -7409,16 +6852,16 @@ "fast-url-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=", + "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", "dev": true, "requires": { "punycode": "^1.3.2" } }, "fastest-levenshtein": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true }, "fastq": { @@ -7469,15 +6912,15 @@ } }, "flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "dev": true }, "form-data": { @@ -7492,15 +6935,15 @@ } }, "fraction.js": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.3.tgz", - "integrity": "sha512-pUHWWt6vHzZZiQJcM6S/0PXfS+g6FM4BF5rj9wZyreivhQPdsh5PpE25VtSNxq80wHS5RfY51Ii+8Z0Zl/pmzg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", "dev": true }, "fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "requires": { "graceful-fs": "^4.2.0", @@ -7520,7 +6963,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, "fsevents": { @@ -7536,6 +6979,24 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, "gauge": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", @@ -7569,20 +7030,20 @@ "dev": true }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.3" } }, "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", "dev": true }, "get-stream": { @@ -7607,50 +7068,33 @@ "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, "requires": { "assert-plus": "^1.0.0" } }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { - "ini": "2.0.0" - }, - "dependencies": { - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true - } + "is-glob": "^4.0.1" } }, "global-modules": { @@ -7701,17 +7145,17 @@ "globjoin": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", - "integrity": "sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", "dev": true }, "globule": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.3.tgz", - "integrity": "sha512-mb1aYtDbIjTu4ShMB85m3UzjX9BVKe9WCzsnfMSZk+K5GpIbBOexgg4PPCt5eHDEG5/ZQAUX2Kct02zfiPLsKg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", + "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", "dev": true, "requires": { "glob": "~7.1.1", - "lodash": "~4.17.10", + "lodash": "^4.17.21", "minimatch": "~3.0.2" }, "dependencies": { @@ -7740,35 +7184,16 @@ } } }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, "graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", "dev": true }, "har-validator": { @@ -7797,9 +7222,9 @@ } }, "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true }, "has-flag": { @@ -7808,6 +7233,15 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -7826,13 +7260,7 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true - }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true }, "hosted-git-info": { @@ -7845,9 +7273,9 @@ } }, "html-tags": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", - "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", + "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", "dev": true }, "http-cache-semantics": { @@ -7870,7 +7298,7 @@ "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "dev": true, "requires": { "assert-plus": "^1.0.0", @@ -7879,9 +7307,9 @@ } }, "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "requires": { "agent-base": "6", @@ -7891,7 +7319,7 @@ "humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "dev": true, "requires": { "ms": "^2.0.0" @@ -7916,7 +7344,7 @@ "ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, "import-fresh": { @@ -7946,7 +7374,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, "indent-string": { @@ -7964,7 +7392,7 @@ "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "requires": { "once": "^1.3.0", @@ -7995,15 +7423,15 @@ } }, "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, "is-bigint": { @@ -8035,24 +7463,15 @@ } }, "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.6.tgz", + "integrity": "sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q==", "dev": true }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, "is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", "dev": true, "requires": { "has": "^1.0.3" @@ -8076,7 +7495,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, "is-fullwidth-code-point": { @@ -8094,20 +7513,10 @@ "is-extglob": "^2.1.1" } }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - } - }, "is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "dev": true }, "is-negative-zero": { @@ -8116,12 +7525,6 @@ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true }, - "is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -8129,30 +7532,18 @@ "dev": true }, "is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, "requires": { "has-tostringtag": "^1.0.0" } }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true }, "is-plain-object": { @@ -8171,22 +7562,19 @@ "has-tostringtag": "^1.0.0" } }, - "is-regexp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", - "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", - "dev": true - }, "is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "dev": true }, "is-string": { @@ -8210,7 +7598,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, "is-weakref": { @@ -8231,34 +7619,28 @@ "is-docker": "^2.0.0" } }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, "jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.1.tgz", + "integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" }, "js-base64": { "version": "2.6.4", @@ -8275,13 +7657,7 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true }, "json-parse-better-errors": { @@ -8311,7 +7687,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true }, "jsonfile": { @@ -8336,15 +7712,6 @@ "verror": "1.10.0" } }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -8352,24 +7719,15 @@ "dev": true }, "known-css-properties": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.24.0.tgz", - "integrity": "sha512-RTSoaUAfLvpR357vWzAz/50Q/BmHfmE6ETSWfutT0AJiw10e6CmcdYRQJlLRd95B53D0Y2aD1jSxD3V3ySF+PA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.25.0.tgz", + "integrity": "sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA==", "dev": true }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "requires": { - "package-json": "^6.3.0" - } - }, "lilconfig": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", "dev": true }, "lines-and-columns": { @@ -8381,7 +7739,7 @@ "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -8393,7 +7751,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true } } @@ -8416,13 +7774,7 @@ "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true }, "lru-cache": { @@ -8434,23 +7786,6 @@ "yallist": "^4.0.0" } }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, "make-fetch-happen": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", @@ -8490,7 +7825,7 @@ "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", "dev": true }, "meow": { @@ -8520,36 +7855,30 @@ "dev": true }, "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, "mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true }, "mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "requires": { - "mime-db": "1.51.0" + "mime-db": "1.52.0" } }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true - }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -8583,9 +7912,9 @@ } }, "minipass": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", - "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", "dev": true, "requires": { "yallist": "^4.0.0" @@ -8662,15 +7991,15 @@ "dev": true }, "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", + "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", "dev": true }, "nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true }, "negotiator": { @@ -8704,9 +8033,9 @@ }, "dependencies": { "are-we-there-yet": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz", - "integrity": "sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", "dev": true, "requires": { "delegates": "^1.0.0", @@ -8714,12 +8043,11 @@ } }, "gauge": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.2.tgz", - "integrity": "sha512-aSPRm2CvA9R8QyU5eXMFPd+cYkyxLsXHd2l5/FOH2V/eml//M04G6KZOmTap07O1PvEwNcl2NndyLfK8g3QrKA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", "dev": true, "requires": { - "ansi-regex": "^5.0.1", "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", @@ -8731,29 +8059,29 @@ } }, "npmlog": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.1.tgz", - "integrity": "sha512-BTHDvY6nrRHuRfyjt1MAufLxYdVXZfd099H4+i1f0lPywNQyI4foeNXJRObB/uy+TYqUW0vAD9gbdSOXPst7Eg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", "dev": true, "requires": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", - "gauge": "^4.0.0", + "gauge": "^4.0.3", "set-blocking": "^2.0.0" } } } }, "node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", "dev": true }, "node-sass": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-7.0.1.tgz", - "integrity": "sha512-uMy+Xt29NlqKCFdFRZyXKOTqGt+QaKHexv9STj2WeLottnlqZEEWx6Bj0MXNthmFRRdM/YwyNo/8Tr46TOM0jQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-7.0.3.tgz", + "integrity": "sha512-8MIlsY/4dXUkJDYht9pIWBhMil3uHmE8b/AdJPjmFn1nBx9X9BASzfzmsCy0uCCb8eqI3SYYzVPDswWqSx7gjw==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -8768,15 +8096,15 @@ "node-gyp": "^8.4.1", "npmlog": "^5.0.0", "request": "^2.88.0", - "sass-graph": "4.0.0", + "sass-graph": "^4.0.1", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" } }, "nodemon": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", - "integrity": "sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz", + "integrity": "sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A==", "dev": true, "requires": { "chokidar": "^3.5.2", @@ -8785,10 +8113,10 @@ "minimatch": "^3.0.4", "pstree.remy": "^1.1.8", "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.5", - "update-notifier": "^5.1.0" + "undefsafe": "^2.0.5" }, "dependencies": { "debug": { @@ -8803,7 +8131,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, "semver": { @@ -8853,19 +8181,7 @@ "normalize-range": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true - }, - "normalize-selector": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/normalize-selector/-/normalize-selector-0.2.0.tgz", - "integrity": "sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=", - "dev": true - }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true }, "npm-run-all": { @@ -8917,7 +8233,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "cross-spawn": { @@ -8936,13 +8252,13 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "dev": true }, "semver": { @@ -8954,7 +8270,7 @@ "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -8963,7 +8279,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true }, "supports-color": { @@ -8989,7 +8305,7 @@ "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", "dev": true, "requires": { "path-key": "^2.0.0" @@ -8998,7 +8314,7 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "dev": true } } @@ -9024,13 +8340,13 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true }, "object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", "dev": true }, "object-keys": { @@ -9040,14 +8356,14 @@ "dev": true }, "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", "dev": true, "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" } }, @@ -9060,22 +8376,16 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "requires": { "wrappy": "1" } }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", "dev": true }, "p-limit": { @@ -9111,44 +8421,6 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "dependencies": { - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9161,7 +8433,7 @@ "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, "requires": { "error-ex": "^1.3.1", @@ -9177,13 +8449,13 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true }, "path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", "dev": true }, "path-key": { @@ -9213,7 +8485,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, "picocolors": { @@ -9237,16 +8509,16 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true }, "postcss": { - "version": "8.4.7", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.7.tgz", - "integrity": "sha512-L9Ye3r6hkkCeOETQX6iOaWZgjp3LL6Lpqm6EtgbKrgqGGteRMNb9vzBfRL96YOSu8o7x3MfIH9Mo5cPJFGrW6A==", + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", "dev": true, "requires": { - "nanoid": "^3.3.1", + "nanoid": "^3.3.4", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } @@ -9280,19 +8552,19 @@ } }, "postcss-load-config": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.3.tgz", - "integrity": "sha512-5EYgaM9auHGtO//ljHH+v/aC/TQ5LHXtL7bQajNAUBKUVKiYE8rYpFms7+V26D9FncaGe2zwCoPQsFKb5zF/Hw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", "dev": true, "requires": { - "lilconfig": "^2.0.4", + "lilconfig": "^2.0.5", "yaml": "^1.10.2" } }, "postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", "dev": true }, "postcss-reporter": { @@ -9308,7 +8580,7 @@ "postcss-resolve-nested-selector": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", - "integrity": "sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4=", + "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", "dev": true }, "postcss-safe-parser": { @@ -9319,16 +8591,16 @@ "requires": {} }, "postcss-scss": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.3.tgz", - "integrity": "sha512-j4KxzWovfdHsyxwl1BxkUal/O4uirvHgdzMKS1aWJBAV0qh2qj5qAZqpeBfVUYGWv+4iK9Az7SPyZ4fyNju1uA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.5.tgz", + "integrity": "sha512-F7xpB6TrXyqUh3GKdyB4Gkp3QL3DDW1+uI+gxx/oJnUt/qXI4trj5OGlp9rOKdoABGULuqtqeG+3HEVQk4DjmA==", "dev": true, "requires": {} }, "postcss-selector-parser": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz", - "integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==", + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", "dev": true, "requires": { "cssesc": "^3.0.0", @@ -9348,16 +8620,10 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", "dev": true }, "process-nextick-args": { @@ -9369,7 +8635,7 @@ "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "dev": true }, "promise-retry": { @@ -9383,9 +8649,9 @@ } }, "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, "pstree.remy": { @@ -9407,18 +8673,9 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true }, - "pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "requires": { - "escape-goat": "^2.0.0" - } - }, "purgecss": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-4.1.3.tgz", @@ -9452,7 +8709,7 @@ "range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", "dev": true }, "rc": { @@ -9470,7 +8727,7 @@ "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "dev": true, "requires": { "pify": "^2.3.0" @@ -9479,7 +8736,7 @@ "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", "dev": true, "requires": { "load-json-file": "^4.0.0", @@ -9517,7 +8774,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true }, "semver": { @@ -9633,6 +8890,17 @@ "strip-indent": "^3.0.0" } }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, "registry-auth-token": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", @@ -9646,7 +8914,7 @@ "registry-url": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", "dev": true, "requires": { "rc": "^1.0.1" @@ -9683,7 +8951,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, "require-from-string": { @@ -9693,12 +8961,12 @@ "dev": true }, "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", "dev": true, "requires": { - "is-core-module": "^2.8.1", + "is-core-module": "^2.9.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -9709,19 +8977,10 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, "retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true }, "reusify": { @@ -9761,57 +9020,40 @@ "dev": true }, "sass-graph": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.0.tgz", - "integrity": "sha512-WSO/MfXqKH7/TS8RdkCX3lVkPFQzCgbqdGsmSKq6tlPU+GpGEsa/5aW18JqItnqh+lPtcjifqdZ/VmiILkKckQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.1.tgz", + "integrity": "sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==", "dev": true, "requires": { "glob": "^7.0.0", "lodash": "^4.17.11", - "scss-tokenizer": "^0.3.0", + "scss-tokenizer": "^0.4.3", "yargs": "^17.2.1" } }, "scss-tokenizer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.3.0.tgz", - "integrity": "sha512-14Zl9GcbBvOT9057ZKjpz5yPOyUWG2ojd9D5io28wHRYsOrs7U95Q+KNL87+32p8rc+LvDpbu/i9ZYjM9Q+FsQ==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", + "integrity": "sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==", "dev": true, "requires": { - "js-base64": "^2.4.3", - "source-map": "^0.7.1" + "js-base64": "^2.4.9", + "source-map": "^0.7.3" } }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dev": true, "requires": { "lru-cache": "^6.0.0" } }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "requires": { - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, "serve": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.2.tgz", - "integrity": "sha512-71R6fKvNgKrqARAag6lYJNnxDzpH7DCNrMuvPY5PLVaC2PDhJsGTj/34o4o4tPWhTuLgEXqvgnAWbATQ9zGZTQ==", + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.4.tgz", + "integrity": "sha512-Lj8rhXmphJCRQVv5qwu0NQZ2h+0MrRyRJxDZu5y3qLH2i/XY6a0FPj/VmjMUdkJb672MBfE8hJ274PU6JzBd0Q==", "dev": true, "requires": { "@zeit/schemas": "2.6.0", @@ -9857,13 +9099,13 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, "supports-color": { @@ -9922,7 +9164,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, "shebang-command": { @@ -9963,6 +9205,23 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "simple-update-notifier": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", + "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "dev": true, + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, "slash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", @@ -9987,30 +9246,30 @@ "dev": true }, "socks": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", - "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz", + "integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==", "dev": true, "requires": { - "ip": "^1.1.5", + "ip": "^2.0.0", "smart-buffer": "^4.2.0" } }, "socks-proxy-agent": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", - "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", "dev": true, "requires": { "agent-base": "^6.0.2", - "debug": "^4.3.1", - "socks": "^2.6.1" + "debug": "^4.3.3", + "socks": "^2.6.2" } }, "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "dev": true }, "source-map-js": { @@ -10046,15 +9305,9 @@ } }, "spdx-license-ids": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", - "dev": true - }, - "specificity": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", - "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", "dev": true }, "sshpk": { @@ -10156,23 +9409,25 @@ } }, "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "strip-ansi": { @@ -10187,13 +9442,13 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", "dev": true }, "strip-indent": { @@ -10208,54 +9463,51 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true }, "style-search": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", - "integrity": "sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", "dev": true }, "stylelint": { - "version": "14.5.3", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.5.3.tgz", - "integrity": "sha512-omHETL+kGHR+fCXFK1SkZD/A+emCP9esggAdWEl8GPjTNeyRYj+H6uetRDcU+7E451zwWiUYGVAX+lApsAZgsQ==", + "version": "14.11.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.11.0.tgz", + "integrity": "sha512-OTLjLPxpvGtojEfpESWM8Ir64Z01E89xsisaBMUP/ngOx1+4VG2DPRcUyCCiin9Rd3kPXPsh/uwHd9eqnvhsYA==", "dev": true, "requires": { + "@csstools/selector-specificity": "^2.0.2", "balanced-match": "^2.0.0", - "colord": "^2.9.2", + "colord": "^2.9.3", "cosmiconfig": "^7.0.1", - "css-functions-list": "^3.0.1", - "debug": "^4.3.3", - "execall": "^2.0.0", + "css-functions-list": "^3.1.0", + "debug": "^4.3.4", "fast-glob": "^3.2.11", - "fastest-levenshtein": "^1.0.12", + "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^6.0.1", - "get-stdin": "^8.0.0", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", - "html-tags": "^3.1.0", + "html-tags": "^3.2.0", "ignore": "^5.2.0", "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.24.0", + "known-css-properties": "^0.25.0", "mathml-tag-names": "^2.1.3", "meow": "^9.0.0", - "micromatch": "^4.0.4", + "micromatch": "^4.0.5", "normalize-path": "^3.0.0", - "normalize-selector": "^0.2.0", "picocolors": "^1.0.0", - "postcss": "^8.4.6", + "postcss": "^8.4.16", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.9", + "postcss-selector-parser": "^6.0.10", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", - "specificity": "^0.4.1", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "style-search": "^0.1.0", @@ -10263,7 +9515,7 @@ "svg-tags": "^1.0.0", "table": "^6.8.0", "v8-compile-cache": "^2.3.0", - "write-file-atomic": "^4.0.1" + "write-file-atomic": "^4.0.2" }, "dependencies": { "array-union": { @@ -10278,12 +9530,6 @@ "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", "dev": true }, - "get-stdin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", - "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", - "dev": true - }, "globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -10316,9 +9562,9 @@ } }, "stylelint-config-recommended": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-6.0.0.tgz", - "integrity": "sha512-ZorSSdyMcxWpROYUvLEMm0vSZud2uB7tX1hzBZwvVY9SV/uly4AvvJPPhCcymZL3fcQhEQG5AELmrxWqtmzacw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-7.0.0.tgz", + "integrity": "sha512-yGn84Bf/q41J4luis1AZ95gj0EQwRX8lWmGmBwkwBNSkpGSpl66XcPTulxGa/Z91aPoNGuIGBmFkcM1MejMo9Q==", "dev": true, "requires": {} }, @@ -10331,15 +9577,24 @@ "postcss-scss": "^4.0.2", "stylelint-config-recommended": "^6.0.0", "stylelint-scss": "^4.0.0" + }, + "dependencies": { + "stylelint-config-recommended": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-6.0.0.tgz", + "integrity": "sha512-ZorSSdyMcxWpROYUvLEMm0vSZud2uB7tX1hzBZwvVY9SV/uly4AvvJPPhCcymZL3fcQhEQG5AELmrxWqtmzacw==", + "dev": true, + "requires": {} + } } }, "stylelint-config-standard": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-24.0.0.tgz", - "integrity": "sha512-+RtU7fbNT+VlNbdXJvnjc3USNPZRiRVp/d2DxOF/vBDDTi0kH5RX2Ny6errdtZJH3boO+bmqIYEllEmok4jiuw==", + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-25.0.0.tgz", + "integrity": "sha512-21HnP3VSpaT1wFjFvv9VjvOGDtAviv47uTp3uFmzcN+3Lt+RYRv6oAplLaV51Kf792JSxJ6svCJh/G18E9VnCA==", "dev": true, "requires": { - "stylelint-config-recommended": "^6.0.0" + "stylelint-config-recommended": "^7.0.0" } }, "stylelint-config-standard-scss": { @@ -10350,18 +9605,36 @@ "requires": { "stylelint-config-recommended-scss": "^5.0.2", "stylelint-config-standard": "^24.0.0" + }, + "dependencies": { + "stylelint-config-recommended": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-6.0.0.tgz", + "integrity": "sha512-ZorSSdyMcxWpROYUvLEMm0vSZud2uB7tX1hzBZwvVY9SV/uly4AvvJPPhCcymZL3fcQhEQG5AELmrxWqtmzacw==", + "dev": true, + "requires": {} + }, + "stylelint-config-standard": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-24.0.0.tgz", + "integrity": "sha512-+RtU7fbNT+VlNbdXJvnjc3USNPZRiRVp/d2DxOF/vBDDTi0kH5RX2Ny6errdtZJH3boO+bmqIYEllEmok4jiuw==", + "dev": true, + "requires": { + "stylelint-config-recommended": "^6.0.0" + } + } } }, "stylelint-config-twbs-bootstrap": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/stylelint-config-twbs-bootstrap/-/stylelint-config-twbs-bootstrap-3.0.1.tgz", - "integrity": "sha512-XvFWKO0aveUy/RziVa5osYaOXAX6CePt8YypnRwKdrMtZWkgP85xnrkYBEpS2fmKl2WN79NCdSQFLU/UFoP7hw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/stylelint-config-twbs-bootstrap/-/stylelint-config-twbs-bootstrap-3.2.1.tgz", + "integrity": "sha512-lfVEx+tW2SHMTYoifXIdB086FgQdQarQGt1vDqkEiuGYEtKeGjf41hovotEijw4t1RSyWuhkfxF8GzUZajTXjQ==", "dev": true, "requires": { "stylelint-config-recess-order": "^3.0.0", - "stylelint-config-standard": "^24.0.0", + "stylelint-config-standard": "^25.0.0", "stylelint-config-standard-scss": "^3.0.0", - "stylelint-scss": "^4.1.0" + "stylelint-scss": "^4.2.0" } }, "stylelint-order": { @@ -10375,9 +9648,9 @@ } }, "stylelint-scss": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.1.0.tgz", - "integrity": "sha512-BNYTo7MMamhFOlcaAWp2dMpjg6hPyM/FFqfDIYzmYVLMmQJqc8lWRIiTqP4UX5bresj9Vo0dKC6odSh43VP2NA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.3.0.tgz", + "integrity": "sha512-GvSaKCA3tipzZHoz+nNO7S02ZqOsdBzMiCx9poSmLlb3tdJlGddEX/8QzCOD8O7GQan9bjsvLMsO5xiw6IhhIQ==", "dev": true, "requires": { "lodash": "^4.17.21", @@ -10397,9 +9670,9 @@ } }, "supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", "dev": true, "requires": { "has-flag": "^4.0.0", @@ -10415,7 +9688,7 @@ "svg-tags": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", "dev": true }, "table": { @@ -10432,9 +9705,9 @@ }, "dependencies": { "ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -10471,12 +9744,6 @@ "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==", "dev": true }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -10498,7 +9765,7 @@ "nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", "dev": true, "requires": { "abbrev": "1" @@ -10542,7 +9809,7 @@ "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, "requires": { "safe-buffer": "^5.0.1" @@ -10551,7 +9818,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "dev": true }, "type-fest": { @@ -10560,24 +9827,15 @@ "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", "dev": true }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" } }, @@ -10605,21 +9863,22 @@ "imurmurhash": "^0.1.4" } }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, + "update-browserslist-db": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", + "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, "update-check": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.2.tgz", @@ -10630,36 +9889,6 @@ "registry-url": "3.1.0" } }, - "update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dev": true, - "requires": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - } - } - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -10677,19 +9906,10 @@ } } }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, "uuid": { @@ -10717,13 +9937,13 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "dev": true, "requires": { "assert-plus": "^1.0.0", @@ -10785,25 +10005,19 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, "write-file-atomic": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.1.tgz", - "integrity": "sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "requires": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -10823,9 +10037,9 @@ "dev": true }, "yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", "dev": true, "requires": { "cliui": "^7.0.2", @@ -10838,9 +10052,9 @@ }, "dependencies": { "yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true } } From 1772fa894a69e60cda99f657ea8442ab3a7bd08c Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 12:52:21 +0200 Subject: [PATCH 045/111] change schedule --- .github/workflows/trivy.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index c7289ff94..ab1ac72b9 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,7 +1,7 @@ name: trivy on: schedule: - - cron: "*/15 * * * *" + - cron: "0 7 * * *" jobs: scan: permissions: @@ -12,6 +12,7 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 + - name: Ensure lowercase name run: echo REPOSITORY_OWNER=$(echo ${{ github.repository_owner }} | tr "[:upper:]" "[:lower:]") >> $GITHUB_ENV From dc92f38e2b8833ca2e73993017e1791a0a3aa52b Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 12:52:50 +0200 Subject: [PATCH 046/111] remove type --- .github/workflows/publish_and_trivyscan.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/publish_and_trivyscan.yml b/.github/workflows/publish_and_trivyscan.yml index 2f80d219a..f3d4249a3 100644 --- a/.github/workflows/publish_and_trivyscan.yml +++ b/.github/workflows/publish_and_trivyscan.yml @@ -2,7 +2,6 @@ name: Publish Docker Image and run Trivy Security Scan on: pull_request: - types: [opened, edited, reopened] push: branches: - master From 5a1bb953734f26a527a6748ed4b8e74943634c77 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 12:57:32 +0200 Subject: [PATCH 047/111] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3126a45bc..a5efba1fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -142,3 +142,4 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - Check for if project busy before status change ([#1266](https://github.com/ScilifelabDataCentre/dds_web/pull/1266)) - Bug fix: Default timestamps fixed ([#1271](https://github.com/ScilifelabDataCentre/dds_web/pull/1271)) - Change docker image to alpine ([#1272](https://github.com/ScilifelabDataCentre/dds_web/pull/1272)) +- Added trivy when publishing to dockerhub ([#1276](https://github.com/ScilifelabDataCentre/dds_web/pull/1276)) From fcb3c19a3083a3718ea8cb388205915de3e237f9 Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Thu, 15 Sep 2022 13:48:57 +0200 Subject: [PATCH 048/111] add changelog entry and a test --- CHANGELOG.md | 4 ++++ tests/api/test_project.py | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3126a45bc..619cae0ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -141,4 +141,8 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - New endpoint for setting project as busy / not busy ([#1266](https://github.com/ScilifelabDataCentre/dds_web/pull/1266)) - Check for if project busy before status change ([#1266](https://github.com/ScilifelabDataCentre/dds_web/pull/1266)) - Bug fix: Default timestamps fixed ([#1271](https://github.com/ScilifelabDataCentre/dds_web/pull/1271)) +<<<<<<< HEAD - Change docker image to alpine ([#1272](https://github.com/ScilifelabDataCentre/dds_web/pull/1272)) +======= +- Bug fix: Cost value displayed by the --usage flag fixed ([#1274](https://github.com/ScilifelabDataCentre/dds_web/pull/1274)) +>>>>>>> 3ae1a84e (add changelog entry and a test) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index af38c1b9b..c6c6e77c8 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -22,6 +22,7 @@ from tests.test_files_new import project_row, file_in_db, FIRST_NEW_FILE from tests.test_project_creation import proj_data_with_existing_users, create_unit_admins from dds_web.database import models +from dds_web.api.project import UserProjects # CONFIG ################################################################################## CONFIG # @@ -1506,3 +1507,22 @@ def test_set_busy_project_already_busy(module_client): ) assert response.status_code == http.HTTPStatus.OK assert "The project is already busy, cannot proceed." in response.json.get("message") + + +# Project usage + + +def test_project_usage(module_client): + """Test if correct cost value is returned.""" + + cost_gbhour = 0.09 / (30 * 24) + + # Get user and project + user = models.User.query.filter_by(username="unitadmin").one_or_none() + assert user + project_0 = user.projects[0] + assert project_0 + + # Call project_usage() for the project and check if cost is calculated correctly + proj_bhours, proj_cost = UserProjects.project_usage(project=project_0) + assert (proj_bhours / 1e9) * cost_gbhour == proj_cost From 26b8c2f34fb2000037b54fe6adff2dbe2995cbc8 Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Thu, 15 Sep 2022 13:56:08 +0200 Subject: [PATCH 049/111] fix conflicts --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 619cae0ab..f0978484c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -141,8 +141,5 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - New endpoint for setting project as busy / not busy ([#1266](https://github.com/ScilifelabDataCentre/dds_web/pull/1266)) - Check for if project busy before status change ([#1266](https://github.com/ScilifelabDataCentre/dds_web/pull/1266)) - Bug fix: Default timestamps fixed ([#1271](https://github.com/ScilifelabDataCentre/dds_web/pull/1271)) -<<<<<<< HEAD - Change docker image to alpine ([#1272](https://github.com/ScilifelabDataCentre/dds_web/pull/1272)) -======= - Bug fix: Cost value displayed by the --usage flag fixed ([#1274](https://github.com/ScilifelabDataCentre/dds_web/pull/1274)) ->>>>>>> 3ae1a84e (add changelog entry and a test) From acd8924ebd800e488a99d77abbf566ac5eb191a9 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 14:20:08 +0200 Subject: [PATCH 050/111] also run trivy at 17 --- .github/workflows/trivy.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index ab1ac72b9..dc5c67138 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,7 +1,8 @@ name: trivy on: schedule: - - cron: "0 7 * * *" + - cron: "0 7,17 * * *" + # - cron: "0 7 * * *" jobs: scan: permissions: From 3abeda798bfb86a1cb99b30908026790dce84b2d Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 14:57:09 +0200 Subject: [PATCH 051/111] change time --- .github/workflows/trivy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index dc5c67138..da3d9d226 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,7 +1,7 @@ name: trivy on: schedule: - - cron: "0 7,17 * * *" + - cron: "5 7,15 * * *" # - cron: "0 7 * * *" jobs: scan: From 6cebdeac8d81bfab9784148377c5c822e3ed9e7c Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 15:11:14 +0200 Subject: [PATCH 052/111] run cron every 5 min --- .github/workflows/trivy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index da3d9d226..0d40578c2 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,7 +1,7 @@ name: trivy on: schedule: - - cron: "5 7,15 * * *" + - cron: "*/5 * * * *" # - cron: "0 7 * * *" jobs: scan: From 738b84d1a0ecd411cce4cc4ec790421176fc4be1 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 15:21:33 +0200 Subject: [PATCH 053/111] intr and description --- .github/pull_request_template.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 376bf7215..7a0d375e6 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,12 +1,15 @@ +> **Before submitting the PR, please go through the seconds below and fill in what you can. If there are any items that are irrelevant for the current PR, remove the row. If a relevant option is missing, please add it as an item and add a PR comment informing that the new option should be included into this template.** + # Description -Please include the following in this section +In this section: -- [ ] Summary of the changes and the related issue -- [ ] Relevant motivation and context -- [ ] Any dependencies that are required for this change +- [ ] Add a summary of the changes and the related issue +- [ ] Add motivation and context regarding why the change is needed +- [ ] List / describe any dependencies or other changes required for this change +- [ ] If this PR solves an issue, link it (if in GitHub) or add the issue ID (if in Jira). Do this by filling in `[issue link or ID]` below. -Fixes # (issue) +Fixes [issue link or ID] ## Type of change From e3186acf8f796882ce7355e572df300f5de50c83 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 15:48:52 +0200 Subject: [PATCH 054/111] checklist updated --- .github/pull_request_template.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7a0d375e6..21e154485 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,31 +2,31 @@ # Description -In this section: - - [ ] Add a summary of the changes and the related issue - [ ] Add motivation and context regarding why the change is needed - [ ] List / describe any dependencies or other changes required for this change -- [ ] If this PR solves an issue, link it (if in GitHub) or add the issue ID (if in Jira). Do this by filling in `[issue link or ID]` below. - -Fixes [issue link or ID] +- [ ] Fixes [link to issue / Jira issue ID] ## Type of change -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] This change requires a documentation update +- [ ] Documentation +- [ ] Workflow +- [ ] Security Alert fix +- [ ] Bug fix (non-breaking) +- [ ] New feature (non-breaking) +- [ ] Breaking change (breaking, will cause existing functionality to not work as expected) # Checklist: -Please delete options that are not relevant. - -- [ ] Any dependent changes have been merged and published in downstream modules -- [ ] Rebase/merge the branch which this PR is made to -- [ ] Changes to the database schema: A new migration is included in the PR -- [ ] Product Owner / Scrum Master: This PR is made to the `master` branch and I have updated the [version](../dds_web/version.py) -- [ ] I am bumping the major version (e.g. 1.x.x to 2.x.x) and I have made the corresponding changes to the CLI version +- [ ] Blocking PRs have been merged +- [ ] Rebase / update of branch done +- [ ] Database schema has changed + - [ ] A new migration is included in the PR + - [ ] The change does not require a migration +- [ ] Product Owner / Scrum Master + - [ ] The [version](../dds_web/version.py) is updated (PR to `master` branch) + - [ ] I am bumping the major version (e.g. 1.x.x to 2.x.x) + - [ ] I have made the corresponding changes to the CLI version ## Formatting and documentation From dfba53722bb3ed22dde65b1ab9ac3b2cfbb884cc Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 16:03:35 +0200 Subject: [PATCH 055/111] checks --- .github/pull_request_template.md | 34 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 21e154485..e4c97c062 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -18,26 +18,34 @@ # Checklist: -- [ ] Blocking PRs have been merged -- [ ] Rebase / update of branch done +## General + +- [ ] [Changelog](../CHANGELOG.md): New row added - [ ] Database schema has changed - [ ] A new migration is included in the PR - [ ] The change does not require a migration +- [ ] Code change + - [ ] Self-review of code done + - [ ] Comments added, particularly in hard-to-understand areas + - [ ] Documentation is updated + +## Repository / Releases + +- [ ] Blocking PRs have been merged +- [ ] Rebase / update of branch done - [ ] Product Owner / Scrum Master - [ ] The [version](../dds_web/version.py) is updated (PR to `master` branch) - [ ] I am bumping the major version (e.g. 1.x.x to 2.x.x) - [ ] I have made the corresponding changes to the CLI version -## Formatting and documentation - -- [ ] I have added a row in the [changelog](../CHANGELOG.md) -- [ ] The code follows the style guidelines of this project: Black / Prettier formatting -- [ ] I have performed a self-review of my code -- [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have made corresponding changes to the documentation -- [ ] My changes generate no new warnings +## Checks -## Tests +- [ ] Formatting: Black & Prettier checks pass +- [ ] Tests + - [ ] I have added tests for the new code + - [ ] The tests pass +- [ ] Trivy: + - [ ] This PR fixes new security alerts + - [ ] Security alerts have been dismissed + - [ ] PR will be merged with new security alerts; This is why: _Please add a short description here_ -- [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] New and existing unit tests pass locally with my changes From ef06162a3fee01bc51edbf40d993ba614f628ee4 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 16:04:29 +0200 Subject: [PATCH 056/111] prettier --- .github/pull_request_template.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e4c97c062..a7f8b0690 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -22,30 +22,29 @@ - [ ] [Changelog](../CHANGELOG.md): New row added - [ ] Database schema has changed - - [ ] A new migration is included in the PR - - [ ] The change does not require a migration + - [ ] A new migration is included in the PR + - [ ] The change does not require a migration - [ ] Code change - - [ ] Self-review of code done - - [ ] Comments added, particularly in hard-to-understand areas - - [ ] Documentation is updated + - [ ] Self-review of code done + - [ ] Comments added, particularly in hard-to-understand areas + - [ ] Documentation is updated ## Repository / Releases - [ ] Blocking PRs have been merged - [ ] Rebase / update of branch done - [ ] Product Owner / Scrum Master - - [ ] The [version](../dds_web/version.py) is updated (PR to `master` branch) - - [ ] I am bumping the major version (e.g. 1.x.x to 2.x.x) - - [ ] I have made the corresponding changes to the CLI version + - [ ] The [version](../dds_web/version.py) is updated (PR to `master` branch) + - [ ] I am bumping the major version (e.g. 1.x.x to 2.x.x) + - [ ] I have made the corresponding changes to the CLI version ## Checks - [ ] Formatting: Black & Prettier checks pass - [ ] Tests - - [ ] I have added tests for the new code - - [ ] The tests pass -- [ ] Trivy: - - [ ] This PR fixes new security alerts - - [ ] Security alerts have been dismissed - - [ ] PR will be merged with new security alerts; This is why: _Please add a short description here_ - + - [ ] I have added tests for the new code + - [ ] The tests pass +- [ ] Trivy: + - [ ] This PR fixes new security alerts + - [ ] Security alerts have been dismissed + - [ ] PR will be merged with new security alerts; This is why: _Please add a short description here_ From 2f03f4b9f46f648783f1e017fa0cfa601ba2aa52 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 16:11:22 +0200 Subject: [PATCH 057/111] trivy --- .github/pull_request_template.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a7f8b0690..7087ab4e4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -45,6 +45,7 @@ - [ ] I have added tests for the new code - [ ] The tests pass - [ ] Trivy: + - [ ] There are no new security alerts - [ ] This PR fixes new security alerts - [ ] Security alerts have been dismissed - [ ] PR will be merged with new security alerts; This is why: _Please add a short description here_ From 1b58ec0ea15982a087f8c689a2de1a78189eae51 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 16:11:40 +0200 Subject: [PATCH 058/111] prettier --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7087ab4e4..cc7b3f4d5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -45,7 +45,7 @@ - [ ] I have added tests for the new code - [ ] The tests pass - [ ] Trivy: - - [ ] There are no new security alerts + - [ ] There are no new security alerts - [ ] This PR fixes new security alerts - [ ] Security alerts have been dismissed - [ ] PR will be merged with new security alerts; This is why: _Please add a short description here_ From b34296ea76832f737dcf5f96725c7d71f24ff8a6 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 16:14:26 +0200 Subject: [PATCH 059/111] codeql and snyk --- .github/pull_request_template.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index cc7b3f4d5..b31eacd71 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -41,10 +41,11 @@ ## Checks - [ ] Formatting: Black & Prettier checks pass +- [ ] CodeQL passes - [ ] Tests - [ ] I have added tests for the new code - [ ] The tests pass -- [ ] Trivy: +- [ ] Trivy / Snyk: - [ ] There are no new security alerts - [ ] This PR fixes new security alerts - [ ] Security alerts have been dismissed From 1a48a301d8407660ee9c6bf9d112774229edfa1b Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 16:15:37 +0200 Subject: [PATCH 060/111] changed cron --- .github/workflows/trivy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 0d40578c2..30f65dbde 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,8 +1,8 @@ name: trivy on: schedule: - - cron: "*/5 * * * *" - # - cron: "0 7 * * *" + # - cron: "*/5 * * * *" + - cron: "0 7,13 * * *" jobs: scan: permissions: From 5b172f43910d727c606f584a14e093f4d747f972 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 16:20:47 +0200 Subject: [PATCH 061/111] typo and missing info --- .github/pull_request_template.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b31eacd71..763c8759d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,6 @@ -> **Before submitting the PR, please go through the seconds below and fill in what you can. If there are any items that are irrelevant for the current PR, remove the row. If a relevant option is missing, please add it as an item and add a PR comment informing that the new option should be included into this template.** +> **Before submitting the PR, please go through the sections below and fill in what you can. If there are any items that are irrelevant for the current PR, remove the row. If a relevant option is missing, please add it as an item and add a PR comment informing that the new option should be included into this template.** + +> **_All items should be ticked before the PR is merged_** # Description From 62c9089efd4af1fc3401b1084de004830f49852e Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 16:21:15 +0200 Subject: [PATCH 062/111] clarification --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 763c8759d..78d9bcc1c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,6 @@ > **Before submitting the PR, please go through the sections below and fill in what you can. If there are any items that are irrelevant for the current PR, remove the row. If a relevant option is missing, please add it as an item and add a PR comment informing that the new option should be included into this template.** -> **_All items should be ticked before the PR is merged_** +> **All _relevant_ items should be ticked before the PR is merged** # Description From 4e0324f4fbfd90a65d2cf0c70e19adc75bd6dff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Thu, 15 Sep 2022 17:05:27 +0200 Subject: [PATCH 063/111] Update CONTRIBUTING.md --- CONTRIBUTING.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2ca7a71ae..4ddfd3f1d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,17 @@ -**In progress** +If you would like to contribute to this project -1. Steps for creating good issues or pull requests. -2. Links to external documentation, mailing lists, or a code of conduct. -3. Community and behavioral expectations. +**Thank you, any suggestions and / or contributions are very appreciated** + +If you would like to suggest a change, please go to the issues tab and fill out and issue. If you would like to contribute with code, follow the steps below: + +1. Fork this repository +2. Make a **draft** pull request. There is a PR template that will guide you through what information we want, checks that need to pass and items you need to tick before we will review your PR. +3. When you have followed the guide in the template and all items are filled in, ticked etc, mark your PR as _ready for review_ + +We will get back to you as soon as possible with possible changes, comments, suggestions etc. + +All information about this project can be found in this repository (e.g. at https://github.com/ScilifelabDataCentre/dds_web/tree/dev/doc). If there is some information missing, don't hestitate to inform us by _creating an issue_. + +## Code of Conduct + +_in progress_ From d536407adfd16fdd5ed2d66202f3f370c27dbc87 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 17:08:08 +0200 Subject: [PATCH 064/111] prettier --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4ddfd3f1d..39faa010a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ If you would like to contribute to this project -**Thank you, any suggestions and / or contributions are very appreciated** +**Thank you, any suggestions and / or contributions are very appreciated** If you would like to suggest a change, please go to the issues tab and fill out and issue. If you would like to contribute with code, follow the steps below: @@ -8,7 +8,7 @@ If you would like to suggest a change, please go to the issues tab and fill out 2. Make a **draft** pull request. There is a PR template that will guide you through what information we want, checks that need to pass and items you need to tick before we will review your PR. 3. When you have followed the guide in the template and all items are filled in, ticked etc, mark your PR as _ready for review_ -We will get back to you as soon as possible with possible changes, comments, suggestions etc. +We will get back to you as soon as possible with possible changes, comments, suggestions etc. All information about this project can be found in this repository (e.g. at https://github.com/ScilifelabDataCentre/dds_web/tree/dev/doc). If there is some information missing, don't hestitate to inform us by _creating an issue_. From 4a263e94abce3dc6abb6fca076f8428462b7ca34 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 17:17:16 +0200 Subject: [PATCH 065/111] Code of Conduct --- CONTRIBUTING.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39faa010a..ce81d7694 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,4 +14,36 @@ All information about this project can be found in this repository (e.g. at http ## Code of Conduct -_in progress_ +### Acceptable behavior + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +### Our responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +### Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainer using any of the private contact addresses. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +--- + +_This was adapted from https://github.com/dec0dOS/amazing-github-template/blob/main/docs/CODE_OF_CONDUCT.md_ \ No newline at end of file From fcf2f80d963341ce8a26b73ecd21874723b2472e Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 17:18:12 +0200 Subject: [PATCH 066/111] prettier --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce81d7694..197ca6431 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,4 +46,4 @@ Project maintainers who do not follow or enforce the Code of Conduct in good fai --- -_This was adapted from https://github.com/dec0dOS/amazing-github-template/blob/main/docs/CODE_OF_CONDUCT.md_ \ No newline at end of file +_This was adapted from https://github.com/dec0dOS/amazing-github-template/blob/main/docs/CODE_OF_CONDUCT.md_ From 00b3871093ef9ccc8ceca81ef1cbc15410e9f3ef Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 15 Sep 2022 17:25:48 +0200 Subject: [PATCH 067/111] added comment about cli --- CONTRIBUTING.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 197ca6431..520f33ab0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -If you would like to contribute to this project +If you would like to contribute to this project... **Thank you, any suggestions and / or contributions are very appreciated** @@ -12,6 +12,8 @@ We will get back to you as soon as possible with possible changes, comments, sug All information about this project can be found in this repository (e.g. at https://github.com/ScilifelabDataCentre/dds_web/tree/dev/doc). If there is some information missing, don't hestitate to inform us by _creating an issue_. +Contributions, suggestions or comments regarding the corresponding CLI should be directed [here](https://github.com/ScilifelabDataCentre/dds_web). + ## Code of Conduct ### Acceptable behavior From f7b5ee321f2b162de7c4c80c83887f014dc6b132 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 07:51:23 +0200 Subject: [PATCH 068/111] tests --- tests/api/test_superadmin_only.py | 116 ++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index 428a2acfc..72af4f24e 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -6,12 +6,15 @@ import http import time import typing +import unittest # Installed import flask import werkzeug +import flask_mail # Own +from dds_web import db from dds_web.database import models import tests @@ -427,3 +430,116 @@ def test_reset_hotp(client: flask.testing.FlaskClient) -> None: user_row_again: models.User = models.User.query.filter_by(username=user_row.username).first() assert user_row_again and not user_row_again.totp_enabled + + +# SendMOTD ######################################################################################### + +def test_send_motd_incorrect_method(client: flask.testing.FlaskClient) -> None: + """Only post should be accepted.""" + # Authenticate + token: typing.Dict = get_token(username=users["Super Admin"], client=client) + + # Attempt request + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + for method in [client.get, client, client.head, client.put, client.delete, client.connect, client.options, client.trace, client.patch]: + response: werkzeug.test.WrapperTestResponse = method(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": "something"}) + assert response.status_code == http.HTTPStatus.METHOD_NOT_ALLOWED + assert mock_mail_send.call_count == 0 + +def test_send_motd_not_superadmin(client: flask.testing.FlaskClient) -> None: + """Only Super Admins should be able to send the motds.""" + for role in ["Unit Admin", "Unit Personnel", "Researcher"]: + # Authenticate + token: typing.Dict = get_token(username=users[role], client=client) + + # Attempt request + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": "something"}) + assert response.status_code == http.HTTPStatus.FORBIDDEN + assert mock_mail_send.call_count == 0 + +def test_send_motd_no_json(client: flask.testing.FlaskClient) -> None: + """The request needs json in order to send a motd.""" + # Authenticate + token: typing.Dict = get_token(username=users["Super Admin"], client=client) + + # Attempt request + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Required data missing from request" in response.json.get("message") + assert mock_mail_send.call_count == 0 + +def test_send_motd_no_motdid(client: flask.testing.FlaskClient) -> None: + """The json should have motd_id.""" + # Authenticate + token: typing.Dict = get_token(username=users["Super Admin"], client=client) + + # Attempt request + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"test": "something"}) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Please specify the ID of the MOTD you want to send." in response.json.get("message") + assert mock_mail_send.call_count == 0 + + +def test_send_motd_nonexistent_motd(client: flask.testing.FlaskClient) -> None: + """The motd_id needs to be a valid motd.""" + # Authenticate + token: typing.Dict = get_token(username=users["Super Admin"], client=client) + + # Specify motd to send + motd_id: int = 10 + + # Attempt request + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": motd_id}) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert f"There is no active MOTD with ID '{motd_id}'" in response.json.get("message") + assert mock_mail_send.call_count == 0 + +def test_send_motd_ok(client: flask.testing.FlaskClient) -> None: + """Send a motd to all users.""" + # Authenticate + token: typing.Dict = get_token(username=users["Super Admin"], client=client) + + # Create a motd + message: str = "This is a message that should become a MOTD and then be sent to all the users." + new_motd: models.MOTD = models.MOTD(message=message) + db.session.add(new_motd) + db.session.commit() + + # Make sure the motd is created + created_motd: models.MOTD = models.MOTD.query.filter_by(message=message).one_or_none() + assert created_motd + + # Get number of users + num_users = models.User.query.count() + + # Attempt request + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": created_motd.id}) + assert response.status_code == http.HTTPStatus.OK + assert mock_mail_send.call_count == num_users + +def test_send_motd_not_active(client: flask.testing.FlaskClient) -> None: + """Attempt sending a motd which is not active.""" + # Authenticate + token: typing.Dict = get_token(username=users["Super Admin"], client=client) + + # Create a motd + message: str = "This is a message that should become a MOTD and then be sent to all the users." + new_motd: models.MOTD = models.MOTD(message=message, active=False) + db.session.add(new_motd) + db.session.commit() + + # Make sure the motd is created + created_motd: models.MOTD = models.MOTD.query.filter_by(message=message).one_or_none() + assert created_motd and not created_motd.active + + # Attempt request + with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": created_motd.id}) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert f"There is no active MOTD with ID '{created_motd.id}'" in response.json.get("message") + assert mock_mail_send.call_count == 0 From d10fc6729eb8f0ca013b5685f37b3a03f7e08967 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 09:02:54 +0200 Subject: [PATCH 069/111] create endpoint sendmotd --- dds_web/api/superadmin_only.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index b9c67e591..77f79d231 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -135,6 +135,30 @@ def put(self): return {"message": "The MOTD was successfully deactivated in the database."} +class SendMOTD(flask_restful.Resource): + """Send a MOTD to all users in database.""" + + @auth.login_required(role=["Super Admin"]) + @logging_bind_request + @json_required + @handle_db_error + def post(self): + """Send MOTD as email to users.""" + # Get MOTD ID + motd_id: int = flask.request.json.get("motd_id") + if not motd_id or not isinstance(motd_id, int): # The id starts at 1 - ok to not accept 0 + raise ddserr.DDSArgumentError( + message="Please specify the ID of the MOTD you want to send." + ) + + # Get MOTD object + motd_obj: models.MOTD.query.get(motd_id) + if not motd_obj: + raise ddserr.DDSArgumentError(message=f"There is no active MOTD with ID '{motd_id}'.") + + + + class FindUser(flask_restful.Resource): """Get all users or check if there a specific user in the database.""" @@ -184,3 +208,5 @@ def put(self): return { "message": f"TOTP has been deactivated for user: {user.username}. They can now use 2FA via email during authentication." } + + From 4cd2589cd35699caf61a7cc337c26640e139cdae Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 10:16:47 +0200 Subject: [PATCH 070/111] generate email --- dds_web/api/superadmin_only.py | 35 +++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 77f79d231..73f18958c 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -5,11 +5,13 @@ #################################################################################################### # Standard library +import os # Installed import flask_restful import flask import structlog +import flask_mail # Own modules from dds_web import auth, db @@ -155,9 +157,40 @@ def post(self): motd_obj: models.MOTD.query.get(motd_id) if not motd_obj: raise ddserr.DDSArgumentError(message=f"There is no active MOTD with ID '{motd_id}'.") - + # Create email content + # put motd_obj.message etc in there etc + + # Get all users primary email + emails = [user.primary_email for user in models.User.query.all()] + + # Structure email + msg = flask_mail.Message(subject="DDS Important Information", recipients=emails) + msg.attach( + "scilifelab_logo.png", + "image/png", + open( + os.path.join(flask.current_app.static_folder, "img/scilifelab_logo.png"), "rb" + ).read(), + "inline", + headers=[ + ["Content-ID", ""], + ], + ) + + msg.body = flask.render_template( + f"mail/motd.txt", + motd=motd_obj.message, + ) + msg.html = flask.render_template( + f"mail/motd.html", + motd=motd_obj.message, + ) + + from dds_web.api import user + user.AddUser.send_email_with_retry(msg=msg) + return {"message": "email sent"} class FindUser(flask_restful.Resource): From a79ade4429121da74fb5635c8e41ee177df6eb1f Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 10:35:12 +0200 Subject: [PATCH 071/111] html --- dds_web/templates/mail/motd.html | 8 ++++++++ dds_web/templates/mail/motd.txt | 5 +++++ 2 files changed, 13 insertions(+) create mode 100644 dds_web/templates/mail/motd.html create mode 100644 dds_web/templates/mail/motd.txt diff --git a/dds_web/templates/mail/motd.html b/dds_web/templates/mail/motd.html new file mode 100644 index 000000000..5f1a5c067 --- /dev/null +++ b/dds_web/templates/mail/motd.html @@ -0,0 +1,8 @@ +{% extends 'mail/mail_base.html' %} +{% block body %} +Important information to all DDS users: + +{{ motd }} + +This email cannot be replied to. If you need to get in contact with us regarding the DDS, please send an email to datacentre@scilifelab.se. +{% endblock %} \ No newline at end of file diff --git a/dds_web/templates/mail/motd.txt b/dds_web/templates/mail/motd.txt new file mode 100644 index 000000000..3566b4d45 --- /dev/null +++ b/dds_web/templates/mail/motd.txt @@ -0,0 +1,5 @@ +Important information to all DDS users: + +{{ motd }} + +This email cannot be replied to. If you need to get in contact with us regarding the DDS, please send an email to datacentre@scilifelab.se. \ No newline at end of file From 2637f22c2a420955acde9be9ae6507f29b776a1a Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 12:00:39 +0200 Subject: [PATCH 072/111] format email and try resend --- dds_web/api/__init__.py | 1 + dds_web/api/superadmin_only.py | 66 ++++++++++++++++---------------- dds_web/api/user.py | 9 +++-- dds_web/templates/mail/motd.html | 28 +++++++++++--- tests/__init__.py | 1 + 5 files changed, 63 insertions(+), 42 deletions(-) diff --git a/dds_web/api/__init__.py b/dds_web/api/__init__.py index 3d8f23f07..6614b5a4e 100644 --- a/dds_web/api/__init__.py +++ b/dds_web/api/__init__.py @@ -81,6 +81,7 @@ def output_json(data, code, headers=None): api.add_resource(superadmin_only.AllUnits, "/unit/info/all", endpoint="all_units") api.add_resource(superadmin_only.MOTD, "/motd", endpoint="motd") +api.add_resource(superadmin_only.SendMOTD, "/motd/send", endpoint="send_motd") api.add_resource(superadmin_only.FindUser, "/user/find", endpoint="find_user") api.add_resource( superadmin_only.ResetTwoFactor, "/user/totp/deactivate", endpoint="reset_user_hotp" diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 73f18958c..2024a72c7 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -6,6 +6,8 @@ # Standard library import os +import smtplib +import time # Installed import flask_restful @@ -14,11 +16,12 @@ import flask_mail # Own modules -from dds_web import auth, db +from dds_web import auth, db, mail from dds_web.database import models from dds_web.api.dds_decorators import json_required, logging_bind_request, handle_db_error from dds_web import utils import dds_web.errors as ddserr +from dds_web.api.user import AddUser # initiate bound logger @@ -154,43 +157,40 @@ def post(self): ) # Get MOTD object - motd_obj: models.MOTD.query.get(motd_id) + motd_obj: models.MOTD = models.MOTD.query.get(motd_id) if not motd_obj: raise ddserr.DDSArgumentError(message=f"There is no active MOTD with ID '{motd_id}'.") # Create email content # put motd_obj.message etc in there etc - - # Get all users primary email - emails = [user.primary_email for user in models.User.query.all()] - - # Structure email - msg = flask_mail.Message(subject="DDS Important Information", recipients=emails) - msg.attach( - "scilifelab_logo.png", - "image/png", - open( - os.path.join(flask.current_app.static_folder, "img/scilifelab_logo.png"), "rb" - ).read(), - "inline", - headers=[ - ["Content-ID", ""], - ], - ) - - msg.body = flask.render_template( - f"mail/motd.txt", - motd=motd_obj.message, - ) - msg.html = flask.render_template( - f"mail/motd.html", - motd=motd_obj.message, - ) - - from dds_web.api import user - user.AddUser.send_email_with_retry(msg=msg) - - return {"message": "email sent"} + subject: str = "DDS Important Information" + body: str = flask.render_template(f"mail/motd.txt", motd=motd_obj.message) + html = flask.render_template(f"mail/motd.html", motd=motd_obj.message) + + # Setup email connection + with mail.connect() as conn: + # Email users + for user in utils.page_query(db.session.query(models.User)): + primary_email = user.primary_email + if not primary_email: + flask.current_app.logger.warning(f"No primary email found for user '{user.username}'.") + pass + msg = flask_mail.Message(subject=subject, recipients=[primary_email], body=body, html=html) + msg.attach( + "scilifelab_logo.png", + "image/png", + open( + os.path.join(flask.current_app.static_folder, "img/scilifelab_logo.png"), "rb" + ).read(), + "inline", + headers=[ + ["Content-ID", ""], + ], + ) + # Send email + AddUser.send_email_with_retry(msg=msg, obj=conn) + + return {"message": f"MOTD '{motd_id}' has been sent to the users."} class FindUser(flask_restful.Resource): diff --git a/dds_web/api/user.py b/dds_web/api/user.py index e20e4be3e..8f72fd012 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -297,18 +297,19 @@ def invite_user(email, new_user_role, project=None, unit=None): } @staticmethod - def send_email_with_retry(msg, times_retried=0): + def send_email_with_retry(msg, times_retried=0, obj=None): """Send email with retry on exception""" - + if obj is None: + obj = mail try: - mail.send(msg) + obj.send(msg) except smtplib.SMTPException as err: # Wait a little bit time.sleep(10) # Retry twice if times_retried < 2: retry = times_retried + 1 - AddUser.send_email_with_retry(msg, retry) + AddUser.send_email_with_retry(msg, times_retried=retry, obj=obj) @staticmethod @logging_bind_request diff --git a/dds_web/templates/mail/motd.html b/dds_web/templates/mail/motd.html index 5f1a5c067..6cc78fa00 100644 --- a/dds_web/templates/mail/motd.html +++ b/dds_web/templates/mail/motd.html @@ -1,8 +1,26 @@ {% extends 'mail/mail_base.html' %} {% block body %} -Important information to all DDS users: - -{{ motd }} - -This email cannot be replied to. If you need to get in contact with us regarding the DDS, please send an email to datacentre@scilifelab.se. + + + + + + + +
+

+ Logo +

+
+

+ Important information to all DDS users: +

+

+ {{ motd }} +

+
+

+

+ This email cannot be replied to. If you need to get in contact with us regarding the DDS, please send an email to datacentre@scilifelab.se. +

{% endblock %} \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index 5ba91be63..07760be38 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -221,6 +221,7 @@ class DDSEndpoint: # Superadmins only LIST_UNITS_ALL = BASE_ENDPOINT + "/unit/info/all" MOTD = BASE_ENDPOINT + "/motd" + MOTD_SEND = BASE_ENDPOINT + "/motd/send" USER_FIND = BASE_ENDPOINT + "/user/find" TOTP_DEACTIVATE = BASE_ENDPOINT + "/user/totp/deactivate" From 1ad0af9890757bb2c80d299e0f5b95422e983dc6 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 12:19:20 +0200 Subject: [PATCH 073/111] connection instead of mail --- dds_web/api/superadmin_only.py | 3 ++- tests/api/test_superadmin_only.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 2024a72c7..7b350e89f 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -158,7 +158,7 @@ def post(self): # Get MOTD object motd_obj: models.MOTD = models.MOTD.query.get(motd_id) - if not motd_obj: + if not motd_obj or not motd_obj.active: raise ddserr.DDSArgumentError(message=f"There is no active MOTD with ID '{motd_id}'.") # Create email content @@ -169,6 +169,7 @@ def post(self): # Setup email connection with mail.connect() as conn: + flask.current_app.logger.info(type(conn)) # Email users for user in utils.page_query(db.session.query(models.User)): primary_email = user.primary_email diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index 72af4f24e..51e2a3e5b 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -440,8 +440,8 @@ def test_send_motd_incorrect_method(client: flask.testing.FlaskClient) -> None: token: typing.Dict = get_token(username=users["Super Admin"], client=client) # Attempt request - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: - for method in [client.get, client, client.head, client.put, client.delete, client.connect, client.options, client.trace, client.patch]: + with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: + for method in [client.get, client.put, client.delete, client.patch]: response: werkzeug.test.WrapperTestResponse = method(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": "something"}) assert response.status_code == http.HTTPStatus.METHOD_NOT_ALLOWED assert mock_mail_send.call_count == 0 @@ -453,7 +453,7 @@ def test_send_motd_not_superadmin(client: flask.testing.FlaskClient) -> None: token: typing.Dict = get_token(username=users[role], client=client) # Attempt request - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": "something"}) assert response.status_code == http.HTTPStatus.FORBIDDEN assert mock_mail_send.call_count == 0 @@ -464,7 +464,7 @@ def test_send_motd_no_json(client: flask.testing.FlaskClient) -> None: token: typing.Dict = get_token(username=users["Super Admin"], client=client) # Attempt request - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token) assert response.status_code == http.HTTPStatus.BAD_REQUEST assert "Required data missing from request" in response.json.get("message") @@ -476,7 +476,7 @@ def test_send_motd_no_motdid(client: flask.testing.FlaskClient) -> None: token: typing.Dict = get_token(username=users["Super Admin"], client=client) # Attempt request - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"test": "something"}) assert response.status_code == http.HTTPStatus.BAD_REQUEST assert "Please specify the ID of the MOTD you want to send." in response.json.get("message") @@ -492,7 +492,7 @@ def test_send_motd_nonexistent_motd(client: flask.testing.FlaskClient) -> None: motd_id: int = 10 # Attempt request - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": motd_id}) assert response.status_code == http.HTTPStatus.BAD_REQUEST assert f"There is no active MOTD with ID '{motd_id}'" in response.json.get("message") @@ -517,7 +517,7 @@ def test_send_motd_ok(client: flask.testing.FlaskClient) -> None: num_users = models.User.query.count() # Attempt request - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": created_motd.id}) assert response.status_code == http.HTTPStatus.OK assert mock_mail_send.call_count == num_users @@ -538,7 +538,7 @@ def test_send_motd_not_active(client: flask.testing.FlaskClient) -> None: assert created_motd and not created_motd.active # Attempt request - with unittest.mock.patch.object(flask_mail.Mail, "send") as mock_mail_send: + with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": created_motd.id}) assert response.status_code == http.HTTPStatus.BAD_REQUEST assert f"There is no active MOTD with ID '{created_motd.id}'" in response.json.get("message") From a64322ed22f64c662a874b9db7fc844066c4c9f0 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 12:32:28 +0200 Subject: [PATCH 074/111] linting --- dds_web/api/superadmin_only.py | 20 +++++---- tests/api/test_superadmin_only.py | 68 ++++++++++++++++++++----------- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 7b350e89f..8cfbb3b82 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -7,7 +7,7 @@ # Standard library import os import smtplib -import time +import time # Installed import flask_restful @@ -140,6 +140,7 @@ def put(self): return {"message": "The MOTD was successfully deactivated in the database."} + class SendMOTD(flask_restful.Resource): """Send a MOTD to all users in database.""" @@ -151,11 +152,11 @@ def post(self): """Send MOTD as email to users.""" # Get MOTD ID motd_id: int = flask.request.json.get("motd_id") - if not motd_id or not isinstance(motd_id, int): # The id starts at 1 - ok to not accept 0 + if not motd_id or not isinstance(motd_id, int): # The id starts at 1 - ok to not accept 0 raise ddserr.DDSArgumentError( message="Please specify the ID of the MOTD you want to send." ) - + # Get MOTD object motd_obj: models.MOTD = models.MOTD.query.get(motd_id) if not motd_obj or not motd_obj.active: @@ -174,14 +175,19 @@ def post(self): for user in utils.page_query(db.session.query(models.User)): primary_email = user.primary_email if not primary_email: - flask.current_app.logger.warning(f"No primary email found for user '{user.username}'.") + flask.current_app.logger.warning( + f"No primary email found for user '{user.username}'." + ) pass - msg = flask_mail.Message(subject=subject, recipients=[primary_email], body=body, html=html) + msg = flask_mail.Message( + subject=subject, recipients=[primary_email], body=body, html=html + ) msg.attach( "scilifelab_logo.png", "image/png", open( - os.path.join(flask.current_app.static_folder, "img/scilifelab_logo.png"), "rb" + os.path.join(flask.current_app.static_folder, "img/scilifelab_logo.png"), + "rb", ).read(), "inline", headers=[ @@ -242,5 +248,3 @@ def put(self): return { "message": f"TOTP has been deactivated for user: {user.username}. They can now use 2FA via email during authentication." } - - diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index 51e2a3e5b..42b509165 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -434,6 +434,7 @@ def test_reset_hotp(client: flask.testing.FlaskClient) -> None: # SendMOTD ######################################################################################### + def test_send_motd_incorrect_method(client: flask.testing.FlaskClient) -> None: """Only post should be accepted.""" # Authenticate @@ -442,10 +443,13 @@ def test_send_motd_incorrect_method(client: flask.testing.FlaskClient) -> None: # Attempt request with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: for method in [client.get, client.put, client.delete, client.patch]: - response: werkzeug.test.WrapperTestResponse = method(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": "something"}) + response: werkzeug.test.WrapperTestResponse = method( + tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": "something"} + ) assert response.status_code == http.HTTPStatus.METHOD_NOT_ALLOWED assert mock_mail_send.call_count == 0 + def test_send_motd_not_superadmin(client: flask.testing.FlaskClient) -> None: """Only Super Admins should be able to send the motds.""" for role in ["Unit Admin", "Unit Personnel", "Researcher"]: @@ -454,10 +458,13 @@ def test_send_motd_not_superadmin(client: flask.testing.FlaskClient) -> None: # Attempt request with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: - response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": "something"}) + response: werkzeug.test.WrapperTestResponse = client.post( + tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": "something"} + ) assert response.status_code == http.HTTPStatus.FORBIDDEN assert mock_mail_send.call_count == 0 + def test_send_motd_no_json(client: flask.testing.FlaskClient) -> None: """The request needs json in order to send a motd.""" # Authenticate @@ -465,11 +472,14 @@ def test_send_motd_no_json(client: flask.testing.FlaskClient) -> None: # Attempt request with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: - response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token) + response: werkzeug.test.WrapperTestResponse = client.post( + tests.DDSEndpoint.MOTD_SEND, headers=token + ) assert response.status_code == http.HTTPStatus.BAD_REQUEST assert "Required data missing from request" in response.json.get("message") assert mock_mail_send.call_count == 0 + def test_send_motd_no_motdid(client: flask.testing.FlaskClient) -> None: """The json should have motd_id.""" # Authenticate @@ -477,7 +487,9 @@ def test_send_motd_no_motdid(client: flask.testing.FlaskClient) -> None: # Attempt request with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: - response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"test": "something"}) + response: werkzeug.test.WrapperTestResponse = client.post( + tests.DDSEndpoint.MOTD_SEND, headers=token, json={"test": "something"} + ) assert response.status_code == http.HTTPStatus.BAD_REQUEST assert "Please specify the ID of the MOTD you want to send." in response.json.get("message") assert mock_mail_send.call_count == 0 @@ -493,53 +505,63 @@ def test_send_motd_nonexistent_motd(client: flask.testing.FlaskClient) -> None: # Attempt request with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: - response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": motd_id}) + response: werkzeug.test.WrapperTestResponse = client.post( + tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": motd_id} + ) assert response.status_code == http.HTTPStatus.BAD_REQUEST assert f"There is no active MOTD with ID '{motd_id}'" in response.json.get("message") assert mock_mail_send.call_count == 0 -def test_send_motd_ok(client: flask.testing.FlaskClient) -> None: - """Send a motd to all users.""" + +def test_send_motd_not_active(client: flask.testing.FlaskClient) -> None: + """Attempt sending a motd which is not active.""" # Authenticate token: typing.Dict = get_token(username=users["Super Admin"], client=client) # Create a motd message: str = "This is a message that should become a MOTD and then be sent to all the users." - new_motd: models.MOTD = models.MOTD(message=message) + new_motd: models.MOTD = models.MOTD(message=message, active=False) db.session.add(new_motd) db.session.commit() # Make sure the motd is created created_motd: models.MOTD = models.MOTD.query.filter_by(message=message).one_or_none() - assert created_motd - - # Get number of users - num_users = models.User.query.count() + assert created_motd and not created_motd.active # Attempt request with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: - response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": created_motd.id}) - assert response.status_code == http.HTTPStatus.OK - assert mock_mail_send.call_count == num_users + response: werkzeug.test.WrapperTestResponse = client.post( + tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": created_motd.id} + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert f"There is no active MOTD with ID '{created_motd.id}'" in response.json.get( + "message" + ) + assert mock_mail_send.call_count == 0 -def test_send_motd_not_active(client: flask.testing.FlaskClient) -> None: - """Attempt sending a motd which is not active.""" + +def test_send_motd_ok(client: flask.testing.FlaskClient) -> None: + """Send a motd to all users.""" # Authenticate token: typing.Dict = get_token(username=users["Super Admin"], client=client) # Create a motd message: str = "This is a message that should become a MOTD and then be sent to all the users." - new_motd: models.MOTD = models.MOTD(message=message, active=False) + new_motd: models.MOTD = models.MOTD(message=message) db.session.add(new_motd) db.session.commit() # Make sure the motd is created created_motd: models.MOTD = models.MOTD.query.filter_by(message=message).one_or_none() - assert created_motd and not created_motd.active + assert created_motd + + # Get number of users + num_users = models.User.query.count() # Attempt request with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: - response: werkzeug.test.WrapperTestResponse = client.post(tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": created_motd.id}) - assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert f"There is no active MOTD with ID '{created_motd.id}'" in response.json.get("message") - assert mock_mail_send.call_count == 0 + response: werkzeug.test.WrapperTestResponse = client.post( + tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": created_motd.id} + ) + assert response.status_code == http.HTTPStatus.OK + assert mock_mail_send.call_count == num_users From 2ee1e1de14d0b4b3ae0b5571c2f6a70a4aac6967 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 12:39:16 +0200 Subject: [PATCH 075/111] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 419d74cec..b60d18377 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -144,3 +144,4 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - Change docker image to alpine ([#1272](https://github.com/ScilifelabDataCentre/dds_web/pull/1272)) - Added trivy when publishing to dockerhub ([#1276](https://github.com/ScilifelabDataCentre/dds_web/pull/1276)) - Bug fix: Cost value displayed by the --usage flag fixed ([#1274](https://github.com/ScilifelabDataCentre/dds_web/pull/1274)) +- New endpoint: SendMOTD - send important information to users ([#1283](https://github.com/ScilifelabDataCentre/dds_web/pull/1283)) From 985a31b83f7a9c65b79414f6139d62f761060999 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 12:41:28 +0200 Subject: [PATCH 076/111] remove unused --- dds_web/api/superadmin_only.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 8cfbb3b82..dacbd397c 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -6,8 +6,6 @@ # Standard library import os -import smtplib -import time # Installed import flask_restful From 89d3555273799f383958f3a95a928a137142dc50 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 12:42:53 +0200 Subject: [PATCH 077/111] email logging --- dds_web/api/superadmin_only.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index dacbd397c..f9c8622b5 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -168,7 +168,6 @@ def post(self): # Setup email connection with mail.connect() as conn: - flask.current_app.logger.info(type(conn)) # Email users for user in utils.page_query(db.session.query(models.User)): primary_email = user.primary_email From d9168ef2ca9ddf81fb383c357fdcc97f05c6c63f Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 12:46:02 +0200 Subject: [PATCH 078/111] move send_email_with_retry to utils --- dds_web/api/superadmin_only.py | 2 +- dds_web/api/user.py | 21 +++------------------ dds_web/utils.py | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index f9c8622b5..ddc3efd66 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -192,7 +192,7 @@ def post(self): ], ) # Send email - AddUser.send_email_with_retry(msg=msg, obj=conn) + utils.send_email_with_retry(msg=msg, obj=conn) return {"message": f"MOTD '{motd_id}' has been sent to the users."} diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 8f72fd012..bf6e47a68 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -296,21 +296,6 @@ def invite_user(email, new_user_role, project=None, unit=None): "errors": projects_not_shared, } - @staticmethod - def send_email_with_retry(msg, times_retried=0, obj=None): - """Send email with retry on exception""" - if obj is None: - obj = mail - try: - obj.send(msg) - except smtplib.SMTPException as err: - # Wait a little bit - time.sleep(10) - # Retry twice - if times_retried < 2: - retry = times_retried + 1 - AddUser.send_email_with_retry(msg, times_retried=retry, obj=obj) - @staticmethod @logging_bind_request def add_to_project(whom, project, role, send_email=True): @@ -494,7 +479,7 @@ def compose_and_send_email_to_user(userobj, mail_type, link=None, project=None): deadline=deadline, ) - AddUser.send_email_with_retry(msg) + dds_web.utils.send_email_with_retry(msg) class RetrieveUserInfo(flask_restful.Resource): @@ -1041,7 +1026,7 @@ def post(self): link=link, ) - AddUser.send_email_with_retry(msg) + dds_web.utils.send_email_with_retry(msg) return { "message": "Please check your email and follow the attached link to activate two-factor with authenticator app." } @@ -1107,7 +1092,7 @@ def post(self): link=link, ) - AddUser.send_email_with_retry(msg) + dds_web.utils.send_email_with_retry(msg) return { "message": "Please check your email and follow the attached link to activate two-factor with email." } diff --git a/dds_web/utils.py b/dds_web/utils.py index 1b1b6ecc6..990bb4ddd 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -10,6 +10,8 @@ import re import typing import urllib.parse +import time +import smtplib # Installed from contextlib import contextmanager @@ -447,6 +449,20 @@ def page_query(q): if not r: break +def send_email_with_retry(msg, times_retried=0, obj=None): + """Send email with retry on exception""" + if obj is None: + obj = mail + try: + obj.send(msg) + except smtplib.SMTPException as err: + # Wait a little bit + time.sleep(10) + # Retry twice + if times_retried < 2: + retry = times_retried + 1 + send_email_with_retry(msg, times_retried=retry, obj=obj) + def create_one_time_password_email(user, hotp_value): """Create HOTP email.""" From 8d749f8f841725cff51984c949a6d5ea6e0c99f6 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 12:46:13 +0200 Subject: [PATCH 079/111] black --- dds_web/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dds_web/utils.py b/dds_web/utils.py index 990bb4ddd..9bdaa2b82 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -449,6 +449,7 @@ def page_query(q): if not r: break + def send_email_with_retry(msg, times_retried=0, obj=None): """Send email with retry on exception""" if obj is None: From a8023ab8c5ff5f31e73d8cbb115ca3a2ce0116f7 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 12:47:51 +0200 Subject: [PATCH 080/111] break in html --- dds_web/templates/mail/motd.html | 1 + 1 file changed, 1 insertion(+) diff --git a/dds_web/templates/mail/motd.html b/dds_web/templates/mail/motd.html index 6cc78fa00..e295040d3 100644 --- a/dds_web/templates/mail/motd.html +++ b/dds_web/templates/mail/motd.html @@ -13,6 +13,7 @@

Important information to all DDS users:

+

{{ motd }}

From dd1c1ef1e3d693cf35a5118c9e60618a13435c52 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 13:53:47 +0200 Subject: [PATCH 081/111] pass to continue --- dds_web/api/superadmin_only.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index ddc3efd66..151aad972 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -175,7 +175,7 @@ def post(self): flask.current_app.logger.warning( f"No primary email found for user '{user.username}'." ) - pass + continue msg = flask_mail.Message( subject=subject, recipients=[primary_email], body=body, html=html ) From 52bc17f15ab165b08de057d486f7c7a95021a597 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 14:21:35 +0200 Subject: [PATCH 082/111] don't send if there is no primary email --- tests/api/test_superadmin_only.py | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index 42b509165..564edd9b9 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -539,6 +539,43 @@ def test_send_motd_not_active(client: flask.testing.FlaskClient) -> None: ) assert mock_mail_send.call_count == 0 +def test_send_motd_no_primary_email(client: flask.testing.FlaskClient) -> None: + """Send a motd to all users.""" + # Authenticate + token: typing.Dict = get_token(username=users["Super Admin"], client=client) + + # Create a motd + message: str = "This is a message that should become a MOTD and then be sent to all the users." + new_motd: models.MOTD = models.MOTD(message=message) + db.session.add(new_motd) + db.session.commit() + + # Make sure the motd is created + created_motd: models.MOTD = models.MOTD.query.filter_by(message=message).one_or_none() + assert created_motd + + # Get number of users + num_users: int = models.User.query.count() + + # Remove primary_email for one user + primary_email: models.Email = models.Email.query.first() + email: str = primary_email.email + username: str = primary_email.user.username + db.session.delete(primary_email) + db.session.commit() + + # Make sure email is removed + assert not models.Email.query.filter_by(email=email).one_or_none() + assert not models.User.query.filter_by(username=username).one().primary_email + + # Attempt request + with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: + response: werkzeug.test.WrapperTestResponse = client.post( + tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": created_motd.id} + ) + assert response.status_code == http.HTTPStatus.OK + assert mock_mail_send.call_count == num_users - 1 + def test_send_motd_ok(client: flask.testing.FlaskClient) -> None: """Send a motd to all users.""" From ef5e1047c6846d9765c04cdd54ee78107f5d5bb4 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 14:23:13 +0200 Subject: [PATCH 083/111] black --- tests/api/test_superadmin_only.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index 564edd9b9..aa42859b5 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -539,6 +539,7 @@ def test_send_motd_not_active(client: flask.testing.FlaskClient) -> None: ) assert mock_mail_send.call_count == 0 + def test_send_motd_no_primary_email(client: flask.testing.FlaskClient) -> None: """Send a motd to all users.""" # Authenticate From 577921817e4ea86d0a6f0da858d3eb74299a61e9 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 15:50:14 +0200 Subject: [PATCH 084/111] maintenance table --- dds_web/__init__.py | 16 +++++++++++++++- dds_web/database/models.py | 23 ++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 70ffeef44..28387f1ad 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -11,6 +11,7 @@ import sys import re import os +import typing # Installed import click @@ -309,10 +310,18 @@ def load_user(user_id): @click.argument("db_type", type=click.Choice(["production", "dev-small", "dev-big"])) @flask.cli.with_appcontext def fill_db_wrapper(db_type): + from dds_web.database import models + + maintenance_rows: typing.List = models.Maintenance.query.all() if db_type == "production": - from dds_web.database import models + # Verify that there's one maintenance row + if maintenance_rows: + maintenance_rows[0].active = True + db.session.delete(maintenance_rows[1::]) + db.session.commit() + # Fill rest username = flask.current_app.config["SUPERADMIN_USERNAME"] password = flask.current_app.config["SUPERADMIN_PASSWORD"] name = flask.current_app.config["SUPERADMIN_NAME"] @@ -339,6 +348,11 @@ def fill_db_wrapper(db_type): db.session.commit() flask.current_app.logger.info(f"Super Admin added: {username} ({email})") else: + # Verify that there's one maintenance row + if maintenance_rows: + maintenance_rows[0].active = False + db.session.delete(maintenance_rows[1::]) + db.session.commit() flask.current_app.logger.info("Initializing development db") assert flask.current_app.config["USE_LOCAL_DB"] diff --git a/dds_web/database/models.py b/dds_web/database/models.py index 1b2569a22..8569a21b8 100644 --- a/dds_web/database/models.py +++ b/dds_web/database/models.py @@ -994,7 +994,7 @@ class MOTD(db.Model): # Columns id = db.Column(db.Integer, primary_key=True, autoincrement=True) - message = db.Column(db.Text, nullable=False, default=None) + message = db.Column(db.Text, nullable=False) date_created = db.Column(db.DateTime(), nullable=False, default=dds_web.utils.current_time) active = db.Column(db.Boolean, nullable=False, default=True) @@ -1024,8 +1024,25 @@ class Usage(db.Model): project = db.relationship("Project", back_populates="monthly_usage") # Additional columns - usage = db.Column(db.Float, nullable=False, default=None) - cost = db.Column(db.Float, nullable=False, default=None) + usage = db.Column(db.Float, nullable=False) + cost = db.Column(db.Float, nullable=False) time_collected = db.Column( db.DateTime(), unique=False, nullable=False, default=dds_web.utils.current_time ) + + +class Maintenance(db.Model): + """ + Keep track of whether or not the DDS is in maintenance mode. + + Primary key: + - id + """ + + # Table setup + __tablename__ = "maintenance" + __table_args__ = {"extend_existing": True} + + # Columns + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + active = db.Column(db.Boolean, nullable=False, default=True) From aab05e1b6f74f70a7910853a089cf7814254f96e Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 15:50:49 +0200 Subject: [PATCH 085/111] migration --- .../versions/eb395af90e18_maintenance.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 migrations/versions/eb395af90e18_maintenance.py diff --git a/migrations/versions/eb395af90e18_maintenance.py b/migrations/versions/eb395af90e18_maintenance.py new file mode 100644 index 000000000..64ff06536 --- /dev/null +++ b/migrations/versions/eb395af90e18_maintenance.py @@ -0,0 +1,33 @@ +"""maintenance + +Revision ID: eb395af90e18 +Revises: 6e9b74878553 +Create Date: 2022-09-19 13:47:03.397931 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = "eb395af90e18" +down_revision = "6e9b74878553" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "maintenance", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("active", sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("maintenance") + # ### end Alembic commands ### From 090f6c8be017f603d3578bad71a1b273db7a2e82 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 16:46:04 +0200 Subject: [PATCH 086/111] set maintenance in init --- dds_web/__init__.py | 24 ++++++++++++------------ dds_web/database/models.py | 1 + docker-compose.yml | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 28387f1ad..14882e1b6 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -312,15 +312,20 @@ def load_user(user_id): def fill_db_wrapper(db_type): from dds_web.database import models - maintenance_rows: typing.List = models.Maintenance.query.all() + maintenance_active: bool = db_type == "production" + flask.current_app.logger.info(f"Setting maintenance as {'active' if maintenance_active else 'inactive'}...") + + old_maintenance: models.Maintenance = models.Maintenance.query.all() + if len(old_maintenance) > 1: + for row in old_maintenance[1::]: + db.session.delete(row) + old_maintenance[0].active = maintenance_active + else: + new_maintenance: models.Maintenance = models.Maintenance(active=maintenance_active) + db.session.add(new_maintenance) + db.session.commit() if db_type == "production": - # Verify that there's one maintenance row - if maintenance_rows: - maintenance_rows[0].active = True - db.session.delete(maintenance_rows[1::]) - db.session.commit() - # Fill rest username = flask.current_app.config["SUPERADMIN_USERNAME"] password = flask.current_app.config["SUPERADMIN_PASSWORD"] @@ -348,11 +353,6 @@ def fill_db_wrapper(db_type): db.session.commit() flask.current_app.logger.info(f"Super Admin added: {username} ({email})") else: - # Verify that there's one maintenance row - if maintenance_rows: - maintenance_rows[0].active = False - db.session.delete(maintenance_rows[1::]) - db.session.commit() flask.current_app.logger.info("Initializing development db") assert flask.current_app.config["USE_LOCAL_DB"] diff --git a/dds_web/database/models.py b/dds_web/database/models.py index 8569a21b8..319c9cf02 100644 --- a/dds_web/database/models.py +++ b/dds_web/database/models.py @@ -1046,3 +1046,4 @@ class Maintenance(db.Model): # Columns id = db.Column(db.Integer, primary_key=True, autoincrement=True) active = db.Column(db.Boolean, nullable=False, default=True) + diff --git a/docker-compose.yml b/docker-compose.yml index 11bb9a3a9..8d86815f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,7 +49,7 @@ services: context: ./ target: base working_dir: /code - command: sh -c "flask db upgrade && flask init-db $$DB_TYPE && flask run -h 0.0.0.0 -p 5000" + command: sh -c "flask db upgrade && flask init-db production && flask run -h 0.0.0.0 -p 5000" environment: - DDS_APP_CONFIG=/code/dds_web/sensitive/dds_app.cfg - FLASK_ENV=development From dc2870c33047b2977eaf6245bda9006fd9d9e4c1 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 16:56:08 +0200 Subject: [PATCH 087/111] tests for maintenance table --- docker-compose.yml | 2 +- tests/test_db_constraints.py | 564 ----------------------------------- 2 files changed, 1 insertion(+), 565 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8d86815f0..11bb9a3a9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,7 +49,7 @@ services: context: ./ target: base working_dir: /code - command: sh -c "flask db upgrade && flask init-db production && flask run -h 0.0.0.0 -p 5000" + command: sh -c "flask db upgrade && flask init-db $$DB_TYPE && flask run -h 0.0.0.0 -p 5000" environment: - DDS_APP_CONFIG=/code/dds_web/sensitive/dds_app.cfg - FLASK_ENV=development diff --git a/tests/test_db_constraints.py b/tests/test_db_constraints.py index c5f0ed847..e69de29bb 100644 --- a/tests/test_db_constraints.py +++ b/tests/test_db_constraints.py @@ -1,564 +0,0 @@ -# IMPORTS ################################################################################ IMPORTS # - -# Standard library - -# Installed -from typing import no_type_check -import pytest -import sqlalchemy - -# Own -from dds_web import db -from dds_web.database import models - -# TESTS #################################################################################### TESTS # - - -# Unit ###################################################################################### Unit # - - -def __setup_unit(): - unit = models.Unit.query.filter_by(name="Unit 1").first() - - projects = unit.projects - assert projects != [] - - # Need empty projects to test the correct constraint - for project in projects: - __delete_files_and_versions(project) - - unit_users = unit.users - assert unit_users != [] - - return unit, projects, unit_users - - -def __delete_files_and_versions(project): - for version in project.file_versions: - db.session.delete(version) - - for file in project.files: - db.session.delete(file) - - db.session.commit() - - -def test_delete_unit_row__with_project_and_users(client): - """ - Unit row deleted when project and users are not deleted. - - Error - Need to delete Project and UnitUser (due to inheritance issues) rows first - """ - unit, _, _ = __setup_unit() - - db.session.delete(unit) - with pytest.raises(sqlalchemy.exc.IntegrityError): - db.session.commit() - - -def test_delete_unit_row__with_users(client): - """ - Unit row deleted when users are not deleted. - - Error - Need to delete UnitUsers (due to inheritance issues) rows first - """ - unit, projects, _ = __setup_unit() - - for project in projects: - db.session.delete(project) - - db.session.delete(unit) - with pytest.raises(sqlalchemy.exc.IntegrityError): - db.session.commit() - - -def test_delete_unit_row(client): - """ - Unit row deleted when projects and users are deleted. - - Invite rows should be deleted - """ - - unit, projects, unit_users = __setup_unit() - - unit_id = unit.id - # Make sure unit has some invites - invites = unit.invites - assert invites != [] - - for project in projects: - db.session.delete(project) - - for user in unit_users: - db.session.delete(user) - db.session.commit() - - db.session.delete(unit) - db.session.commit() - - unit = models.Unit.query.filter_by(name="Unit 1").one_or_none() - assert unit is None - - # Make sure invites have been deleted - invites = models.Invite.query.filter_by(unit_id=unit_id).all() - assert invites == [] - - -# Project #################################################################################### Project # -def __setup_project(): - """ - Project with files and versions - """ - project = models.Project.query.filter_by(public_id="public_project_id").first() - - # Make sure the project is well connected: - assert project.files != [] - assert project.file_versions != [] - assert project.responsible_unit is not None - assert project.responsible_unit.users != [] - assert project.researchusers != [] - - statuses = models.ProjectStatuses.query.filter_by(project_id=project.id).all() - assert statuses != [] - project_users = models.ProjectUsers.query.filter_by(project_id=project.id).all() - assert project_users != [] - - return project - - -def test_delete_project_with_files_and_versions(client): - """ - Project row deleted - - Error - Need to delete File rows and Version rows first - """ - project = __setup_project() - - with pytest.raises(sqlalchemy.exc.IntegrityError): - db.session.delete(project) - db.session.commit() - - -def test_delete_project_with_files(client): - """ - Project row deleted - - Error - Need to delete File rows and Version rows first - """ - project = __setup_project() - - for version in project.file_versions: - db.session.delete(version) - db.session.commit() - - with pytest.raises(sqlalchemy.exc.IntegrityError): - db.session.delete(project) - db.session.commit() - - -def test_delete_project_with_versions(client): - """ - Project row deleted - - Error - Need to delete File rows and Version rows first - """ - project = __setup_project() - - for file in project.files: - db.session.delete(file) - db.session.commit() - - with pytest.raises(sqlalchemy.exc.IntegrityError): - db.session.delete(project) - db.session.commit() - - -def test_delete_project(client): - """ - - Project row deleted - - Unit row kept - User row kept - ProjectStatus rows deleted - ProjectUser rows deleted - """ - project = __setup_project() - - project_id = project.id - nr_users = models.User.query.count() - nr_units = models.Unit.query.count() - - for version in project.file_versions: - db.session.delete(version) - db.session.commit() - - for file in project.files: - db.session.delete(file) - db.session.commit() - - db.session.delete(project) - db.session.commit() - - exists = models.Project.query.filter_by(public_id="public_project_id").one_or_none() - assert exists is None - - # Make sure no users or units have been deleted - assert nr_users == models.User.query.count() - assert nr_units == models.Unit.query.count() - - statuses = models.ProjectStatuses.query.filter_by(project_id=project_id).all() - assert statuses == [] - project_users = models.ProjectUsers.query.filter_by(project_id=project_id).all() - assert project_users == [] - - -# User ########################################################################################## User # - - -def __setup_user(username): - user = models.User.query.filter_by(username=username).first() - - assert user.identifiers != [] - assert user.emails != [] - assert user.projects != [] - assert user.deletion_request is not None - - return user - - -def test_delete_user__researcher(client): - """ - User row deleted - - Identifier rows deleted - Email rows deleted - Project rows kept - DeletionRequest deleted - """ - username = "researchuser" - email_str = "researchuser@mailtrap.io" - user = __setup_user(username) - - project_ids = [project.id for project in user.projects] - nr_projects = len(project_ids) - - db.session.delete(user) - db.session.commit() - - exists = models.User.query.filter_by(username=username).one_or_none() - assert exists is None - - exists = models.ResearchUser.query.filter_by(username=username).one_or_none() - assert exists is None - - # Make sure identifiers are deleted - exists = models.Identifier.query.filter_by(username=username).one_or_none() - assert exists is None - - # Make sure emails are deleted - exists = models.Email.query.filter_by(email=email_str).one_or_none() - assert exists is None - - # Make sure projects are kept - project_ids_after = models.Project.query.filter(models.Project.id.in_(project_ids)).all() - assert len(project_ids_after) == nr_projects - - # Make sure deletion request is deleted - exists = models.DeletionRequest.query.filter_by(requester_id=username).one_or_none() - assert exists is None - - -def test_delete_user__unituser(client): - """ - User row deleted - - Identifier rows deleted - Email rows deleted - Project rows kept - DeletionRequest deleted - """ - username = "unituser" - email_str = "unituser1@mailtrap.io" - user = __setup_user(username) - - project_ids = [project.id for project in user.projects] - nr_projects = len(project_ids) - - db.session.delete(user) - db.session.commit() - - exists = models.User.query.filter_by(username=username).one_or_none() - assert exists is None - - exists = models.UnitUser.query.filter_by(username=username).one_or_none() - assert exists is None - - # Make sure identifiers are deleted - exists = models.Identifier.query.filter_by(username=username).one_or_none() - assert exists is None - - # Make sure emails are deleted - exists = models.Email.query.filter_by(email=email_str).one_or_none() - assert exists is None - - # Make sure projects are kept - project_ids_after = models.Project.query.filter(models.Project.id.in_(project_ids)).all() - assert len(project_ids_after) == nr_projects - - # Make sure deletion request is deleted - exists = models.DeletionRequest.query.filter_by(requester_id=username).one_or_none() - assert exists is None - - -def test_delete_user__superadmin(client): - """ - User row deleted - - Identifier rows deleted - Email rows deleted - Project rows kept - DeletionRequest deleted - """ - username = "superadmin" - email_str = "superadmin@mailtrap.io" - user = __setup_user(username) - - project_ids = [project.id for project in user.projects] - nr_projects = len(project_ids) - - db.session.delete(user) - db.session.commit() - - exists = models.User.query.filter_by(username=username).one_or_none() - assert exists is None - - exists = models.SuperAdmin.query.filter_by(username=username).one_or_none() - assert exists is None - - # Make sure identifiers are deleted - exists = models.Identifier.query.filter_by(username=username).one_or_none() - assert exists is None - - # Make sure emails are deleted - exists = models.Email.query.filter_by(email=email_str).one_or_none() - assert exists is None - - # Make sure projects are kept - project_ids_after = models.Project.query.filter(models.Project.id.in_(project_ids)).all() - assert len(project_ids_after) == nr_projects - - # Make sure deletion request is deleted - exists = models.DeletionRequest.query.filter_by(requester_id=username).one_or_none() - assert exists is None - - -# Identifier ########################################################################## Identifier # - - -def __setup_identifier(username): - identifier = models.Identifier.query.filter_by(username=username).first() - - assert identifier.user is not None - return identifier - - -def test_delete_identifier(client): - """ - Identifier row deleted - - User row kept - """ - username = "researchuser" - identifier = __setup_identifier(username) - - db.session.delete(identifier) - db.session.commit() - - exists = models.Identifier.query.filter_by(username=username).one_or_none() - assert exists is None - - exists = models.User.query.filter_by(username=username).one_or_none() - assert exists is not None - - -# Email #################################################################################### Email # - - -def __setup_email(username): - email = models.Email.query.filter_by(user_id=username).first() - - assert email.user is not None - return email - - -def test_delete_email(client): - """ - Email row deleted - - User row kept - """ - username = "researchuser" - email = __setup_email(username) - - db.session.delete(email) - db.session.commit() - - exists = models.Email.query.filter_by(user_id=username).one_or_none() - assert exists is None - - exists = models.User.query.filter_by(username=username).one_or_none() - assert exists is not None - - -# Invite ################################################################################## Invite # - - -def __setup_invite(unit_name, invite_email): - unit = models.Unit.query.filter_by(name=unit_name).first() - invite = models.Invite(email=invite_email, role="Researcher") - - unit.invites.append(invite) - - db.session.add(invite) - db.session.commit() - - invite = models.Invite.query.filter_by(email=invite_email).first() - assert invite is not None - assert invite.unit is not None - - return invite - - -def test_delete_invite(client): - """ - Invite row deleted - - Unit row kept - """ - unit_name = "Unit 1" - invite_email = "invite_email@example.com" - invite = __setup_invite(unit_name, invite_email) - - db.session.delete(invite) - db.session.commit() - - exists = models.Invite.query.filter_by(email=invite_email).first() - assert exists is None - - exists = models.Unit.query.filter_by(name=unit_name).first() - assert exists is not None - - -# DeletionRequest ################################################################ DeletionRequest # - - -def __setup_deletion_request(username): - deletion_request = models.DeletionRequest.query.filter_by(requester_id=username).first() - assert deletion_request is not None - assert deletion_request.requester is not None - return deletion_request - - -def test_delete_deletion_request(client): - """ - DeletionRequest row deleted - - User row kept - """ - username = "researchuser" - deletion_request = __setup_deletion_request(username) - - db.session.delete(deletion_request) - db.session.commit() - - exists = models.DeletionRequest.query.filter_by(requester_id=username).one_or_none() - assert exists is None - - exists = models.User.query.filter_by(username=username).one_or_none() - assert exists is not None - - -# File ###################################################################################### File # - - -def __setup_file(filename): - file = models.File.query.filter_by(name=filename).first() - - assert file is not None - assert file.project is not None - assert file.versions != [] - return file - - -def test_delete_file(client): - """ - File row deleted - - Project row kept - File versions kept - """ - filename = "filename1" - file = __setup_file(filename) - - project_id = file.project.id - version_ids = [version.id for version in file.versions] - - db.session.delete(file) - db.session.commit() - - exists = models.File.query.filter_by(name=filename).one_or_none() - assert exists is None - - exists = models.Project.query.filter_by(id=project_id).one_or_none() - assert exists is not None - - for version_id in version_ids: - exists = models.Version.query.get(version_id) - assert exists is not None - - -# Version ################################################################################ Version # - - -def __setup_version(file_id): - versions = models.Version.query.filter_by(active_file=file_id).all() - - assert len(versions) == 1 - version = versions[0] - assert version is not None - assert version.file is not None - assert version.project is not None - return version - - -def test_delete_version(client): - """ - Version row deleted - - File row kept - Project row kept - """ - filename = "filename2" - file = models.File.query.filter_by(name=filename).first() - file_id = file.id - version = __setup_version(file_id) - - project_id = version.project.id - - db.session.delete(version) - db.session.commit() - - exists = models.Version.query.filter_by(active_file=file_id).first() - assert exists is None - - exists = models.File.query.filter_by(id=file_id).one_or_none() - assert exists is not None - - exists = models.Project.query.filter_by(id=project_id).one_or_none() - assert exists is not None From 5ec68e5d6e3068724700fcd1d121deb6ba2773f6 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 16:56:19 +0200 Subject: [PATCH 088/111] tests --- tests/test_models.py | 596 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 596 insertions(+) create mode 100644 tests/test_models.py diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 000000000..9d4ccd8d5 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,596 @@ +# IMPORTS ################################################################################ IMPORTS # + +# Standard library + +# Installed +from typing import no_type_check +import pytest +import sqlalchemy +import flask + +# Own +from dds_web import db +from dds_web.database import models + +# TESTS #################################################################################### TESTS # + + +# Unit ###################################################################################### Unit # + + +def __setup_unit(): + unit = models.Unit.query.filter_by(name="Unit 1").first() + + projects = unit.projects + assert projects != [] + + # Need empty projects to test the correct constraint + for project in projects: + __delete_files_and_versions(project) + + unit_users = unit.users + assert unit_users != [] + + return unit, projects, unit_users + + +def __delete_files_and_versions(project): + for version in project.file_versions: + db.session.delete(version) + + for file in project.files: + db.session.delete(file) + + db.session.commit() + + +def test_delete_unit_row__with_project_and_users(client): + """ + Unit row deleted when project and users are not deleted. + + Error + Need to delete Project and UnitUser (due to inheritance issues) rows first + """ + unit, _, _ = __setup_unit() + + db.session.delete(unit) + with pytest.raises(sqlalchemy.exc.IntegrityError): + db.session.commit() + + +def test_delete_unit_row__with_users(client): + """ + Unit row deleted when users are not deleted. + + Error + Need to delete UnitUsers (due to inheritance issues) rows first + """ + unit, projects, _ = __setup_unit() + + for project in projects: + db.session.delete(project) + + db.session.delete(unit) + with pytest.raises(sqlalchemy.exc.IntegrityError): + db.session.commit() + + +def test_delete_unit_row(client): + """ + Unit row deleted when projects and users are deleted. + + Invite rows should be deleted + """ + + unit, projects, unit_users = __setup_unit() + + unit_id = unit.id + # Make sure unit has some invites + invites = unit.invites + assert invites != [] + + for project in projects: + db.session.delete(project) + + for user in unit_users: + db.session.delete(user) + db.session.commit() + + db.session.delete(unit) + db.session.commit() + + unit = models.Unit.query.filter_by(name="Unit 1").one_or_none() + assert unit is None + + # Make sure invites have been deleted + invites = models.Invite.query.filter_by(unit_id=unit_id).all() + assert invites == [] + + +# Project #################################################################################### Project # +def __setup_project(): + """ + Project with files and versions + """ + project = models.Project.query.filter_by(public_id="public_project_id").first() + + # Make sure the project is well connected: + assert project.files != [] + assert project.file_versions != [] + assert project.responsible_unit is not None + assert project.responsible_unit.users != [] + assert project.researchusers != [] + + statuses = models.ProjectStatuses.query.filter_by(project_id=project.id).all() + assert statuses != [] + project_users = models.ProjectUsers.query.filter_by(project_id=project.id).all() + assert project_users != [] + + return project + + +def test_delete_project_with_files_and_versions(client): + """ + Project row deleted + + Error + Need to delete File rows and Version rows first + """ + project = __setup_project() + + with pytest.raises(sqlalchemy.exc.IntegrityError): + db.session.delete(project) + db.session.commit() + + +def test_delete_project_with_files(client): + """ + Project row deleted + + Error + Need to delete File rows and Version rows first + """ + project = __setup_project() + + for version in project.file_versions: + db.session.delete(version) + db.session.commit() + + with pytest.raises(sqlalchemy.exc.IntegrityError): + db.session.delete(project) + db.session.commit() + + +def test_delete_project_with_versions(client): + """ + Project row deleted + + Error + Need to delete File rows and Version rows first + """ + project = __setup_project() + + for file in project.files: + db.session.delete(file) + db.session.commit() + + with pytest.raises(sqlalchemy.exc.IntegrityError): + db.session.delete(project) + db.session.commit() + + +def test_delete_project(client): + """ + + Project row deleted + + Unit row kept + User row kept + ProjectStatus rows deleted + ProjectUser rows deleted + """ + project = __setup_project() + + project_id = project.id + nr_users = models.User.query.count() + nr_units = models.Unit.query.count() + + for version in project.file_versions: + db.session.delete(version) + db.session.commit() + + for file in project.files: + db.session.delete(file) + db.session.commit() + + db.session.delete(project) + db.session.commit() + + exists = models.Project.query.filter_by(public_id="public_project_id").one_or_none() + assert exists is None + + # Make sure no users or units have been deleted + assert nr_users == models.User.query.count() + assert nr_units == models.Unit.query.count() + + statuses = models.ProjectStatuses.query.filter_by(project_id=project_id).all() + assert statuses == [] + project_users = models.ProjectUsers.query.filter_by(project_id=project_id).all() + assert project_users == [] + + +# User ########################################################################################## User # + + +def __setup_user(username): + user = models.User.query.filter_by(username=username).first() + + assert user.identifiers != [] + assert user.emails != [] + assert user.projects != [] + assert user.deletion_request is not None + + return user + + +def test_delete_user__researcher(client): + """ + User row deleted + + Identifier rows deleted + Email rows deleted + Project rows kept + DeletionRequest deleted + """ + username = "researchuser" + email_str = "researchuser@mailtrap.io" + user = __setup_user(username) + + project_ids = [project.id for project in user.projects] + nr_projects = len(project_ids) + + db.session.delete(user) + db.session.commit() + + exists = models.User.query.filter_by(username=username).one_or_none() + assert exists is None + + exists = models.ResearchUser.query.filter_by(username=username).one_or_none() + assert exists is None + + # Make sure identifiers are deleted + exists = models.Identifier.query.filter_by(username=username).one_or_none() + assert exists is None + + # Make sure emails are deleted + exists = models.Email.query.filter_by(email=email_str).one_or_none() + assert exists is None + + # Make sure projects are kept + project_ids_after = models.Project.query.filter(models.Project.id.in_(project_ids)).all() + assert len(project_ids_after) == nr_projects + + # Make sure deletion request is deleted + exists = models.DeletionRequest.query.filter_by(requester_id=username).one_or_none() + assert exists is None + + +def test_delete_user__unituser(client): + """ + User row deleted + + Identifier rows deleted + Email rows deleted + Project rows kept + DeletionRequest deleted + """ + username = "unituser" + email_str = "unituser1@mailtrap.io" + user = __setup_user(username) + + project_ids = [project.id for project in user.projects] + nr_projects = len(project_ids) + + db.session.delete(user) + db.session.commit() + + exists = models.User.query.filter_by(username=username).one_or_none() + assert exists is None + + exists = models.UnitUser.query.filter_by(username=username).one_or_none() + assert exists is None + + # Make sure identifiers are deleted + exists = models.Identifier.query.filter_by(username=username).one_or_none() + assert exists is None + + # Make sure emails are deleted + exists = models.Email.query.filter_by(email=email_str).one_or_none() + assert exists is None + + # Make sure projects are kept + project_ids_after = models.Project.query.filter(models.Project.id.in_(project_ids)).all() + assert len(project_ids_after) == nr_projects + + # Make sure deletion request is deleted + exists = models.DeletionRequest.query.filter_by(requester_id=username).one_or_none() + assert exists is None + + +def test_delete_user__superadmin(client): + """ + User row deleted + + Identifier rows deleted + Email rows deleted + Project rows kept + DeletionRequest deleted + """ + username = "superadmin" + email_str = "superadmin@mailtrap.io" + user = __setup_user(username) + + project_ids = [project.id for project in user.projects] + nr_projects = len(project_ids) + + db.session.delete(user) + db.session.commit() + + exists = models.User.query.filter_by(username=username).one_or_none() + assert exists is None + + exists = models.SuperAdmin.query.filter_by(username=username).one_or_none() + assert exists is None + + # Make sure identifiers are deleted + exists = models.Identifier.query.filter_by(username=username).one_or_none() + assert exists is None + + # Make sure emails are deleted + exists = models.Email.query.filter_by(email=email_str).one_or_none() + assert exists is None + + # Make sure projects are kept + project_ids_after = models.Project.query.filter(models.Project.id.in_(project_ids)).all() + assert len(project_ids_after) == nr_projects + + # Make sure deletion request is deleted + exists = models.DeletionRequest.query.filter_by(requester_id=username).one_or_none() + assert exists is None + + +# Identifier ########################################################################## Identifier # + + +def __setup_identifier(username): + identifier = models.Identifier.query.filter_by(username=username).first() + + assert identifier.user is not None + return identifier + + +def test_delete_identifier(client): + """ + Identifier row deleted + + User row kept + """ + username = "researchuser" + identifier = __setup_identifier(username) + + db.session.delete(identifier) + db.session.commit() + + exists = models.Identifier.query.filter_by(username=username).one_or_none() + assert exists is None + + exists = models.User.query.filter_by(username=username).one_or_none() + assert exists is not None + + +# Email #################################################################################### Email # + + +def __setup_email(username): + email = models.Email.query.filter_by(user_id=username).first() + + assert email.user is not None + return email + + +def test_delete_email(client): + """ + Email row deleted + + User row kept + """ + username = "researchuser" + email = __setup_email(username) + + db.session.delete(email) + db.session.commit() + + exists = models.Email.query.filter_by(user_id=username).one_or_none() + assert exists is None + + exists = models.User.query.filter_by(username=username).one_or_none() + assert exists is not None + + +# Invite ################################################################################## Invite # + + +def __setup_invite(unit_name, invite_email): + unit = models.Unit.query.filter_by(name=unit_name).first() + invite = models.Invite(email=invite_email, role="Researcher") + + unit.invites.append(invite) + + db.session.add(invite) + db.session.commit() + + invite = models.Invite.query.filter_by(email=invite_email).first() + assert invite is not None + assert invite.unit is not None + + return invite + + +def test_delete_invite(client): + """ + Invite row deleted + + Unit row kept + """ + unit_name = "Unit 1" + invite_email = "invite_email@example.com" + invite = __setup_invite(unit_name, invite_email) + + db.session.delete(invite) + db.session.commit() + + exists = models.Invite.query.filter_by(email=invite_email).first() + assert exists is None + + exists = models.Unit.query.filter_by(name=unit_name).first() + assert exists is not None + + +# DeletionRequest ################################################################ DeletionRequest # + + +def __setup_deletion_request(username): + deletion_request = models.DeletionRequest.query.filter_by(requester_id=username).first() + assert deletion_request is not None + assert deletion_request.requester is not None + return deletion_request + + +def test_delete_deletion_request(client): + """ + DeletionRequest row deleted + + User row kept + """ + username = "researchuser" + deletion_request = __setup_deletion_request(username) + + db.session.delete(deletion_request) + db.session.commit() + + exists = models.DeletionRequest.query.filter_by(requester_id=username).one_or_none() + assert exists is None + + exists = models.User.query.filter_by(username=username).one_or_none() + assert exists is not None + + +# File ###################################################################################### File # + + +def __setup_file(filename): + file = models.File.query.filter_by(name=filename).first() + + assert file is not None + assert file.project is not None + assert file.versions != [] + return file + + +def test_delete_file(client): + """ + File row deleted + + Project row kept + File versions kept + """ + filename = "filename1" + file = __setup_file(filename) + + project_id = file.project.id + version_ids = [version.id for version in file.versions] + + db.session.delete(file) + db.session.commit() + + exists = models.File.query.filter_by(name=filename).one_or_none() + assert exists is None + + exists = models.Project.query.filter_by(id=project_id).one_or_none() + assert exists is not None + + for version_id in version_ids: + exists = models.Version.query.get(version_id) + assert exists is not None + + +# Version ################################################################################ Version # + + +def __setup_version(file_id): + versions = models.Version.query.filter_by(active_file=file_id).all() + + assert len(versions) == 1 + version = versions[0] + assert version is not None + assert version.file is not None + assert version.project is not None + return version + + +def test_delete_version(client): + """ + Version row deleted + + File row kept + Project row kept + """ + filename = "filename2" + file = models.File.query.filter_by(name=filename).first() + file_id = file.id + version = __setup_version(file_id) + + project_id = version.project.id + + db.session.delete(version) + db.session.commit() + + exists = models.Version.query.filter_by(active_file=file_id).first() + assert exists is None + + exists = models.File.query.filter_by(id=file_id).one_or_none() + assert exists is not None + + exists = models.Project.query.filter_by(id=project_id).one_or_none() + assert exists is not None + + +# Initiating maintenance table + +def test_new_maintenance_active(client: flask.testing.FlaskClient) -> None: + """Create a new maintenance row.""" + # Verify no maintenance row yet + assert models.Maintenance.query.count() == 0 + + # Create row + new_maintenance_row: models.Maintenance = models.Maintenance() + db.session.add(new_maintenance_row) + db.session.commit() + + # Verify created + assert models.Maintenance.query.count() == 1 + assert models.Maintenance.query.first().active + +def test_new_maintenance_inactive(client: flask.testing.FlaskClient) -> None: + """Create a new maintenance row.""" + # Verify no maintenance row yet + assert models.Maintenance.query.count() == 0 + + # Create row + new_maintenance_row: models.Maintenance = models.Maintenance(active=False) + db.session.add(new_maintenance_row) + db.session.commit() + + # Verify created + assert models.Maintenance.query.count() == 1 + assert not models.Maintenance.query.first().active \ No newline at end of file From a292bfb1801d9dd2afecf76cee6e4bfd42391667 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 16:58:51 +0200 Subject: [PATCH 089/111] linting --- dds_web/__init__.py | 4 +++- dds_web/database/models.py | 1 - tests/test_models.py | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 14882e1b6..9577405cb 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -313,7 +313,9 @@ def fill_db_wrapper(db_type): from dds_web.database import models maintenance_active: bool = db_type == "production" - flask.current_app.logger.info(f"Setting maintenance as {'active' if maintenance_active else 'inactive'}...") + flask.current_app.logger.info( + f"Setting maintenance as {'active' if maintenance_active else 'inactive'}..." + ) old_maintenance: models.Maintenance = models.Maintenance.query.all() if len(old_maintenance) > 1: diff --git a/dds_web/database/models.py b/dds_web/database/models.py index 319c9cf02..8569a21b8 100644 --- a/dds_web/database/models.py +++ b/dds_web/database/models.py @@ -1046,4 +1046,3 @@ class Maintenance(db.Model): # Columns id = db.Column(db.Integer, primary_key=True, autoincrement=True) active = db.Column(db.Boolean, nullable=False, default=True) - diff --git a/tests/test_models.py b/tests/test_models.py index 9d4ccd8d5..651e6b23d 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -567,6 +567,7 @@ def test_delete_version(client): # Initiating maintenance table + def test_new_maintenance_active(client: flask.testing.FlaskClient) -> None: """Create a new maintenance row.""" # Verify no maintenance row yet @@ -581,6 +582,7 @@ def test_new_maintenance_active(client: flask.testing.FlaskClient) -> None: assert models.Maintenance.query.count() == 1 assert models.Maintenance.query.first().active + def test_new_maintenance_inactive(client: flask.testing.FlaskClient) -> None: """Create a new maintenance row.""" # Verify no maintenance row yet @@ -593,4 +595,4 @@ def test_new_maintenance_inactive(client: flask.testing.FlaskClient) -> None: # Verify created assert models.Maintenance.query.count() == 1 - assert not models.Maintenance.query.first().active \ No newline at end of file + assert not models.Maintenance.query.first().active From dc0cdceb72b59b7e65603e40499f2125e5257c87 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 17:00:16 +0200 Subject: [PATCH 090/111] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 419d74cec..8c0712d38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -144,3 +144,7 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - Change docker image to alpine ([#1272](https://github.com/ScilifelabDataCentre/dds_web/pull/1272)) - Added trivy when publishing to dockerhub ([#1276](https://github.com/ScilifelabDataCentre/dds_web/pull/1276)) - Bug fix: Cost value displayed by the --usage flag fixed ([#1274](https://github.com/ScilifelabDataCentre/dds_web/pull/1274)) + +## Sprint (2022-09-16 - 2022-09-30) + +- New table: `Maintenance`, for keeping track of DDS maintenance mode ([#1284](https://github.com/ScilifelabDataCentre/dds_web/pull/1284)) From a4fc6dd2c3131dc9a01a9628020400cd9319bfc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Mon, 19 Sep 2022 17:01:26 +0200 Subject: [PATCH 091/111] Update dds_web/__init__.py --- dds_web/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 9577405cb..d0c6be831 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -328,7 +328,6 @@ def fill_db_wrapper(db_type): db.session.commit() if db_type == "production": - # Fill rest username = flask.current_app.config["SUPERADMIN_USERNAME"] password = flask.current_app.config["SUPERADMIN_PASSWORD"] name = flask.current_app.config["SUPERADMIN_NAME"] From b6124f34729a179a6ccf9848203a51f6775c5a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Mon, 19 Sep 2022 17:05:20 +0200 Subject: [PATCH 092/111] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b60d18377..1d8d744f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -144,4 +144,7 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - Change docker image to alpine ([#1272](https://github.com/ScilifelabDataCentre/dds_web/pull/1272)) - Added trivy when publishing to dockerhub ([#1276](https://github.com/ScilifelabDataCentre/dds_web/pull/1276)) - Bug fix: Cost value displayed by the --usage flag fixed ([#1274](https://github.com/ScilifelabDataCentre/dds_web/pull/1274)) + +## Sprint (2022-09-16 - 2022-09-30) + - New endpoint: SendMOTD - send important information to users ([#1283](https://github.com/ScilifelabDataCentre/dds_web/pull/1283)) From e1a387793a8ee30a7669c5c24ce787aa152f3b3d Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 19 Sep 2022 17:00:16 +0200 Subject: [PATCH 093/111] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3be2e8e30..652104eeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -148,4 +148,6 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe ## Sprint (2022-09-16 - 2022-09-30) - New endpoint: SendMOTD - send important information to users ([#1283](https://github.com/ScilifelabDataCentre/dds_web/pull/1283)) + - New table: `Maintenance`, for keeping track of DDS maintenance mode ([#1284](https://github.com/ScilifelabDataCentre/dds_web/pull/1284)) + From 5265fecc0776f5209fb61c57ca1b48708584861e Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Tue, 20 Sep 2022 15:22:07 +0200 Subject: [PATCH 094/111] add endpoint and a class in superadmin_only --- dds_web/api/__init__.py | 1 + dds_web/api/superadmin_only.py | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/dds_web/api/__init__.py b/dds_web/api/__init__.py index 6614b5a4e..6eaf6075c 100644 --- a/dds_web/api/__init__.py +++ b/dds_web/api/__init__.py @@ -79,6 +79,7 @@ def output_json(data, code, headers=None): # Super Admins ###################################################################### Super Admins # +api.add_resource(superadmin_only.Maintenance, "/maintenance", endpoint="maintenance") api.add_resource(superadmin_only.AllUnits, "/unit/info/all", endpoint="all_units") api.add_resource(superadmin_only.MOTD, "/motd", endpoint="motd") api.add_resource(superadmin_only.SendMOTD, "/motd/send", endpoint="send_motd") diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 151aad972..68ddc4d61 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -245,3 +245,41 @@ def put(self): return { "message": f"TOTP has been deactivated for user: {user.username}. They can now use 2FA via email during authentication." } + + +class Maintenance(flask_restful.Resource): + """Change the maintenance mode of the system.""" + + @auth.login_required(role=["Super Admin"]) + @logging_bind_request + @json_required + @handle_db_error + def put(self): + """Change the Maintenance mode.""" + # Get desired maintenance mode + json_input = flask.request.json + command = json_input.get("state") + flask.current_app.logger.debug(f"command: {command}") + + # Get maintenance row from db + current_mode = models.Maintenance.query.first() + flask.current_app.logger.debug(f"current mode: {current_mode.active}") + # for m in current_mode: + if not current_mode: + raise ddserr.DDSArgumentError(message=f"Failed setting maintenance mode") + + # Activate maintenance if currently inactive + if command == "on": + if current_mode.active: + raise ddserr.DDSArgumentError(message=f"Maintenance mode already active") + else: + current_mode.actve = True + db.session.commit() + return {"message": "Maintenance mode activated."} + elif command == "off": + if not current_mode.active: + raise ddserr.DDSArgumentError(message=f"Maintenance mode already deactivated") + else: + current_mode.active = False + db.session.commit() + return {"message": "Maintenance mode deactivated."} From ca44d0dad9d09a5b335ccf5d0fa356ddfcd9bc25 Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Wed, 21 Sep 2022 08:45:35 +0200 Subject: [PATCH 095/111] fix changelog --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 652104eeb..3be2e8e30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -148,6 +148,4 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe ## Sprint (2022-09-16 - 2022-09-30) - New endpoint: SendMOTD - send important information to users ([#1283](https://github.com/ScilifelabDataCentre/dds_web/pull/1283)) - - New table: `Maintenance`, for keeping track of DDS maintenance mode ([#1284](https://github.com/ScilifelabDataCentre/dds_web/pull/1284)) - From 2c6281a8cf605101957333c605a0e790003a1f28 Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Wed, 21 Sep 2022 09:24:48 +0200 Subject: [PATCH 096/111] simplify endpoint --- dds_web/api/superadmin_only.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 68ddc4d61..a30f64556 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -259,27 +259,18 @@ def put(self): # Get desired maintenance mode json_input = flask.request.json command = json_input.get("state") - flask.current_app.logger.debug(f"command: {command}") # Get maintenance row from db current_mode = models.Maintenance.query.first() - flask.current_app.logger.debug(f"current mode: {current_mode.active}") # for m in current_mode: if not current_mode: raise ddserr.DDSArgumentError(message=f"Failed setting maintenance mode") # Activate maintenance if currently inactive - if command == "on": - if current_mode.active: - raise ddserr.DDSArgumentError(message=f"Maintenance mode already active") - else: - current_mode.actve = True - db.session.commit() - return {"message": "Maintenance mode activated."} - elif command == "off": - if not current_mode.active: - raise ddserr.DDSArgumentError(message=f"Maintenance mode already deactivated") - else: - current_mode.active = False - db.session.commit() - return {"message": "Maintenance mode deactivated."} + if command not in ["on", "off"]: + raise ddserr.DDSArgumentError(message=f"Please, specify the correct command: on or off") + + current_mode.active = command == "on" + db.session.commit() + + return {"message": f"Maintenance set to {command}"} From b142a09e04f4eb352a2fb1680f95ba270b40c107 Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Wed, 21 Sep 2022 10:29:48 +0200 Subject: [PATCH 097/111] adapt to changes in dds_cli --- dds_web/api/superadmin_only.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index a30f64556..4f3bb5829 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -258,7 +258,9 @@ def put(self): """Change the Maintenance mode.""" # Get desired maintenance mode json_input = flask.request.json - command = json_input.get("state") + setting = json_input.get("state") + if not setting: + raise ddserr.DDSArgumentError(message=f"on or off argument is required") # Get maintenance row from db current_mode = models.Maintenance.query.first() @@ -267,10 +269,12 @@ def put(self): raise ddserr.DDSArgumentError(message=f"Failed setting maintenance mode") # Activate maintenance if currently inactive - if command not in ["on", "off"]: - raise ddserr.DDSArgumentError(message=f"Please, specify the correct command: on or off") + if setting not in ["on", "off"]: + raise ddserr.DDSArgumentError( + message=f"Please, specify the correct argument: on or off" + ) - current_mode.active = command == "on" + current_mode.active = setting == "on" db.session.commit() - return {"message": f"Maintenance set to {command}"} + return {"message": f"Maintenance set to {setting}"} From 4bad479a02801a6e8115936ce3aa5df46be1d01b Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 21 Sep 2022 11:44:43 +0200 Subject: [PATCH 098/111] new technical overview to match the project id addition in datadelivery directory --- doc/Technical-Overview.pdf | Bin 2101044 -> 2103277 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/Technical-Overview.pdf b/doc/Technical-Overview.pdf index 544f4c447a50f7e65a1eafbe7ef0dd7438c3f75b..ee0675f2462520bfe825a70f7cc5626c918df912 100644 GIT binary patch delta 125716 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%0AC{TVhnyQuG~DU)_hcs{Kw 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 delta 123792 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

|{eWOxmN27NU7Q}Eq*>Y$s9~c?g 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 From 3d9bf3583d442b2c4c03e0cf50bb046a7d736be0 Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Wed, 21 Sep 2022 15:38:12 +0200 Subject: [PATCH 099/111] tests for Maintenance endpoint --- dds_web/api/superadmin_only.py | 1 - tests/__init__.py | 1 + tests/api/test_superadmin_only.py | 73 +++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 4f3bb5829..650df39c2 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -264,7 +264,6 @@ def put(self): # Get maintenance row from db current_mode = models.Maintenance.query.first() - # for m in current_mode: if not current_mode: raise ddserr.DDSArgumentError(message=f"Failed setting maintenance mode") diff --git a/tests/__init__.py b/tests/__init__.py index 07760be38..0973c0d08 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -219,6 +219,7 @@ class DDSEndpoint: INVOICE = BASE_ENDPOINT + "/invoice" # Superadmins only + MAINTENANCE = BASE_ENDPOINT + "/maintenance" LIST_UNITS_ALL = BASE_ENDPOINT + "/unit/info/all" MOTD = BASE_ENDPOINT + "/motd" MOTD_SEND = BASE_ENDPOINT + "/motd/send" diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index aa42859b5..dd17d6ae6 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -603,3 +603,76 @@ def test_send_motd_ok(client: flask.testing.FlaskClient) -> None: ) assert response.status_code == http.HTTPStatus.OK assert mock_mail_send.call_count == num_users + + +# Maintenance ###################################################################################### + + +def test_set_maintenance_not_superadmin(client: flask.testing.FlaskClient) -> None: + """Change Maintenance mode using everything but Super Admin access.""" + no_access_users: typing.Dict = users.copy() + no_access_users.pop("Super Admin") + + for u in no_access_users: + token: typing.Dict = get_token(username=users[u], client=client) + response: werkzeug.test.WrapperTestResponse = client.put( + tests.DDSEndpoint.MAINTENANCE, headers=token, json={"state": "on"} + ) + assert response.status_code == http.HTTPStatus.FORBIDDEN + + +def test_set_maintenance_incorrect_method(client: flask.testing.FlaskClient) -> None: + """Only put should be accepted.""" + # Authenticate + token: typing.Dict = get_token(username=users["Super Admin"], client=client) + + # Attempt request + for method in [client.get, client.post, client.delete, client.patch]: + response: werkzeug.test.WrapperTestResponse = method( + tests.DDSEndpoint.MAINTENANCE, headers=token, json={"state": "on"} + ) + assert response.status_code == http.HTTPStatus.METHOD_NOT_ALLOWED + + +def test_set_maintenance_no_json(client: flask.testing.FlaskClient) -> None: + """The request needs json in order to change Maintenance mode.""" + # Authenticate + token: typing.Dict = get_token(username=users["Super Admin"], client=client) + + # Attempt request + response: werkzeug.test.WrapperTestResponse = client.put( + tests.DDSEndpoint.MAINTENANCE, headers=token + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Required data missing from request" in response.json.get("message") + + +def test_set_maintenance_incorrect_state(client: flask.testing.FlaskClient) -> None: + """The json should be 'on' or 'off'.""" + # Authenticate + token: typing.Dict = get_token(username=users["Super Admin"], client=client) + + # Attempt request + response: werkzeug.test.WrapperTestResponse = client.put( + tests.DDSEndpoint.MAINTENANCE, headers=token, json={"state": "something"} + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Please, specify the correct argument: on or off" in response.json.get("message") + + +def test_set_maintenance_ok(client: flask.testing.FlaskClient) -> None: + """Set Maintenance mode to 'on'.""" + # Authenticate + token: typing.Dict = get_token(username=users["Super Admin"], client=client) + setting = "on" + + # create record in Maintenance + current_mode: models.Maintenance = models.Maintenance(active=False) + db.session.add(current_mode) + db.session.commit() + # Attempt request + response: werkzeug.test.WrapperTestResponse = client.put( + tests.DDSEndpoint.MAINTENANCE, headers=token, json={"state": setting} + ) + assert response.status_code == http.HTTPStatus.OK + assert f"Maintenance set to {setting}" in response.json.get("message") From 51a034b9820617990f40c937dfbbce34e9018798 Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Wed, 21 Sep 2022 15:42:46 +0200 Subject: [PATCH 100/111] review suggestions --- dds_web/api/superadmin_only.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 650df39c2..024c97e3c 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -260,7 +260,7 @@ def put(self): json_input = flask.request.json setting = json_input.get("state") if not setting: - raise ddserr.DDSArgumentError(message=f"on or off argument is required") + raise ddserr.DDSArgumentError(message="Please, specify the correct argument: on or off") # Get maintenance row from db current_mode = models.Maintenance.query.first() @@ -269,9 +269,7 @@ def put(self): # Activate maintenance if currently inactive if setting not in ["on", "off"]: - raise ddserr.DDSArgumentError( - message=f"Please, specify the correct argument: on or off" - ) + raise ddserr.DDSArgumentError(message="Please, specify the correct argument: on or off") current_mode.active = setting == "on" db.session.commit() From 54c8da163ddfad39ce52036412384ebc6caf4eaa Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Wed, 21 Sep 2022 15:47:43 +0200 Subject: [PATCH 101/111] more review changes and tests adjustments --- dds_web/api/superadmin_only.py | 2 +- tests/api/test_superadmin_only.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 024c97e3c..624cb7c8d 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -265,7 +265,7 @@ def put(self): # Get maintenance row from db current_mode = models.Maintenance.query.first() if not current_mode: - raise ddserr.DDSArgumentError(message=f"Failed setting maintenance mode") + raise ddserr.DDSArgumentError(message="There's no row in the Maintenance table.") # Activate maintenance if currently inactive if setting not in ["on", "off"]: diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index dd17d6ae6..d1c79180a 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -652,6 +652,10 @@ def test_set_maintenance_incorrect_state(client: flask.testing.FlaskClient) -> N # Authenticate token: typing.Dict = get_token(username=users["Super Admin"], client=client) + # create record in Maintenance + current_mode: models.Maintenance = models.Maintenance(active=False) + db.session.add(current_mode) + db.session.commit() # Attempt request response: werkzeug.test.WrapperTestResponse = client.put( tests.DDSEndpoint.MAINTENANCE, headers=token, json={"state": "something"} From dbd2fb3cb1ff359acee9a96526268afd4f72f95e Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Wed, 21 Sep 2022 17:03:42 +0200 Subject: [PATCH 102/111] and more tweaks --- dds_web/api/superadmin_only.py | 4 ++-- tests/api/test_superadmin_only.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 624cb7c8d..ec4a9d37c 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -260,7 +260,7 @@ def put(self): json_input = flask.request.json setting = json_input.get("state") if not setting: - raise ddserr.DDSArgumentError(message="Please, specify the correct argument: on or off") + raise ddserr.DDSArgumentError(message="Please, specify an argument: on or off") # Get maintenance row from db current_mode = models.Maintenance.query.first() @@ -274,4 +274,4 @@ def put(self): current_mode.active = setting == "on" db.session.commit() - return {"message": f"Maintenance set to {setting}"} + return {"message": f"Maintenance set to {setting.upper()}"} diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index d1c79180a..61e47b868 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -679,4 +679,4 @@ def test_set_maintenance_ok(client: flask.testing.FlaskClient) -> None: tests.DDSEndpoint.MAINTENANCE, headers=token, json={"state": setting} ) assert response.status_code == http.HTTPStatus.OK - assert f"Maintenance set to {setting}" in response.json.get("message") + assert f"Maintenance set to {setting.upper()}" in response.json.get("message") From b2f63eefe5879e476b1302d88e26933758516279 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 22 Sep 2022 12:02:02 +0200 Subject: [PATCH 103/111] added test for off --- dds_web/api/superadmin_only.py | 2 +- tests/api/test_superadmin_only.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index ec4a9d37c..80eb5f9ec 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -274,4 +274,4 @@ def put(self): current_mode.active = setting == "on" db.session.commit() - return {"message": f"Maintenance set to {setting.upper()}"} + return {"message": f"Maintenance set to: {setting.upper()}"} diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index 61e47b868..ae0b3d713 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -664,7 +664,7 @@ def test_set_maintenance_incorrect_state(client: flask.testing.FlaskClient) -> N assert "Please, specify the correct argument: on or off" in response.json.get("message") -def test_set_maintenance_ok(client: flask.testing.FlaskClient) -> None: +def test_set_maintenance_on_ok(client: flask.testing.FlaskClient) -> None: """Set Maintenance mode to 'on'.""" # Authenticate token: typing.Dict = get_token(username=users["Super Admin"], client=client) @@ -674,9 +674,34 @@ def test_set_maintenance_ok(client: flask.testing.FlaskClient) -> None: current_mode: models.Maintenance = models.Maintenance(active=False) db.session.add(current_mode) db.session.commit() + + # Verify that maintenance is off + assert models.Maintenance.query.first().active is False + + # Attempt request + response: werkzeug.test.WrapperTestResponse = client.put( + tests.DDSEndpoint.MAINTENANCE, headers=token, json={"state": setting} + ) + assert response.status_code == http.HTTPStatus.OK + assert f"Maintenance set to: {setting.upper()}" in response.json.get("message") + +def test_set_maintenance_off_ok(client: flask.testing.FlaskClient) -> None: + """Set Maintenance mode to 'off'.""" + # Authenticate + token: typing.Dict = get_token(username=users["Super Admin"], client=client) + setting = "off" + + # create record in Maintenance + current_mode: models.Maintenance = models.Maintenance(active=True) + db.session.add(current_mode) + db.session.commit() + + # Verify that maintenance is on + assert models.Maintenance.query.first().active + # Attempt request response: werkzeug.test.WrapperTestResponse = client.put( tests.DDSEndpoint.MAINTENANCE, headers=token, json={"state": setting} ) assert response.status_code == http.HTTPStatus.OK - assert f"Maintenance set to {setting.upper()}" in response.json.get("message") + assert f"Maintenance set to: {setting.upper()}" in response.json.get("message") \ No newline at end of file From 67810da94fab74ba513991c8c33d9420e34d384f Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 22 Sep 2022 12:05:25 +0200 Subject: [PATCH 104/111] changelog and linting --- CHANGELOG.md | 1 + dds_web/api/__init__.py | 2 +- dds_web/api/superadmin_only.py | 2 +- tests/api/test_superadmin_only.py | 3 ++- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3be2e8e30..1fa81338d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -149,3 +149,4 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - New endpoint: SendMOTD - send important information to users ([#1283](https://github.com/ScilifelabDataCentre/dds_web/pull/1283)) - New table: `Maintenance`, for keeping track of DDS maintenance mode ([#1284](https://github.com/ScilifelabDataCentre/dds_web/pull/1284)) +- New endpoint: SetMaintenance - set maintenance mode to on or off ([#1286](https://github.com/ScilifelabDataCentre/dds_web/pull/1286)) diff --git a/dds_web/api/__init__.py b/dds_web/api/__init__.py index 6eaf6075c..35034e8eb 100644 --- a/dds_web/api/__init__.py +++ b/dds_web/api/__init__.py @@ -79,7 +79,7 @@ def output_json(data, code, headers=None): # Super Admins ###################################################################### Super Admins # -api.add_resource(superadmin_only.Maintenance, "/maintenance", endpoint="maintenance") +api.add_resource(superadmin_only.SetMaintenance, "/maintenance", endpoint="maintenance") api.add_resource(superadmin_only.AllUnits, "/unit/info/all", endpoint="all_units") api.add_resource(superadmin_only.MOTD, "/motd", endpoint="motd") api.add_resource(superadmin_only.SendMOTD, "/motd/send", endpoint="send_motd") diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 80eb5f9ec..31a66c66d 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -247,7 +247,7 @@ def put(self): } -class Maintenance(flask_restful.Resource): +class SetMaintenance(flask_restful.Resource): """Change the maintenance mode of the system.""" @auth.login_required(role=["Super Admin"]) diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index ae0b3d713..3e23a6d37 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -685,6 +685,7 @@ def test_set_maintenance_on_ok(client: flask.testing.FlaskClient) -> None: assert response.status_code == http.HTTPStatus.OK assert f"Maintenance set to: {setting.upper()}" in response.json.get("message") + def test_set_maintenance_off_ok(client: flask.testing.FlaskClient) -> None: """Set Maintenance mode to 'off'.""" # Authenticate @@ -704,4 +705,4 @@ def test_set_maintenance_off_ok(client: flask.testing.FlaskClient) -> None: tests.DDSEndpoint.MAINTENANCE, headers=token, json={"state": setting} ) assert response.status_code == http.HTTPStatus.OK - assert f"Maintenance set to: {setting.upper()}" in response.json.get("message") \ No newline at end of file + assert f"Maintenance set to: {setting.upper()}" in response.json.get("message") From e2c5ea75c1ed41a6de2a75af61b783738757be01 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 22 Sep 2022 12:46:34 +0200 Subject: [PATCH 105/111] endpoint and tests --- dds_web/api/__init__.py | 1 + dds_web/api/superadmin_only.py | 10 +++++ tests/__init__.py | 1 + tests/api/test_superadmin_only.py | 63 +++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+) diff --git a/dds_web/api/__init__.py b/dds_web/api/__init__.py index 35034e8eb..0ebc5aeaf 100644 --- a/dds_web/api/__init__.py +++ b/dds_web/api/__init__.py @@ -87,6 +87,7 @@ def output_json(data, code, headers=None): api.add_resource( superadmin_only.ResetTwoFactor, "/user/totp/deactivate", endpoint="reset_user_hotp" ) +api.add_resource(superadmin_only.AnyProjectsBusy, "/proj/busy/any", endpoint="projects_busy_any") # Invoicing ############################################################################ Invoicing # api.add_resource(user.ShowUsage, "/usage", endpoint="usage") diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 31a66c66d..baae53321 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -275,3 +275,13 @@ def put(self): db.session.commit() return {"message": f"Maintenance set to: {setting.upper()}"} + +class AnyProjectsBusy(flask_restful.Resource): + """Check if any projects are busy.""" + @auth.login_required(role=["Super Admin"]) + @logging_bind_request + @handle_db_error + def get(self): + """Check if any projects are busy.""" + num_busy: int = models.Project.query.filter_by(busy=True).count() + return {"num": num_busy} \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index 0973c0d08..03b907c80 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -199,6 +199,7 @@ class DDSEndpoint: PROJECT_STATUS = BASE_ENDPOINT + "/proj/status" PROJECT_ACCESS = BASE_ENDPOINT + "/proj/access" PROJECT_BUSY = BASE_ENDPOINT + "/proj/busy" + PROJECT_BUSY_ANY = BASE_ENDPOINT + "/proj/busy/any" # Listing urls LIST_PROJ = BASE_ENDPOINT + "/proj/list" diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index 3e23a6d37..cbd58bb7f 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -706,3 +706,66 @@ def test_set_maintenance_off_ok(client: flask.testing.FlaskClient) -> None: ) assert response.status_code == http.HTTPStatus.OK assert f"Maintenance set to: {setting.upper()}" in response.json.get("message") + +# AnyProjectsBusy + +# -- get + +def test_anyprojectsbusy_no_token(client: flask.testing.FlaskClient) -> None: + """Token required to check if projects are busy.""" + response = client.get(tests.DDSEndpoint.PROJECT_BUSY_ANY, headers=tests.DEFAULT_HEADER) + assert response.status_code == http.HTTPStatus.UNAUTHORIZED + assert response.json.get("message") + assert "No token" in response.json.get("message") + +def test_anyprojectsbusy_not_allowed(client: flask.testing.FlaskClient) -> None: + """Only super admins allowed.""" + for role in ["researcher", "unituser", "unitadmin"]: + token = tests.UserAuth(tests.USER_CREDENTIALS[role]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY_ANY, + headers=token, + ) + assert response.status_code == http.HTTPStatus.FORBIDDEN + +def test_anyprojectsbusy_true(client: flask.testing.FlaskClient) -> None: + """There are busy projects.""" + # Get a project and set to busy + project: models.Project = models.Project.query.first() + project.busy = True + db.session.commit() + busy_count: int = models.Project.query.filter_by(busy=True).count() + assert busy_count > 0 + + # Call endpoint + token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY_ANY, + headers=token, + ) + assert response.status_code == http.HTTPStatus.OK + + # Check response + num: int = response.json.get("num") + assert num == busy_count + +def test_anyprojectsbusy_false(client: flask.testing.FlaskClient) -> None: + """There are busy projects.""" + # Set all projects to not busy + for project in models.Project.query.all(): + project.busy = True + db.session.commit() + busy_count: int = models.Project.query.filter_by(busy=True).count() + assert busy_count == 0 + + # Call endpoint + token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY_ANY, + headers=token, + ) + assert response.status_code == http.HTTPStatus.OK + + # Check response + num: int = response.json.get("num") + assert num == 0 From 98c5776347c11ffbbe5a29f14c680a214b163c27 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 22 Sep 2022 12:47:16 +0200 Subject: [PATCH 106/111] linting --- dds_web/api/superadmin_only.py | 4 +++- tests/api/test_superadmin_only.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index baae53321..6f530cfbd 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -276,12 +276,14 @@ def put(self): return {"message": f"Maintenance set to: {setting.upper()}"} + class AnyProjectsBusy(flask_restful.Resource): """Check if any projects are busy.""" + @auth.login_required(role=["Super Admin"]) @logging_bind_request @handle_db_error def get(self): """Check if any projects are busy.""" num_busy: int = models.Project.query.filter_by(busy=True).count() - return {"num": num_busy} \ No newline at end of file + return {"num": num_busy} diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index cbd58bb7f..1155e763b 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -707,9 +707,11 @@ def test_set_maintenance_off_ok(client: flask.testing.FlaskClient) -> None: assert response.status_code == http.HTTPStatus.OK assert f"Maintenance set to: {setting.upper()}" in response.json.get("message") + # AnyProjectsBusy -# -- get +# -- get + def test_anyprojectsbusy_no_token(client: flask.testing.FlaskClient) -> None: """Token required to check if projects are busy.""" @@ -718,6 +720,7 @@ def test_anyprojectsbusy_no_token(client: flask.testing.FlaskClient) -> None: assert response.json.get("message") assert "No token" in response.json.get("message") + def test_anyprojectsbusy_not_allowed(client: flask.testing.FlaskClient) -> None: """Only super admins allowed.""" for role in ["researcher", "unituser", "unitadmin"]: @@ -728,6 +731,7 @@ def test_anyprojectsbusy_not_allowed(client: flask.testing.FlaskClient) -> None: ) assert response.status_code == http.HTTPStatus.FORBIDDEN + def test_anyprojectsbusy_true(client: flask.testing.FlaskClient) -> None: """There are busy projects.""" # Get a project and set to busy @@ -744,11 +748,12 @@ def test_anyprojectsbusy_true(client: flask.testing.FlaskClient) -> None: headers=token, ) assert response.status_code == http.HTTPStatus.OK - + # Check response num: int = response.json.get("num") assert num == busy_count + def test_anyprojectsbusy_false(client: flask.testing.FlaskClient) -> None: """There are busy projects.""" # Set all projects to not busy @@ -765,7 +770,7 @@ def test_anyprojectsbusy_false(client: flask.testing.FlaskClient) -> None: headers=token, ) assert response.status_code == http.HTTPStatus.OK - + # Check response num: int = response.json.get("num") assert num == 0 From d1cf46db162efbb7878e3da5adc322df15555609 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 22 Sep 2022 13:12:21 +0200 Subject: [PATCH 107/111] black and additional test --- dds_web/api/superadmin_only.py | 20 +++++++++++-- tests/api/test_superadmin_only.py | 48 +++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 6f530cfbd..752f838f2 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -6,6 +6,7 @@ # Standard library import os +import typing # Installed import flask_restful @@ -285,5 +286,20 @@ class AnyProjectsBusy(flask_restful.Resource): @handle_db_error def get(self): """Check if any projects are busy.""" - num_busy: int = models.Project.query.filter_by(busy=True).count() - return {"num": num_busy} + # Get busy projects + projects_busy: typing.List = models.Project.query.filter_by(busy=True).all() + num_busy: int = len(projects_busy) + + # Set info to always return nu + return_info: typing.Dict = {"num": num_busy} + + # Return 0 if none are busy + if num_busy == 0: + return return_info + + # Check if user listing busy projects + json_input = flask.request.json + if json_input and json_input.get("list") is True: + return_info.update({"projects": {p.public_id: p.date_updated for p in projects_busy}}) + + return return_info diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index 1155e763b..52493eee9 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -774,3 +774,51 @@ def test_anyprojectsbusy_false(client: flask.testing.FlaskClient) -> None: # Check response num: int = response.json.get("num") assert num == 0 + + +def test_anyprojectsbusy_true_list(client: flask.testing.FlaskClient) -> None: + """There are busy projects, list them.""" + wanted_return_info: typing.Dict = {} + + # Get all projects and set to busy + all_projects: typing.List = models.Project.query.all() + for project in all_projects: + project.busy = True + wanted_return_info[project.public_id] = project.date_updated + db.session.commit() + busy_count: int = models.Project.query.filter_by(busy=True).count() + assert busy_count == len(all_projects) + + # Call endpoint + token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) + response = client.put(tests.DDSEndpoint.PROJECT_BUSY_ANY, headers=token, json={"list": True}) + assert response.status_code == http.HTTPStatus.OK + + # Check response + num: int = response.json.get("num") + assert num == len(all_projects) + projects_returned: typing.Dict = response.json.get("projects") + for p in wanted_return_info: + assert p in projects_returned + + +def test_anyprojectsbusy_false_list(client: flask.testing.FlaskClient) -> None: + """There are busy projects.""" + # Get all projects and set to not busy + all_projects: typing.List = models.Project.query.all() + for project in all_projects: + project.busy = False + db.session.commit() + busy_count: int = models.Project.query.filter_by(busy=True).count() + assert busy_count == 0 + + # Call endpoint + token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) + response = client.put(tests.DDSEndpoint.PROJECT_BUSY_ANY, headers=token, json={"list": True}) + assert response.status_code == http.HTTPStatus.OK + + # Check response + num: int = response.json.get("num") + assert num == 0 + projects_returned: typing.Dict = response.json.get("projects") + assert projects_returned is None From cca1f03befd5c9e2638524ac71ccc6d130a7a57d Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 22 Sep 2022 13:13:35 +0200 Subject: [PATCH 108/111] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fa81338d..253cd3fcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -150,3 +150,4 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - New endpoint: SendMOTD - send important information to users ([#1283](https://github.com/ScilifelabDataCentre/dds_web/pull/1283)) - New table: `Maintenance`, for keeping track of DDS maintenance mode ([#1284](https://github.com/ScilifelabDataCentre/dds_web/pull/1284)) - New endpoint: SetMaintenance - set maintenance mode to on or off ([#1286](https://github.com/ScilifelabDataCentre/dds_web/pull/1286)) +- New endpoint: AnyProjectsBusy - check if any projects are busy in DDS ([#1288](https://github.com/ScilifelabDataCentre/dds_web/pull/1288)) From c8547fbf779277581d1a2a11bf116c12dd40e4d4 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 22 Sep 2022 13:30:19 +0200 Subject: [PATCH 109/111] incorrect method --- tests/api/test_superadmin_only.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index 52493eee9..24f50d0f9 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -725,7 +725,7 @@ def test_anyprojectsbusy_not_allowed(client: flask.testing.FlaskClient) -> None: """Only super admins allowed.""" for role in ["researcher", "unituser", "unitadmin"]: token = tests.UserAuth(tests.USER_CREDENTIALS[role]).token(client) - response = client.put( + response = client.get( tests.DDSEndpoint.PROJECT_BUSY_ANY, headers=token, ) @@ -743,7 +743,7 @@ def test_anyprojectsbusy_true(client: flask.testing.FlaskClient) -> None: # Call endpoint token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) - response = client.put( + response = client.get( tests.DDSEndpoint.PROJECT_BUSY_ANY, headers=token, ) @@ -765,7 +765,7 @@ def test_anyprojectsbusy_false(client: flask.testing.FlaskClient) -> None: # Call endpoint token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) - response = client.put( + response = client.get( tests.DDSEndpoint.PROJECT_BUSY_ANY, headers=token, ) @@ -791,7 +791,7 @@ def test_anyprojectsbusy_true_list(client: flask.testing.FlaskClient) -> None: # Call endpoint token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) - response = client.put(tests.DDSEndpoint.PROJECT_BUSY_ANY, headers=token, json={"list": True}) + response = client.get(tests.DDSEndpoint.PROJECT_BUSY_ANY, headers=token, json={"list": True}) assert response.status_code == http.HTTPStatus.OK # Check response @@ -814,7 +814,7 @@ def test_anyprojectsbusy_false_list(client: flask.testing.FlaskClient) -> None: # Call endpoint token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) - response = client.put(tests.DDSEndpoint.PROJECT_BUSY_ANY, headers=token, json={"list": True}) + response = client.get(tests.DDSEndpoint.PROJECT_BUSY_ANY, headers=token, json={"list": True}) assert response.status_code == http.HTTPStatus.OK # Check response From 01c813d4a42668670a90833076503b97c02666c5 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 22 Sep 2022 13:45:01 +0200 Subject: [PATCH 110/111] error in tests --- tests/api/test_superadmin_only.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index 24f50d0f9..94a40eae6 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -758,7 +758,7 @@ def test_anyprojectsbusy_false(client: flask.testing.FlaskClient) -> None: """There are busy projects.""" # Set all projects to not busy for project in models.Project.query.all(): - project.busy = True + project.busy = False db.session.commit() busy_count: int = models.Project.query.filter_by(busy=True).count() assert busy_count == 0 From 4506797938a4c2b3a1b6430713b6aa7f2f64d0c2 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 26 Sep 2022 15:56:50 +0200 Subject: [PATCH 111/111] version --- dds_web/version.py | 2 +- tests/test_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dds_web/version.py b/dds_web/version.py index 72f26f596..8c0d5d5bb 100644 --- a/dds_web/version.py +++ b/dds_web/version.py @@ -1 +1 @@ -__version__ = "1.1.2" +__version__ = "2.0.0" diff --git a/tests/test_version.py b/tests/test_version.py index be2d10076..1293d782b 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -2,4 +2,4 @@ def test_version(): - assert version.__version__ == "1.1.2" + assert version.__version__ == "2.0.0"