From 66528faed552308515a933d4756a0657dd490840 Mon Sep 17 00:00:00 2001 From: Odysseus Chiu Date: Thu, 9 Jan 2025 08:33:04 -0800 Subject: [PATCH] 25143 - remove product / add previously approved product support (#3200) Co-authored-by: Travis Semple --- auth-api/src/auth_api/exceptions/errors.py | 1 + .../auth_api/models/product_subscription.py | 3 +- auth-api/src/auth_api/models/task.py | 21 +- .../src/auth_api/resources/v1/org_products.py | 34 ++- auth-api/src/auth_api/services/products.py | 70 ++++- .../tests/unit/api/test_cors_preflight.py | 4 + auth-api/tests/unit/api/test_org_products.py | 283 ++++++++++++++++++ queue_services/account-mailer/poetry.lock | 2 +- queue_services/auth-queue/poetry.lock | 2 +- 9 files changed, 400 insertions(+), 20 deletions(-) diff --git a/auth-api/src/auth_api/exceptions/errors.py b/auth-api/src/auth_api/exceptions/errors.py index 8e349a187e..a4f6b61d66 100644 --- a/auth-api/src/auth_api/exceptions/errors.py +++ b/auth-api/src/auth_api/exceptions/errors.py @@ -23,6 +23,7 @@ class Error(Enum): """Error Codes.""" + INVALID_ORG = "The organization ID is in an incorrect format.", HTTPStatus.BAD_REQUEST INVALID_INPUT = "Invalid input, please check.", HTTPStatus.BAD_REQUEST DATA_NOT_FOUND = "No matching record found.", HTTPStatus.NOT_FOUND DATA_ALREADY_EXISTS = "The data you want to insert already exists.", HTTPStatus.BAD_REQUEST diff --git a/auth-api/src/auth_api/models/product_subscription.py b/auth-api/src/auth_api/models/product_subscription.py index 64482456f0..b300d2404e 100644 --- a/auth-api/src/auth_api/models/product_subscription.py +++ b/auth-api/src/auth_api/models/product_subscription.py @@ -15,6 +15,7 @@ The ProductSubscription object connects Org models to one or more ProductSubscription models. """ +from typing import Self from sql_versioning import Versioned from sqlalchemy import Column, ForeignKey, Integer, and_ @@ -45,7 +46,7 @@ def find_by_org_ids(cls, org_ids, valid_statuses=VALID_SUBSCRIPTION_STATUSES): ).all() @classmethod - def find_by_org_id_product_code(cls, org_id: int, product_code, valid_statuses=VALID_SUBSCRIPTION_STATUSES): + def find_by_org_id_product_code(cls, org_id: int, product_code, valid_statuses=VALID_SUBSCRIPTION_STATUSES) -> Self: """Find an product subscription instance that matches the provided id.""" return cls.query.filter( and_( diff --git a/auth-api/src/auth_api/models/task.py b/auth-api/src/auth_api/models/task.py index 0badd670ec..120f1780b5 100644 --- a/auth-api/src/auth_api/models/task.py +++ b/auth-api/src/auth_api/models/task.py @@ -13,6 +13,7 @@ # limitations under the License. """This model manages a Task item in the Auth Service.""" import datetime as dt +from typing import Self import pytz from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, text @@ -93,14 +94,14 @@ def fetch_tasks(cls, task_search: TaskSearch): return pagination.items, pagination.total @classmethod - def find_by_task_id(cls, task_id: int): + def find_by_task_id(cls, task_id: int) -> Self: """Find a task instance that matches the provided id.""" return db.session.query(Task).filter_by(id=int(task_id or -1)).first() @classmethod def find_by_task_relationship_id( cls, relationship_id: int, task_relationship_type: str, task_status: str = TaskStatus.OPEN.value - ): + ) -> Self: """Find a task instance that related to the relationship id ( may be an ORG or a PRODUCT.""" return ( db.session.query(Task) @@ -112,6 +113,22 @@ def find_by_task_relationship_id( .first() ) + @classmethod + def find_by_incomplete_task_relationship_id( + cls, relationship_id: int, task_relationship_type: str, relationship_status: str = None + ) -> Self: + """Find a task instance that related to the relationship id ( may be an ORG or a PRODUCT) that is incomplete.""" + query = db.session.query(Task).filter( + Task.relationship_id == int(relationship_id or -1), + Task.relationship_type == task_relationship_type, + Task.status.in_((TaskStatus.OPEN.value, TaskStatus.HOLD.value)), + ) + + if relationship_status is not None: + query = query.filter(Task.relationship_status == relationship_status) + + return query.first() + @classmethod def find_by_task_for_account(cls, org_id: int, status): """Find a task instance that matches the provided id.""" diff --git a/auth-api/src/auth_api/resources/v1/org_products.py b/auth-api/src/auth_api/resources/v1/org_products.py index acfc9e87fe..aabb7eabc1 100644 --- a/auth-api/src/auth_api/resources/v1/org_products.py +++ b/auth-api/src/auth_api/resources/v1/org_products.py @@ -18,7 +18,7 @@ from flask import Blueprint, g, request from flask_cors import cross_origin -from auth_api.exceptions import BusinessException +from auth_api.exceptions import BusinessException, Error from auth_api.schemas import utils as schema_utils from auth_api.services import Product as ProductService from auth_api.utils.auth import jwt as _jwt @@ -34,10 +34,8 @@ def get_org_product_subscriptions(org_id): """GET a new product subscription to the org using the request body.""" - if not org_id or org_id == "None" or not org_id.isdigit() or int(org_id) < 0: - return {"message": "The organization ID is in an incorrect format."}, HTTPStatus.BAD_REQUEST - try: + validate_organization(org_id) include_hidden = request.args.get("include_hidden", None) == "true" # used by NDS response, status = ( json.dumps(ProductService.get_all_product_subscription(org_id=int(org_id), include_hidden=include_hidden)), @@ -54,15 +52,13 @@ def get_org_product_subscriptions(org_id): def post_org_product_subscription(org_id): """Post a new product subscription to the org using the request body.""" - if not org_id or org_id == "None" or not org_id.isdigit() or int(org_id) < 0: - return {"message": "The organization ID is in an incorrect format."}, HTTPStatus.BAD_REQUEST - request_json = request.get_json() valid_format, errors = schema_utils.validate(request_json, "org_product_subscription") if not valid_format: return {"message": schema_utils.serialize(errors)}, HTTPStatus.BAD_REQUEST try: + validate_organization(org_id) roles = g.jwt_oidc_token_info.get("realm_access").get("roles") subscriptions = ProductService.create_product_subscription( int(org_id), request_json, skip_auth=Role.SYSTEM.value in roles, auto_approve=Role.SYSTEM.value in roles @@ -80,17 +76,35 @@ def post_org_product_subscription(org_id): def patch_org_product_subscription(org_id): """Patch existing product subscription to resubmit it for review.""" - if not org_id or org_id == "None" or not org_id.isdigit() or int(org_id) < 0: - return {"message": "The organization ID is in an incorrect format."}, HTTPStatus.BAD_REQUEST - request_json = request.get_json() valid_format, errors = schema_utils.validate(request_json, "org_product_subscription") if not valid_format: return {"message": schema_utils.serialize(errors)}, HTTPStatus.BAD_REQUEST try: + validate_organization(org_id) subscriptions = ProductService.resubmit_product_subscription(int(org_id), request_json) response, status = {"subscriptions": subscriptions}, HTTPStatus.OK except BusinessException as exception: response, status = {"code": exception.code, "message": exception.message}, exception.status_code return response, status + + +@bp.route("/", methods=["DELETE", "OPTIONS"]) +@cross_origin(origins="*", methods=["DELETE"]) +@_jwt.has_one_of_roles([Role.STAFF_CREATE_ACCOUNTS.value, Role.PUBLIC_USER.value, Role.SYSTEM.value]) +def delete_product_subscription(org_id, product_code): + """Delete existing product subscription.""" + + try: + validate_organization(org_id) + subscriptions = ProductService.remove_product_subscription(int(org_id), product_code) + response, status = {"subscriptions": subscriptions}, HTTPStatus.OK + except BusinessException as exception: + response, status = {"code": exception.code, "message": exception.message}, exception.status_code + return response, status + + +def validate_organization(org_id): + if not org_id or org_id == "None" or not org_id.isdigit() or int(org_id) < 0: + raise BusinessException(Error.INVALID_ORG, None) diff --git a/auth-api/src/auth_api/services/products.py b/auth-api/src/auth_api/services/products.py index e078dc3fe9..01827bbb6b 100644 --- a/auth-api/src/auth_api/services/products.py +++ b/auth-api/src/auth_api/services/products.py @@ -143,6 +143,26 @@ def resubmit_product_subscription(org_id, subscription_data: Dict[str, Any], ski return Product.get_all_product_subscription(org_id=org_id, skip_auth=True) + @staticmethod + def _is_previously_approved(org_id: int, product_code: str): + """Check if this product has a task that was previously approved.""" + inactive_sub = ProductSubscriptionModel.find_by_org_id_product_code( + org_id=org_id, product_code=product_code, valid_statuses=(ProductSubscriptionStatus.INACTIVE.value,) + ) + if not inactive_sub: + return False, None + + task = TaskModel.find_by_task_relationship_id( + inactive_sub.id, TaskRelationshipType.PRODUCT.value, TaskStatus.COMPLETED.value + ) + if task is None or ( + task.relationship_status != TaskRelationshipStatus.ACTIVE.value + and task.action == TaskAction.PRODUCT_REVIEW.value + ): + return False, None + + return True, inactive_sub + @staticmethod def create_product_subscription( org_id, @@ -181,10 +201,13 @@ def create_product_subscription( and org.type_code not in PREMIUM_ORG_TYPES ): continue + previously_approved, inactive_sub = Product._is_previously_approved(org_id, product_code) + if previously_approved: + auto_approve = True subscription_status = Product.find_subscription_status(org, product_model, auto_approve) product_subscription = Product._subscribe_and_publish_activity( - org_id, product_code, subscription_status, product_model.description + org_id, product_code, subscription_status, product_model.description, inactive_sub ) # If there is a linked product, add subscription to that too. @@ -229,6 +252,32 @@ def create_product_subscription( return Product.get_all_product_subscription(org_id=org_id, skip_auth=True) + @staticmethod + def remove_product_subscription(org_id: int, product_code: str, skip_auth=False): + """Deactivate org product subscription by code.""" + org: OrgModel = OrgModel.find_by_org_id(org_id) + if not org: + raise BusinessException(Error.DATA_NOT_FOUND, None) + + if not skip_auth: + check_auth(one_of_roles=(*CLIENT_ADMIN_ROLES, STAFF), org_id=org_id) + + existing_sub = ProductSubscriptionModel.find_by_org_id_product_code(org_id, product_code) + + if existing_sub: + existing_sub.status_code = ProductSubscriptionStatus.INACTIVE.value + existing_sub.save() + + pending_task = TaskModel.find_by_incomplete_task_relationship_id( + relationship_id=existing_sub.id, + task_relationship_type=TaskRelationshipType.PRODUCT.value, + relationship_status=ProductSubscriptionStatus.PENDING_STAFF_REVIEW.value, + ) + if pending_task: + pending_task.delete() + + return Product.get_all_product_subscription(org_id=org_id, skip_auth=True) + @staticmethod def _send_product_subscription_confirmation(product_notification_info: ProductNotificationInfo, org_id: int): admin_emails = UserService.get_admin_emails_for_org(org_id) @@ -256,11 +305,22 @@ def _update_parent_subscription(org_id, sub_product_model, subscription_status): @staticmethod def _subscribe_and_publish_activity( - org_id: int, product_code: str, status_code: str, product_model_description: str + org_id: int, + product_code: str, + status_code: str, + product_model_description: str, + inactive_sub: ProductSubscriptionModel = None, ): - subscription = ProductSubscriptionModel( - org_id=org_id, product_code=product_code, status_code=status_code - ).flush() + subscription = None + if inactive_sub: + subscription = inactive_sub + subscription.status_code = status_code + subscription.flush() + else: + subscription = ProductSubscriptionModel( + org_id=org_id, product_code=product_code, status_code=status_code + ).flush() + if status_code == ProductSubscriptionStatus.ACTIVE.value: ActivityLogPublisher.publish_activity( Activity(org_id, ActivityAction.ADD_PRODUCT_AND_SERVICE.value, name=product_model_description) diff --git a/auth-api/tests/unit/api/test_cors_preflight.py b/auth-api/tests/unit/api/test_cors_preflight.py index a55176c678..677b034430 100644 --- a/auth-api/tests/unit/api/test_cors_preflight.py +++ b/auth-api/tests/unit/api/test_cors_preflight.py @@ -218,6 +218,10 @@ def test_preflight_org_products(app, client, jwt, session): assert rv.status_code == HTTPStatus.OK assert_access_control_headers(rv, "*", "GET, PATCH, POST") + rv = client.options("/api/v1/orgs/1/products/ABC", headers={"Access-Control-Request-Method": "DELETE"}) + assert rv.status_code == HTTPStatus.OK + assert_access_control_headers(rv, "*", "DELETE") + def test_preflight_org_permissions(app, client, jwt, session): """Assert preflight responses for org permissions are correct.""" diff --git a/auth-api/tests/unit/api/test_org_products.py b/auth-api/tests/unit/api/test_org_products.py index 8757f130e5..37c85be5f0 100644 --- a/auth-api/tests/unit/api/test_org_products.py +++ b/auth-api/tests/unit/api/test_org_products.py @@ -22,7 +22,9 @@ import pytest +from auth_api.models import Task as TaskModel from auth_api.schemas import utils as schema_utils +from auth_api.utils.enums import ProductSubscriptionStatus, TaskAction, TaskRelationshipType, TaskStatus from tests.utilities.factory_scenarios import TestJwtClaims, TestOrgInfo, TestOrgProductsInfo from tests.utilities.factory_utils import factory_auth_header @@ -821,3 +823,284 @@ def test_get_org_products_validation_error(client, jwt, session, keycloak_mock): content_type="application/json", ) assert rv_products.status_code == HTTPStatus.NOT_FOUND + + +def test_remove_org_product_with_review(client, jwt, session, keycloak_mock): + """Assert that removing a product subscription with review works properly.""" + staff_headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_role) + user_headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.public_user_role) + client.post("/api/v1/users", headers=user_headers, content_type="application/json") + rv = client.post( + "/api/v1/orgs", data=json.dumps(TestOrgInfo.org_premium), headers=user_headers, content_type="application/json" + ) + assert rv.status_code == HTTPStatus.CREATED + dictionary = json.loads(rv.data) + org_id = dictionary.get("id") + product_info = TestOrgProductsInfo.org_products_vs + product_code = product_info["subscriptions"][0]["productCode"] + rv_products = client.post( + f"/api/v1/orgs/{org_id}/products", + data=json.dumps(product_info), + headers=user_headers, + content_type="application/json", + ) + assert rv_products.status_code == HTTPStatus.CREATED + assert schema_utils.validate(rv_products.json, "org_product_subscriptions_response")[0] + + task = assert_task( + client, + jwt, + ProductSubscriptionStatus.PENDING_STAFF_REVIEW.value, + TaskStatus.OPEN.value, + TaskRelationshipType.PRODUCT.value, + TaskAction.PRODUCT_REVIEW.value, + ) + client.put( + "/api/v1/tasks/{}".format(task["id"]), + data=json.dumps({"relationshipStatus": ProductSubscriptionStatus.ACTIVE.value}), + headers=staff_headers, + content_type="application/json", + ) + + assert_task( + client, + jwt, + ProductSubscriptionStatus.ACTIVE.value, + TaskStatus.COMPLETED.value, + TaskRelationshipType.PRODUCT.value, + TaskAction.PRODUCT_REVIEW.value, + ) + + assert_product_subscription(client, jwt, org_id, product_code, ProductSubscriptionStatus.ACTIVE.value) + + rv_products = client.delete( + f"/api/v1/orgs/{org_id}/products/{product_code}", headers=user_headers, content_type="application/json" + ) + assert rv_products.status_code == HTTPStatus.OK + assert schema_utils.validate(rv_products.json, "org_product_subscriptions_response")[0] + + assert_product_subscription(client, jwt, org_id, product_code, ProductSubscriptionStatus.NOT_SUBSCRIBED.value) + assert_task( + client, + jwt, + ProductSubscriptionStatus.ACTIVE.value, + TaskStatus.COMPLETED.value, + TaskRelationshipType.PRODUCT.value, + TaskAction.PRODUCT_REVIEW.value, + ) + + rv_products = client.post( + f"/api/v1/orgs/{org_id}/products", + data=json.dumps(product_info), + headers=user_headers, + content_type="application/json", + ) + assert rv_products.status_code == HTTPStatus.CREATED + assert schema_utils.validate(rv_products.json, "org_product_subscriptions_response")[0] + + assert_product_subscription(client, jwt, org_id, product_code, ProductSubscriptionStatus.ACTIVE.value) + + # Should not create another task as it was previously approved + assert_task( + client, + jwt, + ProductSubscriptionStatus.ACTIVE.value, + TaskStatus.COMPLETED.value, + TaskRelationshipType.PRODUCT.value, + TaskAction.PRODUCT_REVIEW.value, + ) + + +@pytest.mark.parametrize( + "task_status,relationship_status,should_remove_previous_task", + [ + (TaskStatus.HOLD.value, ProductSubscriptionStatus.PENDING_STAFF_REVIEW.value, True), + (TaskStatus.CLOSED.value, ProductSubscriptionStatus.PENDING_STAFF_REVIEW.value, False), + (None, ProductSubscriptionStatus.REJECTED.value, False), + ], +) +def test_remove_org_product_with_incomplete_review_state( + client, jwt, session, keycloak_mock, task_status, relationship_status, should_remove_previous_task +): + """Assert that removing a product subscription with incomplete review task works properly.""" + staff_headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_role) + user_headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.public_user_role) + client.post("/api/v1/users", headers=user_headers, content_type="application/json") + rv = client.post( + "/api/v1/orgs", data=json.dumps(TestOrgInfo.org_premium), headers=user_headers, content_type="application/json" + ) + assert rv.status_code == HTTPStatus.CREATED + dictionary = json.loads(rv.data) + org_id = dictionary.get("id") + product_info = TestOrgProductsInfo.org_products_vs + product_code = product_info["subscriptions"][0]["productCode"] + rv_products = client.post( + f"/api/v1/orgs/{org_id}/products", + data=json.dumps(product_info), + headers=user_headers, + content_type="application/json", + ) + assert rv_products.status_code == HTTPStatus.CREATED + assert schema_utils.validate(rv_products.json, "org_product_subscriptions_response")[0] + + task = assert_task( + client, + jwt, + ProductSubscriptionStatus.PENDING_STAFF_REVIEW.value, + TaskStatus.OPEN.value, + TaskRelationshipType.PRODUCT.value, + TaskAction.PRODUCT_REVIEW.value, + ) + payload = {} + if task_status: + payload["status"] = task_status + if relationship_status: + payload["relationshipStatus"] = relationship_status + + client.put( + "/api/v1/tasks/{}".format(task["id"]), + data=json.dumps(payload), + headers=staff_headers, + content_type="application/json", + ) + + task = assert_task( + client, + jwt, + relationship_status if relationship_status else ProductSubscriptionStatus.PENDING_STAFF_REVIEW.value, + task_status if task_status else TaskStatus.COMPLETED.value, + TaskRelationshipType.PRODUCT.value, + TaskAction.PRODUCT_REVIEW.value, + ) + + rv_products = client.delete( + f"/api/v1/orgs/{org_id}/products/{product_code}", headers=user_headers, content_type="application/json" + ) + assert rv_products.status_code == HTTPStatus.OK + + assert_product_subscription(client, jwt, org_id, product_code, ProductSubscriptionStatus.NOT_SUBSCRIBED.value) + + task_model = TaskModel.find_by_task_id(task["id"]) + if should_remove_previous_task: + assert task_model is None + else: + assert task_model.id == task["id"] + + rv_products = client.post( + f"/api/v1/orgs/{org_id}/products", + data=json.dumps(product_info), + headers=user_headers, + content_type="application/json", + ) + assert rv_products.status_code == HTTPStatus.CREATED + assert schema_utils.validate(rv_products.json, "org_product_subscriptions_response")[0] + + assert_product_subscription(client, jwt, org_id, product_code, ProductSubscriptionStatus.PENDING_STAFF_REVIEW.value) + + if should_remove_previous_task: + task = assert_task( + client, + jwt, + ProductSubscriptionStatus.PENDING_STAFF_REVIEW.value, + TaskStatus.OPEN.value, + TaskRelationshipType.PRODUCT.value, + TaskAction.PRODUCT_REVIEW.value, + ) + else: + task = assert_task( + client, + jwt, + ProductSubscriptionStatus.PENDING_STAFF_REVIEW.value, + TaskStatus.OPEN.value, + TaskRelationshipType.PRODUCT.value, + TaskAction.PRODUCT_REVIEW.value, + 2, + ) + + client.put( + "/api/v1/tasks/{}".format(task["id"]), + data=json.dumps({"relationshipStatus": ProductSubscriptionStatus.ACTIVE.value}), + headers=staff_headers, + content_type="application/json", + ) + + assert_product_subscription(client, jwt, org_id, product_code, ProductSubscriptionStatus.ACTIVE.value) + + +def test_remove_org_product_without_review(client, jwt, session, keycloak_mock): + """Assert that removing a product subscription without works properly.""" + user_headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.public_user_role) + client.post("/api/v1/users", headers=user_headers, content_type="application/json") + rv = client.post( + "/api/v1/orgs", data=json.dumps(TestOrgInfo.org_premium), headers=user_headers, content_type="application/json" + ) + assert rv.status_code == HTTPStatus.CREATED + dictionary = json.loads(rv.data) + org_id = dictionary.get("id") + product_info = TestOrgProductsInfo.org_products_business + product_code = product_info["subscriptions"][0]["productCode"] + rv_products = client.post( + f"/api/v1/orgs/{org_id}/products", + data=json.dumps(product_info), + headers=user_headers, + content_type="application/json", + ) + assert rv_products.status_code == HTTPStatus.CREATED + assert schema_utils.validate(rv_products.json, "org_product_subscriptions_response")[0] + + assert_product_subscription(client, jwt, org_id, product_code, ProductSubscriptionStatus.ACTIVE.value) + + rv_products = client.delete( + f"/api/v1/orgs/{org_id}/products/{product_code}", headers=user_headers, content_type="application/json" + ) + assert rv_products.status_code == HTTPStatus.OK + assert schema_utils.validate(rv_products.json, "org_product_subscriptions_response")[0] + + assert_product_subscription(client, jwt, org_id, product_code, ProductSubscriptionStatus.NOT_SUBSCRIBED.value) + + rv_products = client.post( + f"/api/v1/orgs/{org_id}/products", + data=json.dumps(product_info), + headers=user_headers, + content_type="application/json", + ) + assert rv_products.status_code == HTTPStatus.CREATED + assert schema_utils.validate(rv_products.json, "org_product_subscriptions_response")[0] + + assert_product_subscription(client, jwt, org_id, product_code, ProductSubscriptionStatus.ACTIVE.value) + + +def assert_product_subscription(client, jwt, org_id, product_code, expected_status): + staff_view_account_headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_view_accounts_role) + rv_products = client.get( + f"/api/v1/orgs/{org_id}/products", headers=staff_view_account_headers, content_type="application/json" + ) + list_products = json.loads(rv_products.data) + assert list_products + + product = next(prod for prod in list_products if prod.get("code") == product_code) + assert product + assert product["subscriptionStatus"] == expected_status + assert product["code"] == product_code + + +def assert_task(client, jwt, relationship_status, task_status, relationship_type, action, expected_task_length=1): + staff_headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_role) + rv = client.get("/api/v1/tasks", headers=staff_headers, content_type="application/json") + + item_list = rv.json + assert schema_utils.validate(item_list, "paged_response")[0] + assert rv.status_code == HTTPStatus.OK + assert len(item_list["tasks"]) == expected_task_length + tasks = item_list["tasks"] + task = next( + task + for task in tasks + if task.get("relationshipStatus") == relationship_status and task.get("status") == task_status + ) + assert task["relationshipStatus"] == relationship_status + assert task["relationshipType"] == relationship_type + assert task["status"] == task_status + assert task["action"] == action + + return task diff --git a/queue_services/account-mailer/poetry.lock b/queue_services/account-mailer/poetry.lock index 727560e9ea..c73650c33e 100644 --- a/queue_services/account-mailer/poetry.lock +++ b/queue_services/account-mailer/poetry.lock @@ -1031,7 +1031,7 @@ six = "^1.16.0" type = "git" url = "https://github.com/seeker25/flask-jwt-oidc.git" reference = "main" -resolved_reference = "d208d4643e3b17358f7295bee0f955e67ba6ac88" +resolved_reference = "563f01ef6453eb0ea1cc0a2d71c6665350c853ff" [[package]] name = "flask-mail" diff --git a/queue_services/auth-queue/poetry.lock b/queue_services/auth-queue/poetry.lock index 2d0bd37cab..2ea4540f34 100644 --- a/queue_services/auth-queue/poetry.lock +++ b/queue_services/auth-queue/poetry.lock @@ -1042,7 +1042,7 @@ six = "^1.16.0" type = "git" url = "https://github.com/seeker25/flask-jwt-oidc.git" reference = "main" -resolved_reference = "d208d4643e3b17358f7295bee0f955e67ba6ac88" +resolved_reference = "563f01ef6453eb0ea1cc0a2d71c6665350c853ff" [[package]] name = "flask-mail"