From 2a4dd7267885131ed10f24ca7f7d09f13f07f7ed Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Mon, 6 Jun 2022 16:50:19 -0400 Subject: [PATCH 01/26] 559-endpoint-log-events draft middleware for calling analytics events for each endpoint --- src/fidesops/api/v1/http_middleware.py | 46 ++++++++++++++++++++++++++ src/fidesops/main.py | 3 ++ 2 files changed, 49 insertions(+) create mode 100644 src/fidesops/api/v1/http_middleware.py diff --git a/src/fidesops/api/v1/http_middleware.py b/src/fidesops/api/v1/http_middleware.py new file mode 100644 index 000000000..cf5daf2a3 --- /dev/null +++ b/src/fidesops/api/v1/http_middleware.py @@ -0,0 +1,46 @@ +from datetime import datetime, timezone + +from fastapi import Request +from fideslog.sdk.python.event import AnalyticsEvent +from starlette.middleware.base import BaseHTTPMiddleware + + +class HttpMiddleware(BaseHTTPMiddleware): + """ + Middleware for every endpoint + Currently only used for logging analytics events + """ + def __init__( + self, + app, + ): + super().__init__(app) + + async def dispatch(self, request: Request, call_next): + is_from_front_end = bool(request.headers.get("request_source") in ['admin-ui', 'privacy-center']) # fixme: add to front end + + # process the request and get the response + try: + response = await call_next(request) + status_code = response.status_code + except Exception as e: + error_class = e.__class__.__name__ + status_code = e.args[0] + + # log response code and/or err type + analytics_event = AnalyticsEvent( + docker=in_docker_container(), + event=EVENT.server_start.value, + event_created_at=datetime.now(tz=timezone.utc), + local_host=running_on_local_host(), + endpoint=request.url.path, + status_code=status_code, + extra_data={ + "is_from_front_end": is_from_front_end + } + ) + if error_class: + analytics_event.error = error_class + send_analytics_event(analytics_event) + + return response \ No newline at end of file diff --git a/src/fidesops/main.py b/src/fidesops/main.py index c44bff86d..c69a79da0 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -6,6 +6,7 @@ from fidesops.api.v1.api import api_router from fidesops.api.v1.exception_handlers import ExceptionHandlers +from fidesops.api.v1.http_middleware import HttpMiddleware from fidesops.api.v1.urn_registry import V1_URL_PREFIX from fidesops.common_exceptions import FunctionalityNotConfigured from fidesops.core.config import config @@ -30,6 +31,8 @@ allow_headers=["*"], ) +app.add_middleware(HttpMiddleware) + app.include_router(api_router) for handler in ExceptionHandlers.get_handlers(): app.add_exception_handler(FunctionalityNotConfigured, handler) From aa9dde61652d2a2e22096004d0c84a428cf9ec96 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Tue, 7 Jun 2022 09:23:42 -0400 Subject: [PATCH 02/26] use true source instead of boolean --- src/fidesops/api/v1/http_middleware.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/fidesops/api/v1/http_middleware.py b/src/fidesops/api/v1/http_middleware.py index cf5daf2a3..b4589f01f 100644 --- a/src/fidesops/api/v1/http_middleware.py +++ b/src/fidesops/api/v1/http_middleware.py @@ -1,4 +1,5 @@ from datetime import datetime, timezone +from typing import Optional from fastapi import Request from fideslog.sdk.python.event import AnalyticsEvent @@ -17,30 +18,32 @@ def __init__( super().__init__(app) async def dispatch(self, request: Request, call_next): - is_from_front_end = bool(request.headers.get("request_source") in ['admin-ui', 'privacy-center']) # fixme: add to front end + fides_source: Optional[str] = request.headers.get("X-Fides-Source") # fixme: add to front-end # process the request and get the response try: response = await call_next(request) - status_code = response.status_code + HttpMiddleware.prepare_and_send_analytics_event(response.status_code, fides_source, None) except Exception as e: - error_class = e.__class__.__name__ - status_code = e.args[0] + HttpMiddleware.prepare_and_send_analytics_event(e.args[0], fides_source, e.__class__.__name__) + return response + + @staticmethod + def prepare_and_send_analytics_event(status_code: int, fides_source: Optional[str], error_class: Optional[str]): # log response code and/or err type analytics_event = AnalyticsEvent( docker=in_docker_container(), - event=EVENT.server_start.value, + event=EVENT.server_start.value, # fixme: add to enum EVENT.endpoint_call or something event_created_at=datetime.now(tz=timezone.utc), local_host=running_on_local_host(), endpoint=request.url.path, status_code=status_code, - extra_data={ - "is_from_front_end": is_from_front_end - } ) if error_class: analytics_event.error = error_class + if fides_source: + analytics_event.extra_data = { + "fides_source": fides_source # fixme: add to enums + } send_analytics_event(analytics_event) - - return response \ No newline at end of file From a97f1d0cf8fc893d5a0674f60e0df6c130eda3fe Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Tue, 7 Jun 2022 17:24:57 -0400 Subject: [PATCH 03/26] adds fides source to front end api calls --- .../admin-ui/src/features/common/helpers.ts | 18 ++++++++++++++++++ .../privacy-requests/privacy-requests.slice.ts | 16 ++++------------ .../admin-ui/src/features/user/user.slice.ts | 15 +++------------ .../privacy-center/components/RequestModal.tsx | 1 + src/fidesops/api/v1/http_middleware.py | 4 ++-- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/clients/admin-ui/src/features/common/helpers.ts b/clients/admin-ui/src/features/common/helpers.ts index a7581f073..30fb73294 100644 --- a/clients/admin-ui/src/features/common/helpers.ts +++ b/clients/admin-ui/src/features/common/helpers.ts @@ -3,6 +3,8 @@ */ import { FetchBaseQueryError } from '@reduxjs/toolkit/query'; +import { fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import {AppState} from "../../app/store"; /** * Type predicate to narrow an unknown error to `FetchBaseQueryError` @@ -27,6 +29,22 @@ export function isErrorWithMessage( ); } + +export function buildBaseQuery() { + return fetchBaseQuery({ + baseUrl: process.env.NEXT_PUBLIC_FIDESOPS_API!, + prepareHeaders: (headers, {getState}) => { + const {token} = (getState() as AppState).user; + headers.set('Access-Control-Allow-Origin', '*'); + headers.set('X-Fides-Source', 'fidesops-admin-ui'); + if (token) { + headers.set('authorization', `Bearer ${token}`); + } + return headers; + }, + }) +} + // generic error of the structure we expect from the Fidesops backend interface ResponseError { data: { diff --git a/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts b/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts index 1f4e944ce..142b986cb 100644 --- a/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts +++ b/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { createApi } from '@reduxjs/toolkit/query/react'; import { HYDRATE } from 'next-redux-wrapper'; import type { AppState } from '../../app/store'; @@ -10,6 +10,7 @@ import { PrivacyRequestResponse, PrivacyRequestStatus, } from './types'; +import { buildBaseQuery } from '../common/helpers'; // Helpers export function mapFiltersToSearchParams({ @@ -48,17 +49,7 @@ export function mapFiltersToSearchParams({ // Subject requests API export const privacyRequestApi = createApi({ reducerPath: 'privacyRequestApi', - baseQuery: fetchBaseQuery({ - baseUrl: process.env.NEXT_PUBLIC_FIDESOPS_API!, - prepareHeaders: (headers, { getState }) => { - const { token } = (getState() as AppState).user; - headers.set('Access-Control-Allow-Origin', '*'); - if (token) { - headers.set('authorization', `Bearer ${token}`); - } - return headers; - }, - }), + baseQuery: buildBaseQuery(), tagTypes: ['Request'], endpoints: (build) => ({ getAllPrivacyRequests: build.query< @@ -137,6 +128,7 @@ export const requestCSVDownload = async ({ headers: { 'Access-Control-Allow-Origin': '*', Authorization: `Bearer ${token}`, + 'X-Fides-Source': 'fidesops-admin-ui' }, } ) diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index 62d61aa86..6dafca7ae 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { createApi } from '@reduxjs/toolkit/query/react'; import { HYDRATE } from 'next-redux-wrapper'; import type { AppState } from '../../app/store'; @@ -12,6 +12,7 @@ import { UsersListParams, UsersResponse, } from './types'; +import { buildBaseQuery } from '../common/helpers'; export interface State { id: string; @@ -48,17 +49,7 @@ export const mapFiltersToSearchParams = ({ // User API export const userApi = createApi({ reducerPath: 'userApi', - baseQuery: fetchBaseQuery({ - baseUrl: process.env.NEXT_PUBLIC_FIDESOPS_API!, - prepareHeaders: (headers, { getState }) => { - const { token } = (getState() as AppState).user; - headers.set('Access-Control-Allow-Origin', '*'); - if (token) { - headers.set('authorization', `Bearer ${token}`); - } - return headers; - }, - }), + baseQuery: buildBaseQuery(), tagTypes: ['User'], endpoints: (build) => ({ getAllUsers: build.query({ diff --git a/clients/privacy-center/components/RequestModal.tsx b/clients/privacy-center/components/RequestModal.tsx index 5e4fdbc29..d5cb629cd 100644 --- a/clients/privacy-center/components/RequestModal.tsx +++ b/clients/privacy-center/components/RequestModal.tsx @@ -91,6 +91,7 @@ const useRequestForm = ({ headers: { Accept: 'application/json', 'Content-Type': 'application/json', + 'X-Fides-Source': 'fidesops-privacy-center', }, body: JSON.stringify(body), }); diff --git a/src/fidesops/api/v1/http_middleware.py b/src/fidesops/api/v1/http_middleware.py index b4589f01f..d27e191ef 100644 --- a/src/fidesops/api/v1/http_middleware.py +++ b/src/fidesops/api/v1/http_middleware.py @@ -24,10 +24,10 @@ async def dispatch(self, request: Request, call_next): try: response = await call_next(request) HttpMiddleware.prepare_and_send_analytics_event(response.status_code, fides_source, None) + return response except Exception as e: HttpMiddleware.prepare_and_send_analytics_event(e.args[0], fides_source, e.__class__.__name__) - - return response + raise @staticmethod def prepare_and_send_analytics_event(status_code: int, fides_source: Optional[str], error_class: Optional[str]): From da6e3633b5de7084805b1379acf755e46b6361b3 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Wed, 8 Jun 2022 09:19:48 -0400 Subject: [PATCH 04/26] remove comment --- src/fidesops/api/v1/http_middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fidesops/api/v1/http_middleware.py b/src/fidesops/api/v1/http_middleware.py index d27e191ef..49afd88e4 100644 --- a/src/fidesops/api/v1/http_middleware.py +++ b/src/fidesops/api/v1/http_middleware.py @@ -18,7 +18,7 @@ def __init__( super().__init__(app) async def dispatch(self, request: Request, call_next): - fides_source: Optional[str] = request.headers.get("X-Fides-Source") # fixme: add to front-end + fides_source: Optional[str] = request.headers.get("X-Fides-Source") # process the request and get the response try: From 5bf89371349967da2bdf1eb72ac00cb11fe16058 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Wed, 8 Jun 2022 20:35:44 -0400 Subject: [PATCH 05/26] adds enums --- CHANGELOG.md | 3 ++- src/fidesops/api/v1/http_middleware.py | 37 +++++++++++++++++--------- src/fidesops/core/config.py | 1 + src/fidesops/main.py | 4 +-- src/fidesops/schemas/analytics.py | 9 ++++++- 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 561fe4e67..bffe87d72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ The types of changes are: ### Added * Subject Request Details page [#563](https://github.com/ethyca/fidesops/pull/563) * Restart Graph from Failure [#578](https://github.com/ethyca/fidesops/pull/578) +* Adds Fideslog integration [#541](https://github.com/ethyca/fidesops/pull/541) +* Adds endpoint analytics events []() ## [1.5.2](https://github.com/ethyca/fidesops/compare/1.5.1...1.5.2) @@ -106,7 +108,6 @@ The types of changes are: * Frontend for privacy request denial reaons [#480](https://github.com/ethyca/fidesops/pull/480) * Publish Fidesops to Pypi [#491](https://github.com/ethyca/fidesops/pull/491) * DRP data rights endpoint [#526](https://github.com/ethyca/fidesops/pull/526) -* ADDS Fideslog integration [#541](https://github.com/ethyca/fidesops/pull/541) ### Changed diff --git a/src/fidesops/api/v1/http_middleware.py b/src/fidesops/api/v1/http_middleware.py index 49afd88e4..4d839dc9f 100644 --- a/src/fidesops/api/v1/http_middleware.py +++ b/src/fidesops/api/v1/http_middleware.py @@ -5,45 +5,58 @@ from fideslog.sdk.python.event import AnalyticsEvent from starlette.middleware.base import BaseHTTPMiddleware +from fidesops.analytics import ( + in_docker_container, + running_on_local_host, + send_analytics_event, +) +from fidesops.schemas.analytics import Event, ExtraData + class HttpMiddleware(BaseHTTPMiddleware): """ Middleware for every endpoint Currently only used for logging analytics events """ + def __init__( - self, - app, + self, + app, ): super().__init__(app) async def dispatch(self, request: Request, call_next): fides_source: Optional[str] = request.headers.get("X-Fides-Source") - # process the request and get the response try: response = await call_next(request) - HttpMiddleware.prepare_and_send_analytics_event(response.status_code, fides_source, None) + HttpMiddleware.prepare_and_send_analytics_event( + request.url.path, response.status_code, fides_source, None + ) return response except Exception as e: - HttpMiddleware.prepare_and_send_analytics_event(e.args[0], fides_source, e.__class__.__name__) + HttpMiddleware.prepare_and_send_analytics_event( + request.url.path, e.args[0], fides_source, e.__class__.__name__ + ) raise @staticmethod - def prepare_and_send_analytics_event(status_code: int, fides_source: Optional[str], error_class: Optional[str]): - # log response code and/or err type + def prepare_and_send_analytics_event( + endpoint: str, + status_code: int, + fides_source: Optional[str], + error_class: Optional[str], + ): analytics_event = AnalyticsEvent( docker=in_docker_container(), - event=EVENT.server_start.value, # fixme: add to enum EVENT.endpoint_call or something + event=Event.endpoint_call.value, event_created_at=datetime.now(tz=timezone.utc), local_host=running_on_local_host(), - endpoint=request.url.path, + endpoint=endpoint, status_code=status_code, ) if error_class: analytics_event.error = error_class if fides_source: - analytics_event.extra_data = { - "fides_source": fides_source # fixme: add to enums - } + analytics_event.extra_data = {ExtraData.fides_source.value: fides_source} send_analytics_event(analytics_event) diff --git a/src/fidesops/core/config.py b/src/fidesops/core/config.py index e6d8c59de..1a4ca91a0 100644 --- a/src/fidesops/core/config.py +++ b/src/fidesops/core/config.py @@ -232,6 +232,7 @@ def populate_analytics_id(cls, v: Optional[str]) -> str: """ Populates the appropriate value for analytics id based on config """ + logger.info("found value: " + v) return v or cls.generate_and_store_client_id() @staticmethod diff --git a/src/fidesops/main.py b/src/fidesops/main.py index 0692da1eb..8aea89ac1 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -18,7 +18,7 @@ from fidesops.common_exceptions import FunctionalityNotConfigured from fidesops.core.config import config from fidesops.db.database import init_db -from fidesops.schemas.analytics import EVENT +from fidesops.schemas.analytics import Event from fidesops.tasks.scheduled.scheduler import scheduler from fidesops.tasks.scheduled.tasks import initiate_scheduled_request_intake from fidesops.util.logger import get_fides_log_record_factory @@ -70,7 +70,7 @@ def start_webserver() -> None: send_analytics_event( AnalyticsEvent( docker=in_docker_container(), - event=EVENT.server_start.value, + event=Event.server_start.value, event_created_at=datetime.now(tz=timezone.utc), local_host=running_on_local_host(), ) diff --git a/src/fidesops/schemas/analytics.py b/src/fidesops/schemas/analytics.py index ee69d6732..edbcff8ff 100644 --- a/src/fidesops/schemas/analytics.py +++ b/src/fidesops/schemas/analytics.py @@ -1,7 +1,14 @@ from enum import Enum -class EVENT(str, Enum): +class Event(str, Enum): """Enum to hold analytics event names""" server_start = "server_start" + endpoint_call = "endpoint_call" + + +class ExtraData(str, Enum): + """Enum to hold keys for extra data""" + + fides_source = "fides_source" From f57d6dfe20ad78d776514dac5e446dbec90d77b1 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Mon, 13 Jun 2022 10:14:34 -0400 Subject: [PATCH 06/26] adds http middleware, hard-code dev mode in docker-compose --- docker-compose.yml | 2 +- src/fidesops/api/v1/http_middleware.py | 62 ------------------------- src/fidesops/main.py | 64 ++++++++++++++++++++++++-- 3 files changed, 61 insertions(+), 67 deletions(-) delete mode 100644 src/fidesops/api/v1/http_middleware.py diff --git a/docker-compose.yml b/docker-compose.yml index 1101bca6b..9bf005944 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ services: read_only: False - /fidesops/src/fidesops.egg-info environment: - - FIDESOPS__DEV_MODE=${FIDESOPS__DEV_MODE} + - FIDESOPS__DEV_MODE=True - FIDESOPS__LOG_PII=${FIDESOPS__LOG_PII} - FIDESOPS__HOT_RELOAD=${FIDESOPS__HOT_RELOAD} - FIDESOPS__ROOT_USER__ANALYTICS_ID=${FIDESOPS__ROOT_USER__ANALYTICS_ID} diff --git a/src/fidesops/api/v1/http_middleware.py b/src/fidesops/api/v1/http_middleware.py deleted file mode 100644 index 4d839dc9f..000000000 --- a/src/fidesops/api/v1/http_middleware.py +++ /dev/null @@ -1,62 +0,0 @@ -from datetime import datetime, timezone -from typing import Optional - -from fastapi import Request -from fideslog.sdk.python.event import AnalyticsEvent -from starlette.middleware.base import BaseHTTPMiddleware - -from fidesops.analytics import ( - in_docker_container, - running_on_local_host, - send_analytics_event, -) -from fidesops.schemas.analytics import Event, ExtraData - - -class HttpMiddleware(BaseHTTPMiddleware): - """ - Middleware for every endpoint - Currently only used for logging analytics events - """ - - def __init__( - self, - app, - ): - super().__init__(app) - - async def dispatch(self, request: Request, call_next): - fides_source: Optional[str] = request.headers.get("X-Fides-Source") - - try: - response = await call_next(request) - HttpMiddleware.prepare_and_send_analytics_event( - request.url.path, response.status_code, fides_source, None - ) - return response - except Exception as e: - HttpMiddleware.prepare_and_send_analytics_event( - request.url.path, e.args[0], fides_source, e.__class__.__name__ - ) - raise - - @staticmethod - def prepare_and_send_analytics_event( - endpoint: str, - status_code: int, - fides_source: Optional[str], - error_class: Optional[str], - ): - analytics_event = AnalyticsEvent( - docker=in_docker_container(), - event=Event.endpoint_call.value, - event_created_at=datetime.now(tz=timezone.utc), - local_host=running_on_local_host(), - endpoint=endpoint, - status_code=status_code, - ) - if error_class: - analytics_event.error = error_class - if fides_source: - analytics_event.extra_data = {ExtraData.fides_source.value: fides_source} - send_analytics_event(analytics_event) diff --git a/src/fidesops/main.py b/src/fidesops/main.py index 8aea89ac1..1979ddecd 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -1,9 +1,11 @@ import logging from datetime import datetime, timezone +from typing import Optional, Callable import uvicorn -from fastapi import FastAPI +from fastapi import FastAPI, Request, Response from fideslog.sdk.python.event import AnalyticsEvent +from starlette.background import BackgroundTask from starlette.middleware.cors import CORSMiddleware from fidesops.analytics import ( @@ -13,12 +15,11 @@ ) from fidesops.api.v1.api import api_router from fidesops.api.v1.exception_handlers import ExceptionHandlers -from fidesops.api.v1.http_middleware import HttpMiddleware from fidesops.api.v1.urn_registry import V1_URL_PREFIX from fidesops.common_exceptions import FunctionalityNotConfigured from fidesops.core.config import config from fidesops.db.database import init_db -from fidesops.schemas.analytics import Event +from fidesops.schemas.analytics import Event, ExtraData from fidesops.tasks.scheduled.scheduler import scheduler from fidesops.tasks.scheduled.tasks import initiate_scheduled_request_intake from fidesops.util.logger import get_fides_log_record_factory @@ -39,7 +40,62 @@ allow_headers=["*"], ) -app.add_middleware(HttpMiddleware) + +@app.middleware("http") +async def dispatch_log_request(request: Request, call_next: Callable) -> Response: + fides_source: Optional[str] = request.headers.get("X-Fides-Source") # fixme: admin-ui not sending source + now: datetime = datetime.now(tz=timezone.utc) + endpoint = f"{request.method.capitalize()}: {request.url}" + + try: + response = await call_next(request) + # HTTPExceptions are considered a handled err by default so are not thrown here. + # Accepted workaround is to inspect status code of response. + # More context- https://github.com/tiangolo/fastapi/issues/1840 + try: + if response.status_code >= 400: + response.background = BackgroundTask(prepare_and_log_request, + endpoint, response.status_code, now, fides_source, "HTTPException" + ) + else: + response.background = BackgroundTask(prepare_and_log_request, + endpoint, response.status_code, now, fides_source, None + ) + except Exception as exc: + # always continue if something went wrong with analytics + logger.warning(f"Analytics event for endpoint {request.url} failed to send: {exc}") + return response + + return response + except Exception as e: + logger.warning("exception caught") + response.background = BackgroundTask(prepare_and_log_request, + endpoint, e.args[0], now, fides_source, e.__class__.__name__ + ) + raise + + +def prepare_and_log_request( + endpoint: str, + status_code: int, + event_created_at: datetime, + fides_source: Optional[str], + error_class: Optional[str], +): + analytics_event = AnalyticsEvent( + docker=in_docker_container(), + event=Event.endpoint_call.value, + event_created_at=event_created_at, + local_host=running_on_local_host(), + endpoint=endpoint, + status_code=status_code, + ) + if error_class: + analytics_event.error = error_class + if fides_source: + analytics_event.extra_data = {ExtraData.fides_source.value: fides_source} + send_analytics_event(analytics_event) + app.include_router(api_router) for handler in ExceptionHandlers.get_handlers(): From 3810e85540a8d6a1d1fc491c5131d94cda4b4e58 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Mon, 13 Jun 2022 14:53:12 -0400 Subject: [PATCH 07/26] adds missing source header to login of admin-ui --- clients/admin-ui/src/pages/api/auth/[...nextauth].ts | 2 +- src/fidesops/main.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/clients/admin-ui/src/pages/api/auth/[...nextauth].ts b/clients/admin-ui/src/pages/api/auth/[...nextauth].ts index d2bb8a913..3288095e2 100644 --- a/clients/admin-ui/src/pages/api/auth/[...nextauth].ts +++ b/clients/admin-ui/src/pages/api/auth/[...nextauth].ts @@ -23,7 +23,7 @@ export default NextAuth({ username: credentials!.email, password: credentials!.password, }), - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'X-Fides-Source': 'fidesops-admin-ui' }, }); } catch (error) { throw new Error('Failed to authenticate'); diff --git a/src/fidesops/main.py b/src/fidesops/main.py index 1979ddecd..298a593d5 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -43,9 +43,9 @@ @app.middleware("http") async def dispatch_log_request(request: Request, call_next: Callable) -> Response: - fides_source: Optional[str] = request.headers.get("X-Fides-Source") # fixme: admin-ui not sending source + fides_source: Optional[str] = request.headers.get("X-Fides-Source") now: datetime = datetime.now(tz=timezone.utc) - endpoint = f"{request.method.capitalize()}: {request.url}" + endpoint = f"{request.method}: {request.url.path}" try: response = await call_next(request) @@ -69,9 +69,7 @@ async def dispatch_log_request(request: Request, call_next: Callable) -> Respons return response except Exception as e: logger.warning("exception caught") - response.background = BackgroundTask(prepare_and_log_request, - endpoint, e.args[0], now, fides_source, e.__class__.__name__ - ) + prepare_and_log_request(endpoint, e.args[0], now, fides_source, e.__class__.__name__) raise From 70633a9b9984ad73f4dbd801cccb66832cabb355 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Tue, 14 Jun 2022 10:18:14 -0400 Subject: [PATCH 08/26] fix additional front-end admin ui API calls after latest pull --- .../admin-ui/src/features/auth/auth.slice.ts | 13 +----- .../admin-ui/src/features/common/helpers.ts | 5 ++- src/fidesops/core/config.py | 1 - src/fidesops/main.py | 42 ++++++++++++------- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/clients/admin-ui/src/features/auth/auth.slice.ts b/clients/admin-ui/src/features/auth/auth.slice.ts index 87d44fa3a..742b3315e 100644 --- a/clients/admin-ui/src/features/auth/auth.slice.ts +++ b/clients/admin-ui/src/features/auth/auth.slice.ts @@ -9,6 +9,7 @@ import type { RootState } from '../../app/store'; import { STORED_CREDENTIALS_KEY } from '../../constants'; import { User } from '../user-management/types'; import { LoginRequest, LoginResponse } from './types'; +import {buildBaseQuery} from "../common/helpers"; export interface AuthState { user: User | null; @@ -73,17 +74,7 @@ credentialStorage.startListening({ // Auth API export const authApi = createApi({ reducerPath: 'authApi', - baseQuery: fetchBaseQuery({ - baseUrl: process.env.NEXT_PUBLIC_FIDESOPS_API!, - prepareHeaders: (headers, { getState }) => { - const token = selectToken(getState() as RootState); - headers.set('Access-Control-Allow-Origin', '*'); - if (token) { - headers.set('authorization', `Bearer ${token}`); - } - return headers; - }, - }), + baseQuery: buildBaseQuery(), tagTypes: ['Auth'], endpoints: (build) => ({ login: build.mutation({ diff --git a/clients/admin-ui/src/features/common/helpers.ts b/clients/admin-ui/src/features/common/helpers.ts index 45ac2d936..31a9a70a1 100644 --- a/clients/admin-ui/src/features/common/helpers.ts +++ b/clients/admin-ui/src/features/common/helpers.ts @@ -4,7 +4,8 @@ import { FetchBaseQueryError } from '@reduxjs/toolkit/query'; import { fetchBaseQuery } from '@reduxjs/toolkit/query/react'; -import {AppState} from "../../app/store"; +import {RootState} from "../../app/store"; +import {selectToken} from "../auth"; /** * Type predicate to narrow an unknown error to `FetchBaseQueryError` @@ -34,7 +35,7 @@ export function buildBaseQuery() { return fetchBaseQuery({ baseUrl: process.env.NEXT_PUBLIC_FIDESOPS_API!, prepareHeaders: (headers, {getState}) => { - const {token} = (getState() as AppState).user; + const token = selectToken(getState() as RootState); headers.set('Access-Control-Allow-Origin', '*'); headers.set('X-Fides-Source', 'fidesops-admin-ui'); if (token) { diff --git a/src/fidesops/core/config.py b/src/fidesops/core/config.py index 3c463d050..45ebffdaa 100644 --- a/src/fidesops/core/config.py +++ b/src/fidesops/core/config.py @@ -105,7 +105,6 @@ def populate_analytics_id(cls, v: Optional[str]) -> str: """ Populates the appropriate value for analytics id based on config """ - logger.info("found value: " + v) return v or cls.generate_and_store_client_id() @staticmethod diff --git a/src/fidesops/main.py b/src/fidesops/main.py index b1a66adaf..f076e95b8 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -1,6 +1,6 @@ import logging from datetime import datetime, timezone -from typing import Optional, Callable +from typing import Callable, Optional import uvicorn from fastapi import FastAPI, Request, Response @@ -54,31 +54,45 @@ async def dispatch_log_request(request: Request, call_next: Callable) -> Respons # More context- https://github.com/tiangolo/fastapi/issues/1840 try: if response.status_code >= 400: - response.background = BackgroundTask(prepare_and_log_request, - endpoint, response.status_code, now, fides_source, "HTTPException" - ) + response.background = BackgroundTask( + prepare_and_log_request, + endpoint, + response.status_code, + now, + fides_source, + "HTTPException", + ) else: - response.background = BackgroundTask(prepare_and_log_request, - endpoint, response.status_code, now, fides_source, None - ) + response.background = BackgroundTask( + prepare_and_log_request, + endpoint, + response.status_code, + now, + fides_source, + None, + ) except Exception as exc: # always continue if something went wrong with analytics - logger.warning(f"Analytics event for endpoint {request.url} failed to send: {exc}") + logger.warning( + f"Analytics event for endpoint {request.url} failed to send: {exc}" + ) return response return response except Exception as e: logger.warning("exception caught") - prepare_and_log_request(endpoint, e.args[0], now, fides_source, e.__class__.__name__) + prepare_and_log_request( + endpoint, e.args[0], now, fides_source, e.__class__.__name__ + ) raise def prepare_and_log_request( - endpoint: str, - status_code: int, - event_created_at: datetime, - fides_source: Optional[str], - error_class: Optional[str], + endpoint: str, + status_code: int, + event_created_at: datetime, + fides_source: Optional[str], + error_class: Optional[str], ): analytics_event = AnalyticsEvent( docker=in_docker_container(), From e385ebd5ebcb36b28c62c44e947496457c54d8a4 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Tue, 14 Jun 2022 11:54:06 -0400 Subject: [PATCH 09/26] fix circular imports --- clients/admin-ui/.eslintrc.js | 1 + clients/admin-ui/package-lock.json | 346 ++++-------------- clients/admin-ui/package.json | 2 +- .../admin-ui/src/features/auth/auth.slice.ts | 16 +- .../src/features/common/CommonHeaders.ts | 13 + .../admin-ui/src/features/common/helpers.ts | 19 - .../privacy-requests.slice.ts | 15 +- .../user-management/user-management.slice.ts | 16 +- 8 files changed, 120 insertions(+), 308 deletions(-) create mode 100644 clients/admin-ui/src/features/common/CommonHeaders.ts diff --git a/clients/admin-ui/.eslintrc.js b/clients/admin-ui/.eslintrc.js index e78b33d95..b760c3f10 100644 --- a/clients/admin-ui/.eslintrc.js +++ b/clients/admin-ui/.eslintrc.js @@ -15,6 +15,7 @@ module.exports = { // causes bug in re-exporting default exports, see // https://github.com/eslint/eslint/issues/15617 'no-restricted-exports': [0], + 'import/prefer-default-export': 'off', 'react/function-component-definition': [ 2, { diff --git a/clients/admin-ui/package-lock.json b/clients/admin-ui/package-lock.json index fdfbc59e9..b46b40ea7 100644 --- a/clients/admin-ui/package-lock.json +++ b/clients/admin-ui/package-lock.json @@ -19,6 +19,7 @@ "formik": "^2.2.9", "framer-motion": "^5", "lodash.debounce": "^4.0.8", + "msw": "^0.42.0", "next": "12.1.0", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -49,7 +50,6 @@ "identity-obj-proxy": "^3.0.0", "jest": "^27.5.1", "prettier": "^2.6.2", - "msw": "^0.42.0", "typescript": "4.5.5" } }, @@ -3965,7 +3965,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.2.1.tgz", "integrity": "sha512-0tDfcPw5/s7QsNQqS3knAvAD5w5PF1nNPagRhKO/yECY+sMbJxoC2sLWnH7Lzmh52mTSVLKDhd1r92Q3kfljnQ==", - "dev": true, "dependencies": { "@types/set-cookie-parser": "^2.4.0", "set-cookie-parser": "^2.4.6" @@ -3978,7 +3977,6 @@ "version": "0.16.4", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.16.4.tgz", "integrity": "sha512-McPKUFlZNS/wo+OAor15k0fv2skK+EdWl9CEcdxAqsN4vKajlxCxDU4B5W/pn1y0TJPSAOmxR6LYFe/8esePrg==", - "dev": true, "dependencies": { "@open-draft/until": "^1.0.3", "@xmldom/xmldom": "^0.7.5", @@ -4240,8 +4238,7 @@ "node_modules/@open-draft/until": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz", - "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==", - "dev": true + "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==" }, "node_modules/@polka/url": { "version": "1.0.0-next.21", @@ -4534,8 +4531,7 @@ "node_modules/@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" }, "node_modules/@types/graceful-fs": { "version": "4.1.5", @@ -4624,8 +4620,7 @@ "node_modules/@types/js-levenshtein": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz", - "integrity": "sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==", - "dev": true + "integrity": "sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==" }, "node_modules/@types/json-schema": { "version": "7.0.9", @@ -4668,7 +4663,6 @@ "version": "17.0.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.10.tgz", "integrity": "sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==", - "dev": true, "license": "MIT" }, "node_modules/@types/parse-json": { @@ -4730,7 +4724,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.2.tgz", "integrity": "sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -5011,7 +5004,6 @@ "version": "0.7.5", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz", "integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==", - "dev": true, "engines": { "node": ">=10.0.0" } @@ -5117,7 +5109,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -5132,7 +5123,6 @@ "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, "engines": { "node": ">=10" }, @@ -5144,7 +5134,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5154,7 +5143,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -5170,7 +5158,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -5438,7 +5425,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -5458,7 +5444,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "engines": { "node": ">=8" } @@ -5467,7 +5452,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -5489,7 +5473,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -5538,7 +5521,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -5609,7 +5591,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -5634,14 +5615,12 @@ "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "funding": [ { "type": "individual", @@ -5668,7 +5647,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -5692,7 +5670,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, "dependencies": { "restore-cursor": "^3.1.0" }, @@ -5704,7 +5681,6 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", - "dev": true, "engines": { "node": ">=6" }, @@ -5716,7 +5692,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, "engines": { "node": ">= 10" } @@ -5725,7 +5700,6 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -5736,7 +5710,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true, "engines": { "node": ">=0.8" } @@ -5761,7 +5734,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5774,7 +5746,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -5831,7 +5802,6 @@ "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -6076,7 +6046,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, "dependencies": { "clone": "^1.0.2" } @@ -6873,7 +6842,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "engines": { "node": ">=0.8.x" } @@ -6929,7 +6897,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -7008,7 +6975,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -7023,7 +6989,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -7045,7 +7010,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -7213,7 +7177,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -7248,7 +7211,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -7394,7 +7356,6 @@ "version": "16.5.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.5.0.tgz", "integrity": "sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -7447,7 +7408,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7485,8 +7445,7 @@ "node_modules/headers-polyfill": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.0.7.tgz", - "integrity": "sha512-JoLCAdCEab58+2/yEmSnOlficyHFpIl0XJqwu3l+Unkm1gXpFUYsThz6Yha3D6tNhocWkCPfyW0YVIGWFqTi7w==", - "dev": true + "integrity": "sha512-JoLCAdCEab58+2/yEmSnOlficyHFpIl0XJqwu3l+Unkm1gXpFUYsThz6Yha3D6tNhocWkCPfyW0YVIGWFqTi7w==" }, "node_modules/hey-listen": { "version": "1.0.8", @@ -7561,7 +7520,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -7585,7 +7543,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -7689,14 +7646,12 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/inquirer": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.1.tgz", "integrity": "sha512-pxhBaw9cyTFMjwKtkjePWDhvwzvrNGAw7En4hottzlPvz80GZaMZthdDU35aA6/f5FRZf3uhE057q8w1DE3V2g==", - "dev": true, "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -7764,7 +7719,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -7834,7 +7788,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7844,7 +7797,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -7862,7 +7814,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -7875,7 +7826,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, "engines": { "node": ">=8" } @@ -7896,14 +7846,12 @@ "node_modules/is-node-process": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.0.1.tgz", - "integrity": "sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ==", - "dev": true + "integrity": "sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ==" }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -8011,7 +7959,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, "engines": { "node": ">=10" }, @@ -9113,7 +9060,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9341,7 +9287,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -9465,7 +9410,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, "engines": { "node": ">=6" } @@ -9517,7 +9461,6 @@ "version": "0.42.0", "resolved": "https://registry.npmjs.org/msw/-/msw-0.42.0.tgz", "integrity": "sha512-vB9rzgiGHoQGfkKpp3QZHxobzfuuQOJk+0bff0wtbK8k3P3CaUSt8bCwvExours682AY4mUfTjIkCsxy0JoS3w==", - "dev": true, "hasInstallScript": true, "dependencies": { "@mswjs/cookies": "^0.2.0", @@ -9564,7 +9507,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -9580,7 +9522,6 @@ "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -9599,14 +9540,12 @@ "node_modules/msw/node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, "node_modules/msw/node_modules/type-fest": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, "engines": { "node": ">=10" }, @@ -9617,14 +9556,12 @@ "node_modules/msw/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "node_modules/msw/node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -9634,7 +9571,6 @@ "version": "17.4.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.0.tgz", "integrity": "sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA==", - "dev": true, "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -9652,7 +9588,6 @@ "version": "21.0.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", - "dev": true, "engines": { "node": ">=12" } @@ -9660,8 +9595,7 @@ "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "node_modules/nanoid": { "version": "3.3.2", @@ -9745,7 +9679,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9895,7 +9828,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -9938,7 +9870,6 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -9961,7 +9892,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9969,8 +9899,7 @@ "node_modules/outvariant": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.3.0.tgz", - "integrity": "sha512-yeWM9k6UPfG/nzxdaPlJkB2p08hCg4xP6Lx99F+vP8YF7xyZVfTmJjrrNalkmzudD4WFvNLVudQikqUmF8zhVQ==", - "dev": true + "integrity": "sha512-yeWM9k6UPfG/nzxdaPlJkB2p08hCg4xP6Lx99F+vP8YF7xyZVfTmJjrrNalkmzudD4WFvNLVudQikqUmF8zhVQ==" }, "node_modules/p-limit": { "version": "1.3.0", @@ -10083,8 +10012,7 @@ "node_modules/path-to-regexp": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", - "dev": true + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, "node_modules/path-type": { "version": "4.0.0", @@ -10104,7 +10032,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -10266,11 +10193,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/pretty-format": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", - "integrity": "sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U=" - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -10542,7 +10464,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -10557,7 +10478,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -10634,7 +10554,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10704,7 +10623,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -10743,7 +10661,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -10775,7 +10692,6 @@ "version": "7.5.5", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", - "dev": true, "dependencies": { "tslib": "^2.1.0" } @@ -10789,8 +10705,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/saxes": { "version": "5.0.1", @@ -10826,8 +10741,7 @@ "node_modules/set-cookie-parser": { "version": "2.4.8", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", - "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==", - "dev": true + "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -10870,8 +10784,7 @@ "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 + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/sirv": { "version": "1.0.19", @@ -10982,7 +10895,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -10991,7 +10903,6 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.4.tgz", "integrity": "sha512-xIqTLS5azUH1djSUsLH9DbP6UnM/nI18vu8d43JigCQEoVsnY+mrlE+qv6kYqs6/1OkMnMIiL6ffedQSZStuoQ==", - "dev": true, "dependencies": { "events": "^3.3.0" } @@ -11000,7 +10911,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -11010,7 +10920,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -11044,7 +10953,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -11057,8 +10965,7 @@ "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/string.prototype.matchall": { "version": "4.0.6", @@ -11112,7 +11019,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11197,7 +11103,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -11283,8 +11188,7 @@ "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "node_modules/tiny-invariant": { "version": "1.2.0", @@ -11301,7 +11205,6 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -11328,7 +11231,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -11467,7 +11369,7 @@ "version": "4.5.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -11567,7 +11469,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true, "license": "MIT" }, "node_modules/v8-compile-cache": { @@ -11634,7 +11535,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, "dependencies": { "defaults": "^1.0.3" } @@ -11753,7 +11653,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -11823,7 +11722,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -14885,7 +14783,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.2.1.tgz", "integrity": "sha512-0tDfcPw5/s7QsNQqS3knAvAD5w5PF1nNPagRhKO/yECY+sMbJxoC2sLWnH7Lzmh52mTSVLKDhd1r92Q3kfljnQ==", - "dev": true, "requires": { "@types/set-cookie-parser": "^2.4.0", "set-cookie-parser": "^2.4.6" @@ -14895,7 +14792,6 @@ "version": "0.16.4", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.16.4.tgz", "integrity": "sha512-McPKUFlZNS/wo+OAor15k0fv2skK+EdWl9CEcdxAqsN4vKajlxCxDU4B5W/pn1y0TJPSAOmxR6LYFe/8esePrg==", - "dev": true, "requires": { "@open-draft/until": "^1.0.3", "@xmldom/xmldom": "^0.7.5", @@ -15039,8 +14935,7 @@ "@open-draft/until": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz", - "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==", - "dev": true + "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==" }, "@polka/url": { "version": "1.0.0-next.21", @@ -15267,8 +15162,7 @@ "@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" }, "@types/graceful-fs": { "version": "4.1.5", @@ -15350,8 +15244,7 @@ "@types/js-levenshtein": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz", - "integrity": "sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==", - "dev": true + "integrity": "sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==" }, "@types/json-schema": { "version": "7.0.9", @@ -15390,8 +15283,7 @@ "@types/node": { "version": "17.0.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.10.tgz", - "integrity": "sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==", - "dev": true + "integrity": "sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==" }, "@types/parse-json": { "version": "4.0.0", @@ -15448,7 +15340,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.2.tgz", "integrity": "sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==", - "dev": true, "requires": { "@types/node": "*" } @@ -15626,8 +15517,7 @@ "@xmldom/xmldom": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz", - "integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==", - "dev": true + "integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==" }, "abab": { "version": "2.0.5", @@ -15703,7 +15593,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, "requires": { "type-fest": "^0.21.3" }, @@ -15711,22 +15600,19 @@ "type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" } } }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -15735,7 +15621,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -15933,20 +15818,17 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -15967,7 +15849,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -16003,7 +15884,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -16045,7 +15925,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -16060,14 +15939,12 @@ "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -16083,7 +15960,6 @@ "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" } @@ -16106,7 +15982,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, "requires": { "restore-cursor": "^3.1.0" } @@ -16114,20 +15989,17 @@ "cli-spinners": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", - "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", - "dev": true + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==" }, "cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -16137,8 +16009,7 @@ "clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, "co": { "version": "4.6.0", @@ -16156,7 +16027,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -16164,8 +16034,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "combined-stream": { "version": "1.0.8", @@ -16210,8 +16079,7 @@ "cookie": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" }, "copy-to-clipboard": { "version": "3.3.1", @@ -16394,7 +16262,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, "requires": { "clone": "^1.0.2" } @@ -16979,8 +16846,7 @@ "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "execa": { "version": "5.1.1", @@ -17021,7 +16887,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, "requires": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -17092,7 +16957,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, "requires": { "escape-string-regexp": "^1.0.5" }, @@ -17100,8 +16964,7 @@ "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=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" } } }, @@ -17118,7 +16981,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -17241,7 +17103,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "optional": true }, "function-bind": { @@ -17263,8 +17124,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { "version": "1.1.1", @@ -17359,8 +17219,7 @@ "graphql": { "version": "16.5.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.5.0.tgz", - "integrity": "sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA==", - "dev": true + "integrity": "sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA==" }, "gzip-size": { "version": "6.0.0", @@ -17394,8 +17253,7 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "has-symbols": { "version": "1.0.2", @@ -17415,8 +17273,7 @@ "headers-polyfill": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.0.7.tgz", - "integrity": "sha512-JoLCAdCEab58+2/yEmSnOlficyHFpIl0XJqwu3l+Unkm1gXpFUYsThz6Yha3D6tNhocWkCPfyW0YVIGWFqTi7w==", - "dev": true + "integrity": "sha512-JoLCAdCEab58+2/yEmSnOlficyHFpIl0XJqwu3l+Unkm1gXpFUYsThz6Yha3D6tNhocWkCPfyW0YVIGWFqTi7w==" }, "hey-listen": { "version": "1.0.8", @@ -17477,7 +17334,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -17494,8 +17350,7 @@ "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { "version": "5.2.0", @@ -17552,14 +17407,12 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "inquirer": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.1.tgz", "integrity": "sha512-pxhBaw9cyTFMjwKtkjePWDhvwzvrNGAw7En4hottzlPvz80GZaMZthdDU35aA6/f5FRZf3uhE057q8w1DE3V2g==", - "dev": true, "requires": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -17614,7 +17467,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "requires": { "binary-extensions": "^2.0.0" } @@ -17655,14 +17507,12 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-generator-fn": { "version": "2.1.0", @@ -17674,7 +17524,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -17682,8 +17531,7 @@ "is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" }, "is-negative-zero": { "version": "2.0.2", @@ -17694,14 +17542,12 @@ "is-node-process": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.0.1.tgz", - "integrity": "sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ==", - "dev": true + "integrity": "sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ==" }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-number-object": { "version": "1.0.6", @@ -17767,8 +17613,7 @@ "is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" }, "is-weakref": { "version": "1.0.2", @@ -18619,8 +18464,7 @@ "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "dev": true + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==" }, "js-tokens": { "version": "4.0.0", @@ -18794,7 +18638,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, "requires": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -18881,8 +18724,7 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "min-indent": { "version": "1.0.1", @@ -18919,7 +18761,6 @@ "version": "0.42.0", "resolved": "https://registry.npmjs.org/msw/-/msw-0.42.0.tgz", "integrity": "sha512-vB9rzgiGHoQGfkKpp3QZHxobzfuuQOJk+0bff0wtbK8k3P3CaUSt8bCwvExours682AY4mUfTjIkCsxy0JoS3w==", - "dev": true, "requires": { "@mswjs/cookies": "^0.2.0", "@mswjs/interceptors": "^0.16.3", @@ -18947,7 +18788,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -18957,7 +18797,6 @@ "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, "requires": { "whatwg-url": "^5.0.0" } @@ -18965,26 +18804,22 @@ "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, "type-fest": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==" }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, "requires": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -18994,7 +18829,6 @@ "version": "17.4.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.0.tgz", "integrity": "sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA==", - "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -19008,16 +18842,14 @@ "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==", - "dev": true + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==" } } }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "nanoid": { "version": "3.3.2", @@ -19067,8 +18899,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "npm-run-path": { "version": "4.0.1", @@ -19170,7 +19001,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -19199,7 +19029,6 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, "requires": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -19215,14 +19044,12 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "outvariant": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.3.0.tgz", - "integrity": "sha512-yeWM9k6UPfG/nzxdaPlJkB2p08hCg4xP6Lx99F+vP8YF7xyZVfTmJjrrNalkmzudD4WFvNLVudQikqUmF8zhVQ==", - "dev": true + "integrity": "sha512-yeWM9k6UPfG/nzxdaPlJkB2p08hCg4xP6Lx99F+vP8YF7xyZVfTmJjrrNalkmzudD4WFvNLVudQikqUmF8zhVQ==" }, "p-limit": { "version": "1.3.0", @@ -19299,8 +19126,7 @@ "path-to-regexp": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", - "dev": true + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, "path-type": { "version": "4.0.0", @@ -19315,8 +19141,7 @@ "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pirates": { "version": "4.0.5", @@ -19427,11 +19252,6 @@ "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", "dev": true }, - "pretty-format": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", - "integrity": "sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U=" - }, "prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -19609,7 +19429,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -19620,7 +19439,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -19673,8 +19491,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "reselect": { "version": "4.1.5", @@ -19723,7 +19540,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, "requires": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -19747,8 +19563,7 @@ "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" }, "run-parallel": { "version": "1.2.0", @@ -19763,7 +19578,6 @@ "version": "7.5.5", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", - "dev": true, "requires": { "tslib": "^2.1.0" } @@ -19776,8 +19590,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "saxes": { "version": "5.0.1", @@ -19805,8 +19618,7 @@ "set-cookie-parser": { "version": "2.4.8", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", - "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==", - "dev": true + "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" }, "shebang-command": { "version": "2.0.0", @@ -19837,8 +19649,7 @@ "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 + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "sirv": { "version": "1.0.19", @@ -19928,14 +19739,12 @@ "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, "strict-event-emitter": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.4.tgz", "integrity": "sha512-xIqTLS5azUH1djSUsLH9DbP6UnM/nI18vu8d43JigCQEoVsnY+mrlE+qv6kYqs6/1OkMnMIiL6ffedQSZStuoQ==", - "dev": true, "requires": { "events": "^3.3.0" } @@ -19944,7 +19753,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "requires": { "safe-buffer": "~5.2.0" }, @@ -19952,8 +19760,7 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -19971,7 +19778,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -19981,8 +19787,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" } } }, @@ -20026,7 +19831,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -20077,7 +19881,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -20139,8 +19942,7 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "tiny-invariant": { "version": "1.2.0", @@ -20156,7 +19958,6 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, "requires": { "os-tmpdir": "~1.0.2" } @@ -20176,7 +19977,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -20280,7 +20080,7 @@ "version": "4.5.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", - "dev": true + "devOptional": true }, "unbox-primitive": { "version": "1.0.1", @@ -20342,8 +20142,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "v8-compile-cache": { "version": "2.3.0", @@ -20401,7 +20200,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, "requires": { "defaults": "^1.0.3" } @@ -20492,7 +20290,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -20539,8 +20336,7 @@ "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yallist": { "version": "4.0.0", diff --git a/clients/admin-ui/package.json b/clients/admin-ui/package.json index d18aa4b2f..4ad2f47cd 100644 --- a/clients/admin-ui/package.json +++ b/clients/admin-ui/package.json @@ -6,7 +6,7 @@ "dev:mock": "echo '🚨 Running with mock API'; NEXT_PUBLIC_MOCK_API=true next dev", "build": "next build && next export", "start": "next start", - "lint": "eslint . --ext .ts,.tsx", + "lint": "eslint --fix . --ext .ts,.tsx", "format": "prettier --write .", "test": "jest --watch", "test:ci": "jest", diff --git a/clients/admin-ui/src/features/auth/auth.slice.ts b/clients/admin-ui/src/features/auth/auth.slice.ts index a71cd8e49..8470f7fa2 100644 --- a/clients/admin-ui/src/features/auth/auth.slice.ts +++ b/clients/admin-ui/src/features/auth/auth.slice.ts @@ -3,13 +3,13 @@ import { createSlice, PayloadAction, } from '@reduxjs/toolkit'; -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import {createApi, fetchBaseQuery} from '@reduxjs/toolkit/query/react'; import type { RootState } from '../../app/store'; -import { BASE_API_URN, STORED_CREDENTIALS_KEY } from '../../constants'; +import {BASE_API_URN, STORED_CREDENTIALS_KEY} from '../../constants'; +import {addCommonHeaders} from "../common/CommonHeaders"; import { User } from '../user-management/types'; import { LoginRequest, LoginResponse } from './types'; -import {buildBaseQuery} from "../common/helpers"; export interface AuthState { user: User | null; @@ -72,9 +72,15 @@ credentialStorage.startListening({ }); // Auth API -export const authApi = createApi({ +export const authApi: any = createApi({ reducerPath: 'authApi', - baseQuery: buildBaseQuery(), + baseQuery: fetchBaseQuery({ + baseUrl: BASE_API_URN, + prepareHeaders: (headers, {getState}) => { + const token: string | null = selectToken(getState() as RootState); + return addCommonHeaders(headers, token) + }, + }), tagTypes: ['Auth'], endpoints: (build) => ({ login: build.mutation({ diff --git a/clients/admin-ui/src/features/common/CommonHeaders.ts b/clients/admin-ui/src/features/common/CommonHeaders.ts new file mode 100644 index 000000000..92f5df1b8 --- /dev/null +++ b/clients/admin-ui/src/features/common/CommonHeaders.ts @@ -0,0 +1,13 @@ + + +/** + * Adds common headers to all api calls to fidesops + */ +export function addCommonHeaders(headers: Headers, token: string | null) { + headers.set('Access-Control-Allow-Origin', '*'); + headers.set('X-Fides-Source', 'fidesops-admin-ui'); + if (token) { + headers.set('authorization', `Bearer ${token}`); + } + return headers; +} \ No newline at end of file diff --git a/clients/admin-ui/src/features/common/helpers.ts b/clients/admin-ui/src/features/common/helpers.ts index 31a9a70a1..d73be81c2 100644 --- a/clients/admin-ui/src/features/common/helpers.ts +++ b/clients/admin-ui/src/features/common/helpers.ts @@ -3,9 +3,6 @@ */ import { FetchBaseQueryError } from '@reduxjs/toolkit/query'; -import { fetchBaseQuery } from '@reduxjs/toolkit/query/react'; -import {RootState} from "../../app/store"; -import {selectToken} from "../auth"; /** * Type predicate to narrow an unknown error to `FetchBaseQueryError` @@ -30,22 +27,6 @@ export function isErrorWithMessage( ); } - -export function buildBaseQuery() { - return fetchBaseQuery({ - baseUrl: process.env.NEXT_PUBLIC_FIDESOPS_API!, - prepareHeaders: (headers, {getState}) => { - const token = selectToken(getState() as RootState); - headers.set('Access-Control-Allow-Origin', '*'); - headers.set('X-Fides-Source', 'fidesops-admin-ui'); - if (token) { - headers.set('authorization', `Bearer ${token}`); - } - return headers; - }, - }) -} - // generic error of the structure we expect from the Fidesops backend interface ResponseError { data: { diff --git a/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts b/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts index 6856d154e..d0694a22c 100644 --- a/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts +++ b/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts @@ -1,8 +1,10 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { createApi } from '@reduxjs/toolkit/query/react'; +import {createApi, fetchBaseQuery} from '@reduxjs/toolkit/query/react'; import type { RootState } from '../../app/store'; import { BASE_API_URN } from '../../constants'; +import {selectToken} from "../auth"; +import {addCommonHeaders} from "../common/CommonHeaders"; import { DenyPrivacyRequest, PrivacyRequest, @@ -10,7 +12,6 @@ import { PrivacyRequestResponse, PrivacyRequestStatus, } from './types'; -import { buildBaseQuery } from '../common/helpers'; // Helpers export function mapFiltersToSearchParams({ @@ -47,9 +48,15 @@ export function mapFiltersToSearchParams({ } // Subject requests API -export const privacyRequestApi = createApi({ +export const privacyRequestApi: any = createApi({ reducerPath: 'privacyRequestApi', - baseQuery: buildBaseQuery(), + baseQuery: fetchBaseQuery({ + baseUrl: BASE_API_URN, + prepareHeaders: (headers, {getState}) => { + const token: string | null = selectToken(getState() as RootState); + return addCommonHeaders(headers, token) + }, + }), tagTypes: ['Request'], endpoints: (build) => ({ getAllPrivacyRequests: build.query< diff --git a/clients/admin-ui/src/features/user-management/user-management.slice.ts b/clients/admin-ui/src/features/user-management/user-management.slice.ts index a652a56fb..925e496fd 100644 --- a/clients/admin-ui/src/features/user-management/user-management.slice.ts +++ b/clients/admin-ui/src/features/user-management/user-management.slice.ts @@ -1,7 +1,10 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { createApi } from '@reduxjs/toolkit/query/react'; +import {createApi, fetchBaseQuery} from '@reduxjs/toolkit/query/react'; import type { RootState } from '../../app/store'; +import {BASE_API_URN} from "../../constants"; +import {selectToken} from "../auth"; +import {addCommonHeaders} from "../common/CommonHeaders"; import { User, UserPasswordUpdate, @@ -11,7 +14,6 @@ import { UsersListParams, UsersResponse, } from './types'; -import { buildBaseQuery } from '../common/helpers'; export interface UserManagementState { id: string; @@ -75,9 +77,15 @@ export const mapFiltersToSearchParams = ({ ...(username ? { username } : {}), }); -export const userApi = createApi({ +export const userApi: any = createApi({ reducerPath: 'userApi', - baseQuery: buildBaseQuery(), + baseQuery: fetchBaseQuery({ + baseUrl: BASE_API_URN, + prepareHeaders: (headers, {getState}) => { + const token: string | null = selectToken(getState() as RootState); + return addCommonHeaders(headers, token) + }, + }), tagTypes: ['User'], endpoints: (build) => ({ getAllUsers: build.query({ From 3d0480d0588d0afefd73f58e2e667646f42883d1 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Tue, 14 Jun 2022 12:11:56 -0400 Subject: [PATCH 10/26] fix build issues in admin ui --- clients/admin-ui/src/features/privacy-requests/RequestTable.tsx | 2 +- .../src/features/user-management/UserManagementTable.tsx | 2 +- clients/admin-ui/src/pages/api/auth/[...nextauth].ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/clients/admin-ui/src/features/privacy-requests/RequestTable.tsx b/clients/admin-ui/src/features/privacy-requests/RequestTable.tsx index 93c08d763..6ca0c37d5 100644 --- a/clients/admin-ui/src/features/privacy-requests/RequestTable.tsx +++ b/clients/admin-ui/src/features/privacy-requests/RequestTable.tsx @@ -75,7 +75,7 @@ const RequestTable: React.FC = () => { - {requests.map((request) => ( + {requests.map((request: any) => ( ))} diff --git a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx index a91e5e7e9..de0e98e38 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx @@ -66,7 +66,7 @@ const UserManagementTable: React.FC = () => { - {users?.map((user) => ( + {users?.map((user: any) => ( ))} diff --git a/clients/admin-ui/src/pages/api/auth/[...nextauth].ts b/clients/admin-ui/src/pages/api/auth/[...nextauth].ts index e69de29bb..336ce12bb 100644 --- a/clients/admin-ui/src/pages/api/auth/[...nextauth].ts +++ b/clients/admin-ui/src/pages/api/auth/[...nextauth].ts @@ -0,0 +1 @@ +export {} From d768f133e049b14dec255274e1330692043afaf7 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Tue, 14 Jun 2022 12:57:18 -0400 Subject: [PATCH 11/26] mypy return type --- src/fidesops/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fidesops/main.py b/src/fidesops/main.py index 74247b604..538f8b3e1 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -95,7 +95,7 @@ def prepare_and_log_request( event_created_at: datetime, fides_source: Optional[str], error_class: Optional[str], -): +) -> None: analytics_event = AnalyticsEvent( docker=in_docker_container(), event=Event.endpoint_call.value, From ca8c37eefacb64cf4bd1c54ffd29e0b04c79e0db Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Tue, 14 Jun 2022 15:48:39 -0400 Subject: [PATCH 12/26] set dev mode to false in graph test --- tests/graph/test_graph.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/graph/test_graph.py b/tests/graph/test_graph.py index 97da4a3ba..514beb215 100644 --- a/tests/graph/test_graph.py +++ b/tests/graph/test_graph.py @@ -70,6 +70,7 @@ def test_retry_decorator(privacy_request, policy): CollectionAddress("postgres_example", "payment_card") ] + config.dev_mode = False config.execution.TASK_RETRY_COUNT = 5 config.execution.TASK_RETRY_DELAY = 0.1 config.execution.TASK_RETRY_BACKOFF = 0.01 From 26ce57e663034a36bab0fc020f700fbac347bf96 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Tue, 14 Jun 2022 15:58:48 -0400 Subject: [PATCH 13/26] unintended eslint change --- clients/admin-ui/package.json | 2 +- clients/admin-ui/src/features/common/CommonHeaders.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/admin-ui/package.json b/clients/admin-ui/package.json index 4ad2f47cd..d18aa4b2f 100644 --- a/clients/admin-ui/package.json +++ b/clients/admin-ui/package.json @@ -6,7 +6,7 @@ "dev:mock": "echo '🚨 Running with mock API'; NEXT_PUBLIC_MOCK_API=true next dev", "build": "next build && next export", "start": "next start", - "lint": "eslint --fix . --ext .ts,.tsx", + "lint": "eslint . --ext .ts,.tsx", "format": "prettier --write .", "test": "jest --watch", "test:ci": "jest", diff --git a/clients/admin-ui/src/features/common/CommonHeaders.ts b/clients/admin-ui/src/features/common/CommonHeaders.ts index 92f5df1b8..aa93eb372 100644 --- a/clients/admin-ui/src/features/common/CommonHeaders.ts +++ b/clients/admin-ui/src/features/common/CommonHeaders.ts @@ -10,4 +10,4 @@ export function addCommonHeaders(headers: Headers, token: string | null) { headers.set('authorization', `Bearer ${token}`); } return headers; -} \ No newline at end of file +} From 1b1af46996f36b8b03e1125bd51e0430c98078b0 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Wed, 15 Jun 2022 08:39:42 -0400 Subject: [PATCH 14/26] adds dev mode false to additional tests --- tests/integration_tests/test_sql_task.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration_tests/test_sql_task.py b/tests/integration_tests/test_sql_task.py index 368d901e3..d03f9b634 100644 --- a/tests/integration_tests/test_sql_task.py +++ b/tests/integration_tests/test_sql_task.py @@ -956,6 +956,7 @@ def test_retry_access_request( policy, integration_postgres_config, ): + config.dev_mode = False config.execution.TASK_RETRY_COUNT = 1 config.execution.TASK_RETRY_DELAY = 0.1 config.execution.TASK_RETRY_BACKOFF = 0.01 @@ -1007,6 +1008,7 @@ def test_retry_erasure( policy, integration_postgres_config, ): + config.dev_mode = False config.execution.TASK_RETRY_COUNT = 2 config.execution.TASK_RETRY_DELAY = 0.1 config.execution.TASK_RETRY_BACKOFF = 0.01 From 4c661c9596ec9d90e64891ac79600d278f8d13cc Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Wed, 15 Jun 2022 09:18:22 -0400 Subject: [PATCH 15/26] turn dev mode on for test_manual_task --- tests/integration_tests/test_manual_task.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration_tests/test_manual_task.py b/tests/integration_tests/test_manual_task.py index c49e3a888..138bd47a4 100644 --- a/tests/integration_tests/test_manual_task.py +++ b/tests/integration_tests/test_manual_task.py @@ -3,6 +3,7 @@ import pytest from fidesops.common_exceptions import PrivacyRequestPaused +from fidesops.core.config import config from fidesops.graph.config import CollectionAddress from fidesops.models.policy import PausedStep from fidesops.models.privacy_request import ( @@ -26,6 +27,7 @@ def test_postgres_with_manual_input_access_request_task( integration_manual_config, ) -> None: """Run a privacy request with two manual nodes""" + config.dev_mode = False privacy_request = PrivacyRequest( id=f"test_postgres_access_request_task_{uuid.uuid4()}" ) From f3baadfb5748835768c38d1034e6961bc1f029dc Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Wed, 15 Jun 2022 09:58:06 -0400 Subject: [PATCH 16/26] adds dev mode false to test_execution --- tests/integration_tests/test_execution.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/integration_tests/test_execution.py b/tests/integration_tests/test_execution.py index 6890af100..0141da652 100644 --- a/tests/integration_tests/test_execution.py +++ b/tests/integration_tests/test_execution.py @@ -5,6 +5,7 @@ from pydantic import ValidationError from sqlalchemy.exc import InvalidRequestError +from fidesops.core.config import config from fidesops.db.session import get_db_session from fidesops.graph.config import CollectionAddress from fidesops.graph.graph import DatasetGraph @@ -183,6 +184,8 @@ def test_collection_omitted_on_restart_from_failure( """Remove secrets to make privacy request fail, then delete the connection config. Build a graph that does not contain the deleted dataset config and re-run.""" + config.dev_mode = False + integration_mongodb_config.secrets = {} integration_mongodb_config.save(db) @@ -438,6 +441,8 @@ def test_skip_collection_on_restart( """Remove secrets to make privacy request fail, then disable connection config and confirm that datastores are skipped on re-run""" + config.dev_mode = False + integration_mongodb_config.secrets = {} integration_mongodb_config.save(db) @@ -562,6 +567,8 @@ def test_restart_graph_from_failure( ) -> None: """Run a failed privacy request and restart from failure""" + config.dev_mode = False + privacy_request = PrivacyRequest( id=f"test_postgres_access_request_task_{uuid.uuid4()}" ) From 8f9d1e2b6706d9d3e5e645b45ea5ef29a6e8ca32 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Wed, 15 Jun 2022 10:21:29 -0400 Subject: [PATCH 17/26] prettier --- clients/admin-ui/src/features/auth/auth.slice.ts | 4 ++-- .../admin-ui/src/features/common/CommonHeaders.ts | 14 ++++++-------- .../privacy-requests/privacy-requests.slice.ts | 6 +++--- .../user-management/user-management.slice.ts | 4 ++-- .../admin-ui/src/pages/api/auth/[...nextauth].ts | 2 +- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/clients/admin-ui/src/features/auth/auth.slice.ts b/clients/admin-ui/src/features/auth/auth.slice.ts index 434308293..decb042d5 100644 --- a/clients/admin-ui/src/features/auth/auth.slice.ts +++ b/clients/admin-ui/src/features/auth/auth.slice.ts @@ -76,9 +76,9 @@ export const authApi: any = createApi({ reducerPath: "authApi", baseQuery: fetchBaseQuery({ baseUrl: BASE_API_URN, - prepareHeaders: (headers, {getState}) => { + prepareHeaders: (headers, { getState }) => { const token: string | null = selectToken(getState() as RootState); - return addCommonHeaders(headers, token) + return addCommonHeaders(headers, token); }, }), tagTypes: ["Auth"], diff --git a/clients/admin-ui/src/features/common/CommonHeaders.ts b/clients/admin-ui/src/features/common/CommonHeaders.ts index aa93eb372..bad68afe0 100644 --- a/clients/admin-ui/src/features/common/CommonHeaders.ts +++ b/clients/admin-ui/src/features/common/CommonHeaders.ts @@ -1,13 +1,11 @@ - - /** * Adds common headers to all api calls to fidesops */ export function addCommonHeaders(headers: Headers, token: string | null) { - headers.set('Access-Control-Allow-Origin', '*'); - headers.set('X-Fides-Source', 'fidesops-admin-ui'); - if (token) { - headers.set('authorization', `Bearer ${token}`); - } - return headers; + headers.set("Access-Control-Allow-Origin", "*"); + headers.set("X-Fides-Source", "fidesops-admin-ui"); + if (token) { + headers.set("authorization", `Bearer ${token}`); + } + return headers; } diff --git a/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts b/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts index b89d72471..d5e768013 100644 --- a/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts +++ b/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts @@ -52,9 +52,9 @@ export const privacyRequestApi: any = createApi({ reducerPath: "privacyRequestApi", baseQuery: fetchBaseQuery({ baseUrl: BASE_API_URN, - prepareHeaders: (headers, {getState}) => { + prepareHeaders: (headers, { getState }) => { const token: string | null = selectToken(getState() as RootState); - return addCommonHeaders(headers, token) + return addCommonHeaders(headers, token); }, }), tagTypes: ["Request"], @@ -127,7 +127,7 @@ export const requestCSVDownload = async ({ headers: { "Access-Control-Allow-Origin": "*", Authorization: `Bearer ${token}`, - 'X-Fides-Source': 'fidesops-admin-ui' + "X-Fides-Source": "fidesops-admin-ui", }, } ) diff --git a/clients/admin-ui/src/features/user-management/user-management.slice.ts b/clients/admin-ui/src/features/user-management/user-management.slice.ts index 0c81fd57f..4f5e4b5ad 100644 --- a/clients/admin-ui/src/features/user-management/user-management.slice.ts +++ b/clients/admin-ui/src/features/user-management/user-management.slice.ts @@ -81,9 +81,9 @@ export const userApi: any = createApi({ reducerPath: "userApi", baseQuery: fetchBaseQuery({ baseUrl: BASE_API_URN, - prepareHeaders: (headers, {getState}) => { + prepareHeaders: (headers, { getState }) => { const token: string | null = selectToken(getState() as RootState); - return addCommonHeaders(headers, token) + return addCommonHeaders(headers, token); }, }), tagTypes: ["User"], diff --git a/clients/admin-ui/src/pages/api/auth/[...nextauth].ts b/clients/admin-ui/src/pages/api/auth/[...nextauth].ts index 336ce12bb..cb0ff5c3b 100644 --- a/clients/admin-ui/src/pages/api/auth/[...nextauth].ts +++ b/clients/admin-ui/src/pages/api/auth/[...nextauth].ts @@ -1 +1 @@ -export {} +export {}; From 53f414e53cfa4396a8d27cc68bccd0da1268809d Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Thu, 16 Jun 2022 17:45:03 -0400 Subject: [PATCH 18/26] adds unit test for middleware, removes dev mode checks --- src/fidesops/main.py | 38 ++---- src/fidesops/task/graph_task.py | 3 - tests/conftest.py | 9 ++ tests/graph/test_graph.py | 1 - tests/integration_tests/test_execution.py | 6 - tests/integration_tests/test_manual_task.py | 1 - tests/integration_tests/test_sql_task.py | 2 - tests/test_main.py | 139 ++++++++++++++++++++ 8 files changed, 158 insertions(+), 41 deletions(-) create mode 100644 tests/test_main.py diff --git a/src/fidesops/main.py b/src/fidesops/main.py index 538f8b3e1..0edead19f 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -47,44 +47,26 @@ async def dispatch_log_request(request: Request, call_next: Callable) -> Response: fides_source: Optional[str] = request.headers.get("X-Fides-Source") now: datetime = datetime.now(tz=timezone.utc) - endpoint = f"{request.method}: {request.url.path}" + endpoint = f"{request.method}: {request.url}" try: response = await call_next(request) # HTTPExceptions are considered a handled err by default so are not thrown here. # Accepted workaround is to inspect status code of response. # More context- https://github.com/tiangolo/fastapi/issues/1840 - try: - if response.status_code >= 400: - response.background = BackgroundTask( - prepare_and_log_request, - endpoint, - response.status_code, - now, - fides_source, - "HTTPException", - ) - else: - response.background = BackgroundTask( - prepare_and_log_request, - endpoint, - response.status_code, - now, - fides_source, - None, - ) - except Exception as exc: - # always continue if something went wrong with analytics - logger.warning( - f"Analytics event for endpoint {request.url} failed to send: {exc}" - ) - return response + response.background = BackgroundTask( + prepare_and_log_request, + endpoint, + response.status_code, + now, + fides_source, + "HTTPException" if response.status_code >= 400 else None + ) return response except Exception as e: - logger.warning("exception caught") prepare_and_log_request( - endpoint, e.args[0], now, fides_source, e.__class__.__name__ + endpoint, 500, now, fides_source, e.__class__.__name__ ) raise diff --git a/src/fidesops/task/graph_task.py b/src/fidesops/task/graph_task.py index 39e913fd4..c01c44137 100644 --- a/src/fidesops/task/graph_task.py +++ b/src/fidesops/task/graph_task.py @@ -63,9 +63,6 @@ def result(*args: Any, **kwargs: Any) -> List[Optional[Row]]: self = args[0] raised_ex = None - if config.dev_mode: - # If dev mode, return here so exception isn't caught - return func(*args, **kwargs) for attempt in range(config.execution.TASK_RETRY_COUNT + 1): try: self.skip_if_disabled() diff --git a/tests/conftest.py b/tests/conftest.py index 7fe27599f..4c50932d7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -162,3 +162,12 @@ def integration_config() -> MutableMapping[str, Any]: def celery_enable_logging(): """Turns on celery output logs.""" return True + + +@pytest.fixture(autouse=True, scope="session") +def analytics_opt_out(): + """Disable sending analytics when running tests.""" + original_value = config.root_user.ANALYTICS_OPT_OUT + config.root_user.ANALYTICS_OPT_OUT = True + yield + config.root_user.ANALYTICS_OPT_OUT = original_value diff --git a/tests/graph/test_graph.py b/tests/graph/test_graph.py index 514beb215..97da4a3ba 100644 --- a/tests/graph/test_graph.py +++ b/tests/graph/test_graph.py @@ -70,7 +70,6 @@ def test_retry_decorator(privacy_request, policy): CollectionAddress("postgres_example", "payment_card") ] - config.dev_mode = False config.execution.TASK_RETRY_COUNT = 5 config.execution.TASK_RETRY_DELAY = 0.1 config.execution.TASK_RETRY_BACKOFF = 0.01 diff --git a/tests/integration_tests/test_execution.py b/tests/integration_tests/test_execution.py index 0141da652..103ab67c1 100644 --- a/tests/integration_tests/test_execution.py +++ b/tests/integration_tests/test_execution.py @@ -184,8 +184,6 @@ def test_collection_omitted_on_restart_from_failure( """Remove secrets to make privacy request fail, then delete the connection config. Build a graph that does not contain the deleted dataset config and re-run.""" - config.dev_mode = False - integration_mongodb_config.secrets = {} integration_mongodb_config.save(db) @@ -441,8 +439,6 @@ def test_skip_collection_on_restart( """Remove secrets to make privacy request fail, then disable connection config and confirm that datastores are skipped on re-run""" - config.dev_mode = False - integration_mongodb_config.secrets = {} integration_mongodb_config.save(db) @@ -567,8 +563,6 @@ def test_restart_graph_from_failure( ) -> None: """Run a failed privacy request and restart from failure""" - config.dev_mode = False - privacy_request = PrivacyRequest( id=f"test_postgres_access_request_task_{uuid.uuid4()}" ) diff --git a/tests/integration_tests/test_manual_task.py b/tests/integration_tests/test_manual_task.py index 138bd47a4..520bd0352 100644 --- a/tests/integration_tests/test_manual_task.py +++ b/tests/integration_tests/test_manual_task.py @@ -27,7 +27,6 @@ def test_postgres_with_manual_input_access_request_task( integration_manual_config, ) -> None: """Run a privacy request with two manual nodes""" - config.dev_mode = False privacy_request = PrivacyRequest( id=f"test_postgres_access_request_task_{uuid.uuid4()}" ) diff --git a/tests/integration_tests/test_sql_task.py b/tests/integration_tests/test_sql_task.py index d03f9b634..368d901e3 100644 --- a/tests/integration_tests/test_sql_task.py +++ b/tests/integration_tests/test_sql_task.py @@ -956,7 +956,6 @@ def test_retry_access_request( policy, integration_postgres_config, ): - config.dev_mode = False config.execution.TASK_RETRY_COUNT = 1 config.execution.TASK_RETRY_DELAY = 0.1 config.execution.TASK_RETRY_BACKOFF = 0.01 @@ -1008,7 +1007,6 @@ def test_retry_erasure( policy, integration_postgres_config, ): - config.dev_mode = False config.execution.TASK_RETRY_COUNT = 2 config.execution.TASK_RETRY_DELAY = 0.1 config.execution.TASK_RETRY_BACKOFF = 0.01 diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 000000000..73cee13a6 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,139 @@ +import asyncio +from datetime import datetime, timezone +from typing import Any +from unittest import mock +from unittest.mock import Mock + +import pytest +from fideslog.sdk.python.event import AnalyticsEvent +from starlette.requests import Request +from starlette.responses import Response + +from fidesops.analytics import in_docker_container, running_on_local_host +from fidesops.main import dispatch_log_request +from fidesops.schemas.analytics import Event +from fidesops.util.async_util import wait_for + + +class CustomTestException(BaseException): + """Mock Non-HTTP Exception""" + pass + + +@mock.patch("fidesops.analytics.send_analytics_event") +@mock.patch("datetime.datetime") +def test_dispatch_log_request_no_exception( + datetime_mock: Mock, + send_analytics_request_mock: Mock, +) -> None: + request = Request( + scope={ + "type": "http", + "method": 'GET', + "path": "/testing", + "server": { + "host": "www.testsite.com", + "port": 3000 + }, + "headers": {} + }, + ) + + datetime_mock.now.return_value = datetime(2022, 5, 22, tzinfo=timezone.utc) + + f = asyncio.Future() + f.set_result(Response(status_code=200)) + + def mock_call_next(request: Request) -> Any: + return f + + wait_for(dispatch_log_request(request, mock_call_next)) + assert send_analytics_request_mock.assert_called_once_with( + AnalyticsEvent( + docker=in_docker_container(), + event=Event.endpoint_call.value, + event_created_at=datetime_mock.now.return_value, + local_host=running_on_local_host(), + endpoint="GET: http://www.testsite.com/testing", + status_code=200, + ) + ) + + +@mock.patch("fidesops.analytics.send_analytics_event") +@mock.patch("datetime.datetime") +def test_dispatch_log_request_http_exception( + datetime_mock: Mock, + send_analytics_request_mock: Mock, +) -> None: + request = Request( + scope={ + "type": "http", + "method": 'GET', + "path": "/testing", + "server": { + "host": "www.testsite.com", + "port": 3000 + }, + "headers": {} + }, + ) + + datetime_mock.now.return_value = datetime(2022, 5, 22, tzinfo=timezone.utc) + + f = asyncio.Future() + f.set_result(Response(status_code=404)) + + def mock_call_next(request: Request) -> Any: + return f + + wait_for(dispatch_log_request(request, mock_call_next)) + assert send_analytics_request_mock.assert_called_once_with( + AnalyticsEvent( + docker=in_docker_container(), + event=Event.endpoint_call.value, + event_created_at=datetime_mock.now.return_value, + local_host=running_on_local_host(), + endpoint="GET: http://www.testsite.com/testing", + status_code=404, + ) + ) + + +@mock.patch("fidesops.analytics.send_analytics_event") +@mock.patch("datetime.datetime") +def test_dispatch_log_request_other_exception( + datetime_mock: Mock, + send_analytics_request_mock: Mock, +) -> None: + request = Request( + scope={ + "type": "http", + "method": 'GET', + "path": "/testing", + "server": { + "host": "www.testsite.com", + "port": 3000 + }, + "headers": {} + }, + ) + + datetime_mock.now.return_value = datetime(2022, 5, 22) + + def mock_call_next(request: Request) -> Any: + raise CustomTestException(BaseException) + + with pytest.raises(CustomTestException): + wait_for(dispatch_log_request(request, mock_call_next)) + assert send_analytics_request_mock.assert_called_once_with( + AnalyticsEvent( + docker=in_docker_container(), + event=Event.endpoint_call.value, + event_created_at=datetime_mock.now.return_value, + local_host=running_on_local_host(), + endpoint="GET: http://www.testsite.com/testing", + status_code=500, + error="CustomTestException" + ) + ) From e95ca1d9f8317c44006e099031ef1b1952590d96 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Fri, 17 Jun 2022 10:50:59 -0400 Subject: [PATCH 19/26] remove some tests for now, formatting --- src/fidesops/main.py | 6 ++-- tests/test_main.py | 69 ++++++++------------------------------------ 2 files changed, 14 insertions(+), 61 deletions(-) diff --git a/src/fidesops/main.py b/src/fidesops/main.py index 0edead19f..ba68d1b04 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -60,14 +60,12 @@ async def dispatch_log_request(request: Request, call_next: Callable) -> Respons response.status_code, now, fides_source, - "HTTPException" if response.status_code >= 400 else None + "HTTPException" if response.status_code >= 400 else None, ) return response except Exception as e: - prepare_and_log_request( - endpoint, 500, now, fides_source, e.__class__.__name__ - ) + prepare_and_log_request(endpoint, 500, now, fides_source, e.__class__.__name__) raise diff --git a/tests/test_main.py b/tests/test_main.py index 73cee13a6..d3ec0b906 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -17,65 +17,23 @@ class CustomTestException(BaseException): """Mock Non-HTTP Exception""" - pass - - -@mock.patch("fidesops.analytics.send_analytics_event") -@mock.patch("datetime.datetime") -def test_dispatch_log_request_no_exception( - datetime_mock: Mock, - send_analytics_request_mock: Mock, -) -> None: - request = Request( - scope={ - "type": "http", - "method": 'GET', - "path": "/testing", - "server": { - "host": "www.testsite.com", - "port": 3000 - }, - "headers": {} - }, - ) - - datetime_mock.now.return_value = datetime(2022, 5, 22, tzinfo=timezone.utc) - - f = asyncio.Future() - f.set_result(Response(status_code=200)) - - def mock_call_next(request: Request) -> Any: - return f - wait_for(dispatch_log_request(request, mock_call_next)) - assert send_analytics_request_mock.assert_called_once_with( - AnalyticsEvent( - docker=in_docker_container(), - event=Event.endpoint_call.value, - event_created_at=datetime_mock.now.return_value, - local_host=running_on_local_host(), - endpoint="GET: http://www.testsite.com/testing", - status_code=200, - ) - ) + pass @mock.patch("fidesops.analytics.send_analytics_event") @mock.patch("datetime.datetime") def test_dispatch_log_request_http_exception( - datetime_mock: Mock, - send_analytics_request_mock: Mock, + datetime_mock: Mock, + send_analytics_request_mock: Mock, ) -> None: request = Request( scope={ "type": "http", - "method": 'GET', + "method": "GET", "path": "/testing", - "server": { - "host": "www.testsite.com", - "port": 3000 - }, - "headers": {} + "server": {"host": "www.testsite.com", "port": 3000}, + "headers": {}, }, ) @@ -103,19 +61,16 @@ def mock_call_next(request: Request) -> Any: @mock.patch("fidesops.analytics.send_analytics_event") @mock.patch("datetime.datetime") def test_dispatch_log_request_other_exception( - datetime_mock: Mock, - send_analytics_request_mock: Mock, + datetime_mock: Mock, + send_analytics_request_mock: Mock, ) -> None: request = Request( scope={ "type": "http", - "method": 'GET', + "method": "GET", "path": "/testing", - "server": { - "host": "www.testsite.com", - "port": 3000 - }, - "headers": {} + "server": {"host": "www.testsite.com", "port": 3000}, + "headers": {}, }, ) @@ -134,6 +89,6 @@ def mock_call_next(request: Request) -> Any: local_host=running_on_local_host(), endpoint="GET: http://www.testsite.com/testing", status_code=500, - error="CustomTestException" + error="CustomTestException", ) ) From 0397684b43877cac3165e8d1becba814334bf387 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Fri, 17 Jun 2022 16:19:50 -0400 Subject: [PATCH 20/26] bump fideslog version, move main.py test to existing file, add str replace for hostname validation --- requirements.txt | 2 +- src/fidesops/main.py | 10 ++++- tests/api/v1/test_main.py | 54 ++++++++++++++++++++++ tests/test_main.py | 94 --------------------------------------- 4 files changed, 63 insertions(+), 97 deletions(-) delete mode 100644 tests/test_main.py diff --git a/requirements.txt b/requirements.txt index 8ecb65d96..eac5acc84 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ fastapi-pagination[sqlalchemy]~= 0.8.3 fastapi[all]==0.78.0 fideslang==1.0.0 fideslib==2.0.4 -fideslog==1.1.5 +fideslog==1.2.0 multidimensional_urlencode==0.0.4 pandas==1.3.3 passlib[bcrypt]==1.7.4 diff --git a/src/fidesops/main.py b/src/fidesops/main.py index ba68d1b04..901000c15 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -47,7 +47,13 @@ async def dispatch_log_request(request: Request, call_next: Callable) -> Response: fides_source: Optional[str] = request.headers.get("X-Fides-Source") now: datetime = datetime.now(tz=timezone.utc) - endpoint = f"{request.method}: {request.url}" + valid_hostname = request.url.hostname + # only needed until we implement validator fix in fideslog + if valid_hostname == "0.0.0.0": + valid_hostname = "localhost" + endpoint = ( + f"{request.method}: {request.url.scheme}://{valid_hostname}{request.url.path}" + ) try: response = await call_next(request) @@ -62,8 +68,8 @@ async def dispatch_log_request(request: Request, call_next: Callable) -> Respons fides_source, "HTTPException" if response.status_code >= 400 else None, ) - return response + except Exception as e: prepare_and_log_request(endpoint, 500, now, fides_source, e.__class__.__name__) raise diff --git a/tests/api/v1/test_main.py b/tests/api/v1/test_main.py index 901f1940b..92994731b 100644 --- a/tests/api/v1/test_main.py +++ b/tests/api/v1/test_main.py @@ -1,9 +1,63 @@ +from datetime import datetime, timezone +from typing import Any +from unittest import mock +from unittest.mock import Mock + +import pytest +from fideslog.sdk.python.event import AnalyticsEvent +from starlette.requests import Request from starlette.testclient import TestClient +from fidesops.analytics import in_docker_container, running_on_local_host from fidesops.api.v1.urn_registry import V1_URL_PREFIX +from fidesops.main import dispatch_log_request +from fidesops.schemas.analytics import Event +from fidesops.util.async_util import wait_for + + +class CustomTestException(BaseException): + """Mock Non-HTTP Exception""" + + pass def test_read_autogenerated_docs(api_client: TestClient): """Test to ensure automatically generated docs build properly""" response = api_client.get(f"{V1_URL_PREFIX}/openapi.json") assert response.status_code == 200 + + +@mock.patch("fidesops.analytics.send_analytics_event") +@mock.patch("datetime.datetime") +def test_dispatch_log_request_other_exception( + datetime_mock: Mock, + send_analytics_request_mock: Mock, +) -> None: + request = Request( + scope={ + "type": "http", + "method": "GET", + "path": "/testing", + "server": {"host": "www.testsite.com", "port": 3000}, + "headers": {}, + }, + ) + + datetime_mock.now.return_value = datetime(2022, 5, 22, tzinfo=timezone.utc) + + def mock_call_next(request: Request) -> Any: + raise CustomTestException(BaseException) + + with pytest.raises(CustomTestException): + wait_for(dispatch_log_request(request, mock_call_next)) + assert send_analytics_request_mock.assert_called_once_with( + AnalyticsEvent( + docker=in_docker_container(), + event=Event.endpoint_call.value, + event_created_at=datetime_mock.now.return_value, + local_host=running_on_local_host(), + endpoint="GET: http://www.testsite.com/testing", + status_code=500, + error="CustomTestException", + ) + ) diff --git a/tests/test_main.py b/tests/test_main.py deleted file mode 100644 index d3ec0b906..000000000 --- a/tests/test_main.py +++ /dev/null @@ -1,94 +0,0 @@ -import asyncio -from datetime import datetime, timezone -from typing import Any -from unittest import mock -from unittest.mock import Mock - -import pytest -from fideslog.sdk.python.event import AnalyticsEvent -from starlette.requests import Request -from starlette.responses import Response - -from fidesops.analytics import in_docker_container, running_on_local_host -from fidesops.main import dispatch_log_request -from fidesops.schemas.analytics import Event -from fidesops.util.async_util import wait_for - - -class CustomTestException(BaseException): - """Mock Non-HTTP Exception""" - - pass - - -@mock.patch("fidesops.analytics.send_analytics_event") -@mock.patch("datetime.datetime") -def test_dispatch_log_request_http_exception( - datetime_mock: Mock, - send_analytics_request_mock: Mock, -) -> None: - request = Request( - scope={ - "type": "http", - "method": "GET", - "path": "/testing", - "server": {"host": "www.testsite.com", "port": 3000}, - "headers": {}, - }, - ) - - datetime_mock.now.return_value = datetime(2022, 5, 22, tzinfo=timezone.utc) - - f = asyncio.Future() - f.set_result(Response(status_code=404)) - - def mock_call_next(request: Request) -> Any: - return f - - wait_for(dispatch_log_request(request, mock_call_next)) - assert send_analytics_request_mock.assert_called_once_with( - AnalyticsEvent( - docker=in_docker_container(), - event=Event.endpoint_call.value, - event_created_at=datetime_mock.now.return_value, - local_host=running_on_local_host(), - endpoint="GET: http://www.testsite.com/testing", - status_code=404, - ) - ) - - -@mock.patch("fidesops.analytics.send_analytics_event") -@mock.patch("datetime.datetime") -def test_dispatch_log_request_other_exception( - datetime_mock: Mock, - send_analytics_request_mock: Mock, -) -> None: - request = Request( - scope={ - "type": "http", - "method": "GET", - "path": "/testing", - "server": {"host": "www.testsite.com", "port": 3000}, - "headers": {}, - }, - ) - - datetime_mock.now.return_value = datetime(2022, 5, 22) - - def mock_call_next(request: Request) -> Any: - raise CustomTestException(BaseException) - - with pytest.raises(CustomTestException): - wait_for(dispatch_log_request(request, mock_call_next)) - assert send_analytics_request_mock.assert_called_once_with( - AnalyticsEvent( - docker=in_docker_container(), - event=Event.endpoint_call.value, - event_created_at=datetime_mock.now.return_value, - local_host=running_on_local_host(), - endpoint="GET: http://www.testsite.com/testing", - status_code=500, - error="CustomTestException", - ) - ) From 0a8f94b9cec31a360881f2cf309e366b70b9171f Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Fri, 17 Jun 2022 17:58:12 -0400 Subject: [PATCH 21/26] prevents AnalyticsEvent from being called if analytics opt out is true --- src/fidesops/main.py | 28 +++++++++++++---------- tests/api/v1/test_main.py | 48 --------------------------------------- 2 files changed, 16 insertions(+), 60 deletions(-) diff --git a/src/fidesops/main.py b/src/fidesops/main.py index 901000c15..019d752a1 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -82,19 +82,23 @@ def prepare_and_log_request( fides_source: Optional[str], error_class: Optional[str], ) -> None: - analytics_event = AnalyticsEvent( - docker=in_docker_container(), - event=Event.endpoint_call.value, - event_created_at=event_created_at, - local_host=running_on_local_host(), - endpoint=endpoint, - status_code=status_code, + # this check prevents AnalyticsEvent from being called with invalid endpoint during unit tests + if config.root_user.ANALYTICS_OPT_OUT: + return + send_analytics_event( + AnalyticsEvent( + docker=in_docker_container(), + event=Event.endpoint_call.value, + event_created_at=event_created_at, + local_host=running_on_local_host(), + endpoint=endpoint, + status_code=status_code, + error=error_class or None, + extra_data={ExtraData.fides_source.value: fides_source} + if fides_source + else None, + ) ) - if error_class: - analytics_event.error = error_class - if fides_source: - analytics_event.extra_data = {ExtraData.fides_source.value: fides_source} - send_analytics_event(analytics_event) app.include_router(api_router) diff --git a/tests/api/v1/test_main.py b/tests/api/v1/test_main.py index 92994731b..9ff8fa45c 100644 --- a/tests/api/v1/test_main.py +++ b/tests/api/v1/test_main.py @@ -1,18 +1,6 @@ -from datetime import datetime, timezone -from typing import Any -from unittest import mock -from unittest.mock import Mock - -import pytest -from fideslog.sdk.python.event import AnalyticsEvent -from starlette.requests import Request from starlette.testclient import TestClient -from fidesops.analytics import in_docker_container, running_on_local_host from fidesops.api.v1.urn_registry import V1_URL_PREFIX -from fidesops.main import dispatch_log_request -from fidesops.schemas.analytics import Event -from fidesops.util.async_util import wait_for class CustomTestException(BaseException): @@ -25,39 +13,3 @@ def test_read_autogenerated_docs(api_client: TestClient): """Test to ensure automatically generated docs build properly""" response = api_client.get(f"{V1_URL_PREFIX}/openapi.json") assert response.status_code == 200 - - -@mock.patch("fidesops.analytics.send_analytics_event") -@mock.patch("datetime.datetime") -def test_dispatch_log_request_other_exception( - datetime_mock: Mock, - send_analytics_request_mock: Mock, -) -> None: - request = Request( - scope={ - "type": "http", - "method": "GET", - "path": "/testing", - "server": {"host": "www.testsite.com", "port": 3000}, - "headers": {}, - }, - ) - - datetime_mock.now.return_value = datetime(2022, 5, 22, tzinfo=timezone.utc) - - def mock_call_next(request: Request) -> Any: - raise CustomTestException(BaseException) - - with pytest.raises(CustomTestException): - wait_for(dispatch_log_request(request, mock_call_next)) - assert send_analytics_request_mock.assert_called_once_with( - AnalyticsEvent( - docker=in_docker_container(), - event=Event.endpoint_call.value, - event_created_at=datetime_mock.now.return_value, - local_host=running_on_local_host(), - endpoint="GET: http://www.testsite.com/testing", - status_code=500, - error="CustomTestException", - ) - ) From 1ec7db794ed20556a62761dbe60e409f568977b5 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Mon, 20 Jun 2022 16:32:23 -0400 Subject: [PATCH 22/26] bump fideslog version, add docstrings, refactor local host method, add unit tests --- requirements.txt | 2 +- src/fidesops/analytics.py | 9 ++-- src/fidesops/main.py | 27 ++++++----- .../test_connection_config_endpoints.py | 47 +++++++++++++++++++ 4 files changed, 70 insertions(+), 15 deletions(-) diff --git a/requirements.txt b/requirements.txt index eac5acc84..5b8eef566 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ fastapi-pagination[sqlalchemy]~= 0.8.3 fastapi[all]==0.78.0 fideslang==1.0.0 fideslib==2.0.4 -fideslog==1.2.0 +fideslog==1.2.1 multidimensional_urlencode==0.0.4 pandas==1.3.3 passlib[bcrypt]==1.7.4 diff --git a/src/fidesops/analytics.py b/src/fidesops/analytics.py index 90706a487..c060aade4 100644 --- a/src/fidesops/analytics.py +++ b/src/fidesops/analytics.py @@ -1,6 +1,7 @@ import logging import os from platform import system +from typing import Optional from fideslog.sdk.python.client import AnalyticsClient from fideslog.sdk.python.event import AnalyticsEvent @@ -17,9 +18,11 @@ def in_docker_container() -> bool: return bool(os.getenv("RUNNING_IN_DOCKER") == "true") -def running_on_local_host() -> bool: - """For events submitted as a result of making API server requests, `True` if the API server is running on the user's local host. Default: `False`.""" - return True +def accessed_through_local_host(hostname: str) -> bool: + """`True`if the event was submitted through a local host, e,g, 127.0.0.1.""" + # testserver is hostname in unit tests + LOCAL_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1", "testserver"] + return hostname in LOCAL_HOSTS analytics_client = AnalyticsClient( diff --git a/src/fidesops/main.py b/src/fidesops/main.py index 019d752a1..759fd3f1e 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -12,7 +12,7 @@ from fidesops.analytics import ( in_docker_container, - running_on_local_host, + accessed_through_local_host, send_analytics_event, ) from fidesops.api.v1.api import api_router @@ -45,15 +45,15 @@ @app.middleware("http") async def dispatch_log_request(request: Request, call_next: Callable) -> Response: + """ + HTTP Middleware that logs analytics events for each call to Fidesops endpoints. + :param request: Request to fidesops api + :param call_next: Callable api endpoint + :return: Response + """ fides_source: Optional[str] = request.headers.get("X-Fides-Source") now: datetime = datetime.now(tz=timezone.utc) - valid_hostname = request.url.hostname - # only needed until we implement validator fix in fideslog - if valid_hostname == "0.0.0.0": - valid_hostname = "localhost" - endpoint = ( - f"{request.method}: {request.url.scheme}://{valid_hostname}{request.url.path}" - ) + endpoint = f"{request.method}: {request.url}" try: response = await call_next(request) @@ -63,6 +63,7 @@ async def dispatch_log_request(request: Request, call_next: Callable) -> Respons response.background = BackgroundTask( prepare_and_log_request, endpoint, + request.url.hostname, response.status_code, now, fides_source, @@ -71,17 +72,22 @@ async def dispatch_log_request(request: Request, call_next: Callable) -> Respons return response except Exception as e: - prepare_and_log_request(endpoint, 500, now, fides_source, e.__class__.__name__) + prepare_and_log_request(endpoint, request.url.hostname, 500, now, fides_source, e.__class__.__name__) raise def prepare_and_log_request( endpoint: str, + hostname: str, status_code: int, event_created_at: datetime, fides_source: Optional[str], error_class: Optional[str], ) -> None: + """ + Prepares and sends analytics event provided the user is not opted out of analytics. + """ + # this check prevents AnalyticsEvent from being called with invalid endpoint during unit tests if config.root_user.ANALYTICS_OPT_OUT: return @@ -90,7 +96,7 @@ def prepare_and_log_request( docker=in_docker_container(), event=Event.endpoint_call.value, event_created_at=event_created_at, - local_host=running_on_local_host(), + local_host=accessed_through_local_host(hostname), endpoint=endpoint, status_code=status_code, error=error_class or None, @@ -147,7 +153,6 @@ def start_webserver() -> None: docker=in_docker_container(), event=Event.server_start.value, event_created_at=datetime.now(tz=timezone.utc), - local_host=running_on_local_host(), ) ) diff --git a/tests/api/v1/endpoints/test_connection_config_endpoints.py b/tests/api/v1/endpoints/test_connection_config_endpoints.py index 9c266d759..f63ff0c8b 100644 --- a/tests/api/v1/endpoints/test_connection_config_endpoints.py +++ b/tests/api/v1/endpoints/test_connection_config_endpoints.py @@ -1,4 +1,5 @@ import json +from datetime import datetime from typing import Dict, List from unittest import mock from unittest.mock import Mock @@ -378,6 +379,52 @@ def test_patch_connections_failed_response( "disabled": False, "description": None, } + @mock.patch('fidesops.main.prepare_and_log_request') + def test_patch_connections_incorrect_scope_analytics( + self, mocked_prepare_and_log_request, api_client: TestClient, generate_auth_header, payload + ) -> None: + url = V1_URL_PREFIX + CONNECTIONS + auth_header = generate_auth_header(scopes=[STORAGE_DELETE]) + response = api_client.patch(url, headers=auth_header, json=payload) + assert 403 == response.status_code + assert mocked_prepare_and_log_request.called + call_args = mocked_prepare_and_log_request._mock_call_args[0] + + assert call_args[0] == 'PATCH: http://testserver/api/v1/connection' + assert call_args[1] == 'testserver' + assert call_args[2] == 403 + assert isinstance(call_args[3], datetime) + assert call_args[4] is None + assert call_args[5] == 'HTTPException' + + @mock.patch('fidesops.main.prepare_and_log_request') + def test_patch_http_connection_successful_analytics( + self, mocked_prepare_and_log_request, api_client, db: Session, generate_auth_header, url + ): + auth_header = generate_auth_header(scopes=[CONNECTION_CREATE_OR_UPDATE]) + payload = [ + { + "name": "My Post-Execution Webhook", + "key": "webhook_key", + "connection_type": "https", + "access": "read", + } + ] + response = api_client.patch(url, headers=auth_header, json=payload) + assert 200 == response.status_code + body = json.loads(response.text) + assert body["succeeded"][0]["connection_type"] == "https" + http_config = ConnectionConfig.get_by(db, field="key", value="webhook_key") + http_config.delete(db) + + call_args = mocked_prepare_and_log_request._mock_call_args[0] + + assert call_args[0] == 'PATCH: http://testserver/api/v1/connection' + assert call_args[1] == 'testserver' + assert call_args[2] == 200 + assert isinstance(call_args[3], datetime) + assert call_args[4] is None + assert call_args[5] is None class TestGetConnections: From 8adf0e6ab042d1984f1898a0c6dda1c2bde58f11 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Mon, 20 Jun 2022 16:49:15 -0400 Subject: [PATCH 23/26] format --- src/fidesops/main.py | 4 ++- .../test_connection_config_endpoints.py | 28 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/fidesops/main.py b/src/fidesops/main.py index 759fd3f1e..b4c5bdfcd 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -72,7 +72,9 @@ async def dispatch_log_request(request: Request, call_next: Callable) -> Respons return response except Exception as e: - prepare_and_log_request(endpoint, request.url.hostname, 500, now, fides_source, e.__class__.__name__) + prepare_and_log_request( + endpoint, request.url.hostname, 500, now, fides_source, e.__class__.__name__ + ) raise diff --git a/tests/api/v1/endpoints/test_connection_config_endpoints.py b/tests/api/v1/endpoints/test_connection_config_endpoints.py index f63ff0c8b..952a2f027 100644 --- a/tests/api/v1/endpoints/test_connection_config_endpoints.py +++ b/tests/api/v1/endpoints/test_connection_config_endpoints.py @@ -379,9 +379,14 @@ def test_patch_connections_failed_response( "disabled": False, "description": None, } - @mock.patch('fidesops.main.prepare_and_log_request') + + @mock.patch("fidesops.main.prepare_and_log_request") def test_patch_connections_incorrect_scope_analytics( - self, mocked_prepare_and_log_request, api_client: TestClient, generate_auth_header, payload + self, + mocked_prepare_and_log_request, + api_client: TestClient, + generate_auth_header, + payload, ) -> None: url = V1_URL_PREFIX + CONNECTIONS auth_header = generate_auth_header(scopes=[STORAGE_DELETE]) @@ -390,16 +395,21 @@ def test_patch_connections_incorrect_scope_analytics( assert mocked_prepare_and_log_request.called call_args = mocked_prepare_and_log_request._mock_call_args[0] - assert call_args[0] == 'PATCH: http://testserver/api/v1/connection' - assert call_args[1] == 'testserver' + assert call_args[0] == "PATCH: http://testserver/api/v1/connection" + assert call_args[1] == "testserver" assert call_args[2] == 403 assert isinstance(call_args[3], datetime) assert call_args[4] is None - assert call_args[5] == 'HTTPException' + assert call_args[5] == "HTTPException" - @mock.patch('fidesops.main.prepare_and_log_request') + @mock.patch("fidesops.main.prepare_and_log_request") def test_patch_http_connection_successful_analytics( - self, mocked_prepare_and_log_request, api_client, db: Session, generate_auth_header, url + self, + mocked_prepare_and_log_request, + api_client, + db: Session, + generate_auth_header, + url, ): auth_header = generate_auth_header(scopes=[CONNECTION_CREATE_OR_UPDATE]) payload = [ @@ -419,8 +429,8 @@ def test_patch_http_connection_successful_analytics( call_args = mocked_prepare_and_log_request._mock_call_args[0] - assert call_args[0] == 'PATCH: http://testserver/api/v1/connection' - assert call_args[1] == 'testserver' + assert call_args[0] == "PATCH: http://testserver/api/v1/connection" + assert call_args[1] == "testserver" assert call_args[2] == 200 assert isinstance(call_args[3], datetime) assert call_args[4] is None From a07880a96ba094644b6fcc251d5a2c6084f920f8 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Mon, 20 Jun 2022 18:43:27 -0400 Subject: [PATCH 24/26] isort --- src/fidesops/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fidesops/main.py b/src/fidesops/main.py index b4c5bdfcd..aa178eddc 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -11,8 +11,8 @@ from starlette.middleware.cors import CORSMiddleware from fidesops.analytics import ( - in_docker_container, accessed_through_local_host, + in_docker_container, send_analytics_event, ) from fidesops.api.v1.api import api_router From 209c9716b12d99277354534c1b139b96a4b3b933 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Mon, 20 Jun 2022 18:52:12 -0400 Subject: [PATCH 25/26] unused import --- src/fidesops/analytics.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/fidesops/analytics.py b/src/fidesops/analytics.py index c060aade4..571e3a6ea 100644 --- a/src/fidesops/analytics.py +++ b/src/fidesops/analytics.py @@ -1,7 +1,6 @@ import logging import os from platform import system -from typing import Optional from fideslog.sdk.python.client import AnalyticsClient from fideslog.sdk.python.event import AnalyticsEvent From 8cda76a00d751ee17aed945659ac5cb4c97b17da Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Mon, 20 Jun 2022 19:13:38 -0400 Subject: [PATCH 26/26] hostname is optional type --- src/fidesops/analytics.py | 3 ++- src/fidesops/main.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/fidesops/analytics.py b/src/fidesops/analytics.py index 571e3a6ea..3370b44cf 100644 --- a/src/fidesops/analytics.py +++ b/src/fidesops/analytics.py @@ -1,6 +1,7 @@ import logging import os from platform import system +from typing import Optional from fideslog.sdk.python.client import AnalyticsClient from fideslog.sdk.python.event import AnalyticsEvent @@ -17,7 +18,7 @@ def in_docker_container() -> bool: return bool(os.getenv("RUNNING_IN_DOCKER") == "true") -def accessed_through_local_host(hostname: str) -> bool: +def accessed_through_local_host(hostname: Optional[str]) -> bool: """`True`if the event was submitted through a local host, e,g, 127.0.0.1.""" # testserver is hostname in unit tests LOCAL_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1", "testserver"] diff --git a/src/fidesops/main.py b/src/fidesops/main.py index aa178eddc..1c9108862 100644 --- a/src/fidesops/main.py +++ b/src/fidesops/main.py @@ -80,7 +80,7 @@ async def dispatch_log_request(request: Request, call_next: Callable) -> Respons def prepare_and_log_request( endpoint: str, - hostname: str, + hostname: Optional[str], status_code: int, event_created_at: datetime, fides_source: Optional[str],