Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added network interceptor hook #458

Merged
merged 10 commits into from
Nov 9, 2023
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

## [0.16.8] - 2023-11-7

- Added `network_interceptor` to the `supertokens_config` in `init`.
- This can be used to capture/modify all the HTTP requests sent to the core.
- Added optional parameter `user_context` to session functions `get_session()`, `refresh_session()`, `revoke_all_sessions_for_user()`, `get_all_session_handles_for_user()`, `revoke_session()`, `revoke_multiple_sessions()`, `update_session_data_in_database()` and `get_session_information()`
- Solves the issue - https://github.com/supertokens/supertokens-core/issues/865
- Fixed the dependencies in the example apps
- Example apps will now fetch the latest version of the frameworks

## [0.16.7] - 2023-11-2

- Added `debug` flag in `init()`. If set to `True`, debug logs will be printed.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
Django==4.0.4
django-cors-headers==3.12.0
python-dotenv==0.19.2
supertokens-python
supertokens-python[django]
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
fastapi==0.68.1
uvicorn==0.16.0
python-dotenv==0.19.2
supertokens-python
supertokens-python[fastapi]
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
flask==2.0.1
flask_cors==3.0.10
python-dotenv==0.19.2
supertokens-python
supertokens-python[flask]
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@

setup(
name="supertokens_python",
version="0.16.7",
version="0.16.8",
author="SuperTokens",
license="Apache 2.0",
author_email="[email protected]",
Expand Down
2 changes: 1 addition & 1 deletion supertokens_python/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from __future__ import annotations

SUPPORTED_CDI_VERSIONS = ["3.0"]
VERSION = "0.16.7"
VERSION = "0.16.8"
TELEMETRY = "/telemetry"
USER_COUNT = "/users/count"
USER_DELETE = "/user/remove"
Expand Down
118 changes: 109 additions & 9 deletions supertokens_python/querier.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@
from __future__ import annotations

import asyncio

from json import JSONDecodeError
from os import environ
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Optional
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Optional, Tuple
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved

from httpx import AsyncClient, ConnectTimeout, NetworkError, Response

Expand Down Expand Up @@ -50,6 +49,25 @@ class Querier:
api_version = None
__last_tried_index: int = 0
__hosts_alive_for_testing: Set[str] = set()
network_interceptor: Optional[
Callable[
[
str,
str,
Dict[str, Any],
Optional[Dict[str, Any]],
Optional[Dict[str, Any]],
Optional[Dict[str, Any]],
],
Tuple[
str,
str,
Dict[str, Any],
Optional[Dict[str, Any]],
Optional[Dict[str, Any]],
],
]
] = None

def __init__(self, hosts: List[Host], rid_to_core: Union[None, str] = None):
self.__hosts = hosts
Expand Down Expand Up @@ -141,14 +159,37 @@ def get_instance(rid_to_core: Union[str, None] = None):
return Querier(Querier.__hosts, rid_to_core)

@staticmethod
def init(hosts: List[Host], api_key: Union[str, None] = None):
def init(
hosts: List[Host],
api_key: Union[str, None] = None,
network_interceptor: Optional[
Callable[
[
str,
str,
Dict[str, Any],
Optional[Dict[str, Any]],
Optional[Dict[str, Any]],
Optional[Dict[str, Any]],
],
Tuple[
str,
str,
Dict[str, Any],
Optional[Dict[str, Any]],
Optional[Dict[str, Any]],
],
]
] = None,
):
if not Querier.__init_called:
Querier.__init_called = True
Querier.__hosts = hosts
Querier.__api_key = api_key
Querier.api_version = None
Querier.__last_tried_index = 0
Querier.__hosts_alive_for_testing = set()
Querier.network_interceptor = network_interceptor

async def __get_headers_with_api_version(self, path: NormalisedURLPath):
headers = {API_VERSION_HEADER: await self.get_api_version()}
Expand All @@ -159,17 +200,33 @@ async def __get_headers_with_api_version(self, path: NormalisedURLPath):
return headers

async def send_get_request(
self, path: NormalisedURLPath, params: Union[Dict[str, Any], None] = None
self,
path: NormalisedURLPath,
params: Union[Dict[str, Any], None] = None,
user_context: Union[Dict[str, Any], None] = None,
) -> Dict[str, Any]:
if params is None:
params = {}

async def f(url: str, method: str) -> Response:
headers = await self.__get_headers_with_api_version(path)
nonlocal params
if Querier.network_interceptor is not None:
(
url,
method,
headers,
params,
_,
) = Querier.network_interceptor( # pylint:disable=not-callable
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
url, method, headers, params, {}, user_context
)

return await self.api_request(
url,
method,
2,
headers=await self.__get_headers_with_api_version(path),
headers=headers,
params=params,
)

Expand All @@ -179,6 +236,7 @@ async def send_post_request(
self,
path: NormalisedURLPath,
data: Union[Dict[str, Any], None] = None,
user_context: Union[Dict[str, Any], None] = None,
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
test: bool = False,
) -> Dict[str, Any]:
if data is None:
Expand All @@ -195,35 +253,66 @@ async def send_post_request(
headers["content-type"] = "application/json; charset=utf-8"

async def f(url: str, method: str) -> Response:
nonlocal headers, data
if Querier.network_interceptor is not None:
print(Querier.network_interceptor)
print(url, method, headers, {}, data, user_context)
(
url,
method,
headers,
_,
data,
) = Querier.network_interceptor( # pylint:disable=not-callable
url, method, headers, {}, data, user_context
)
return await self.api_request(
url,
method,
2,
headers=await self.__get_headers_with_api_version(path),
headers=headers,
json=data,
)

return await self.__send_request_helper(path, "POST", f, len(self.__hosts))

async def send_delete_request(
self, path: NormalisedURLPath, params: Union[Dict[str, Any], None] = None
self,
path: NormalisedURLPath,
params: Union[Dict[str, Any], None] = None,
user_context: Union[Dict[str, Any], None] = None,
) -> Dict[str, Any]:
if params is None:
params = {}

async def f(url: str, method: str) -> Response:
headers = await self.__get_headers_with_api_version(path)
nonlocal params
if Querier.network_interceptor is not None:
(
url,
method,
headers,
params,
_,
) = Querier.network_interceptor( # pylint:disable=not-callable
url, method, headers, params, {}, user_context
)
return await self.api_request(
url,
method,
2,
headers=await self.__get_headers_with_api_version(path),
headers=headers,
params=params,
)

return await self.__send_request_helper(path, "DELETE", f, len(self.__hosts))

async def send_put_request(
self, path: NormalisedURLPath, data: Union[Dict[str, Any], None] = None
self,
path: NormalisedURLPath,
data: Union[Dict[str, Any], None] = None,
user_context: Union[Dict[str, Any], None] = None,
) -> Dict[str, Any]:
if data is None:
data = {}
Expand All @@ -232,6 +321,17 @@ async def send_put_request(
headers["content-type"] = "application/json; charset=utf-8"

async def f(url: str, method: str) -> Response:
nonlocal headers, data
if Querier.network_interceptor is not None:
(
url,
method,
headers,
_,
data,
) = Querier.network_interceptor( # pylint:disable=not-callable
url, method, headers, {}, data, user_context
)
return await self.api_request(url, method, 2, headers=headers, json=data)

return await self.__send_request_helper(path, "PUT", f, len(self.__hosts))
Expand Down
3 changes: 2 additions & 1 deletion supertokens_python/recipe/dashboard/api/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ async def handle_analytics_post(

try:
response = await Querier.get_instance().send_get_request(
NormalisedURLPath("/telemetry")
NormalisedURLPath("/telemetry"),
_user_context,
)
if response is not None:
if (
Expand Down
2 changes: 1 addition & 1 deletion supertokens_python/recipe/dashboard/api/search/getTags.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ async def handle_get_tags(
_: APIInterface, _tenant_id: str, __: APIOptions, _user_context: Dict[str, Any]
) -> SearchTagsOK:
response = await Querier.get_instance().send_get_request(
NormalisedURLPath("/user/search/tags")
NormalisedURLPath("/user/search/tags"), _user_context
)
return SearchTagsOK(tags=response["tags"])
1 change: 1 addition & 0 deletions supertokens_python/recipe/dashboard/api/signin.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ async def handle_emailpassword_signin_api(
response = await Querier.get_instance().send_post_request(
NormalisedURLPath("/recipe/dashboard/signin"),
{"email": email, "password": password},
user_context=_user_context,
)

if "status" in response and response["status"] == "OK":
Expand Down
1 change: 1 addition & 0 deletions supertokens_python/recipe/dashboard/api/signout.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ async def handle_emailpassword_signout_api(
await Querier.get_instance().send_delete_request(
NormalisedURLPath("/recipe/dashboard/session"),
{"sessionId": session_id_form_auth_header},
user_context=_user_context,
)
return SignOutOK()
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ async def handle_user_delete(
if user_id is None:
raise_bad_input_exception("Missing required parameter 'userId'")

await Supertokens.get_instance().delete_user(user_id)
await Supertokens.get_instance().delete_user(user_id, _user_context)

return UserDeleteAPIResponse()
4 changes: 3 additions & 1 deletion supertokens_python/recipe/dashboard/api/users_count_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,7 @@ async def handle_users_count_get_api(
_api_options: APIOptions,
_user_context: Dict[str, Any],
) -> UserCountGetAPIResponse:
count = await Supertokens.get_instance().get_user_count(None, tenant_id)
count = await Supertokens.get_instance().get_user_count(
None, tenant_id, _user_context
)
return UserCountGetAPIResponse(count=count)
1 change: 1 addition & 0 deletions supertokens_python/recipe/dashboard/api/users_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ async def handle_users_get_api(
pagination_token=pagination_token,
include_recipe_ids=None,
query=api_options.request.get_query_params(),
user_context=user_context,
)

# user metadata bulk fetch with batches:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ async def should_allow_access(
await Querier.get_instance().send_post_request(
NormalisedURLPath("/recipe/dashboard/session/verify"),
{"sessionId": auth_header_value},
user_context=user_context,
)
)
if session_verification_response.get("status") != "OK":
Expand Down
Loading
Loading