Skip to content

Commit 2366f1e

Browse files
add patch for is_authenticated
1 parent 36b9125 commit 2366f1e

File tree

2 files changed

+130
-14
lines changed

2 files changed

+130
-14
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Tusk Drift currently supports the following packages and versions:
5555
| psycopg | `>=3.1.12` |
5656
| psycopg2 | all versions |
5757
| Redis | `>=4.0.0` |
58+
| Kinde | `>=2.0.1` |
5859

5960
If you're using packages or versions not listed above, please create an issue with the package + version you'd like an instrumentation for.
6061

drift/instrumentation/kinde/instrumentation.py

Lines changed: 129 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
"""Kinde SDK instrumentation for REPLAY mode.
22
3-
This instrumentation patches StorageManager.get() to handle device ID mismatch
4-
during replay. This enables successful authentication for Flask or FastAPI.
3+
This instrumentation patches Kinde SDK for replay compatibility using a two-tier approach:
54
6-
Problem: During replay, Kinde's StorageManager generates a new UUID on server
7-
startup, but the replayed session contains data keyed with the old device ID
8-
(e.g., `device:OLD-UUID:user_id`). The lookup fails because StorageManager
9-
looks for `device:NEW-UUID:user_id`.
5+
1. PRIMARY: Patch StorageManager.get() to handle device ID mismatch
6+
- During replay, Kinde's StorageManager generates a new UUID on server startup
7+
- But the replayed session contains data keyed with the old device ID
8+
- This patch scans session keys for pattern `device:*:{key}` and extracts the correct device ID
109
11-
Solution: Patch StorageManager.get() to:
12-
1. Try normal lookup with current device_id
13-
2. If that fails, scan session keys for pattern `device:*:{key}`
14-
3. Extract device ID from found key and cache it for future lookups
15-
4. Return the found value
10+
2. FALLBACK: Patch all is_authenticated() methods/functions to return True
11+
- OAuth.is_authenticated()
12+
- UserSession.is_authenticated()
13+
- Tokens.is_authenticated()
14+
- helpers.is_authenticated()
15+
- If StorageManager patch doesn't help (e.g., app stores auth state elsewhere)
16+
- Return True anyway since we're replaying known-good authenticated requests
1617
1718
This approach is framework-agnostic - it works with Flask or FastAPI. Kinde does not support Django.
1819
@@ -101,11 +102,77 @@ def _scan_session_for_key(session: Any, target_key: str) -> tuple[str | None, An
101102
return None, None
102103

103104

105+
def _patch_is_authenticated_method(cls: type, method_name: str, class_name: str) -> bool:
106+
"""Patch an is_authenticated method on a class to return True as fallback.
107+
108+
Args:
109+
cls: The class containing the method
110+
method_name: Name of the method to patch
111+
class_name: Display name for logging
112+
113+
Returns:
114+
True if patching succeeded, False otherwise.
115+
"""
116+
original = getattr(cls, method_name)
117+
118+
def patched(*args: Any, **kwargs: Any) -> bool:
119+
result = original(*args, **kwargs)
120+
if result:
121+
logger.debug(f"[KindeInstrumentation] {class_name}.{method_name}() returned True")
122+
return True
123+
logger.debug(
124+
f"[KindeInstrumentation] {class_name}.{method_name}() returned False, "
125+
"using REPLAY mode fallback (returning True)"
126+
)
127+
return True
128+
129+
setattr(cls, method_name, patched)
130+
logger.debug(f"[KindeInstrumentation] Patched {class_name}.{method_name}()")
131+
return True
132+
133+
134+
def _patch_is_authenticated_function(module: ModuleType, func_name: str, module_name: str) -> bool:
135+
"""Patch a standalone is_authenticated function to return True as fallback.
136+
137+
Args:
138+
module: The module containing the function
139+
func_name: Name of the function to patch
140+
module_name: Display name for logging
141+
142+
Returns:
143+
True if patching succeeded, False otherwise.
144+
"""
145+
original = getattr(module, func_name)
146+
147+
def patched(*args: Any, **kwargs: Any) -> bool:
148+
result = original(*args, **kwargs)
149+
if result:
150+
logger.debug(f"[KindeInstrumentation] {module_name}.{func_name}() returned True")
151+
return True
152+
logger.debug(
153+
f"[KindeInstrumentation] {module_name}.{func_name}() returned False, "
154+
"using REPLAY mode fallback (returning True)"
155+
)
156+
return True
157+
158+
setattr(module, func_name, patched)
159+
logger.debug(f"[KindeInstrumentation] Patched {module_name}.{func_name}()")
160+
return True
161+
162+
104163
class KindeInstrumentation(InstrumentationBase):
105164
"""Instrumentation to patch Kinde SDK for REPLAY mode compatibility.
106165
107-
Patches StorageManager.get() to handle device ID mismatch by scanning
108-
session keys and extracting the correct device ID from recorded data.
166+
Uses a two-tier approach:
167+
1. Patches StorageManager.get() to handle device ID mismatch by scanning
168+
session keys and extracting the correct device ID from recorded data.
169+
2. Patches all is_authenticated() methods/functions as a fallback to return
170+
True if the StorageManager approach doesn't work:
171+
- OAuth.is_authenticated()
172+
- UserSession.is_authenticated()
173+
- Tokens.is_authenticated()
174+
- helpers.is_authenticated()
175+
109176
Works with Flask, FastAPI, and other frameworks using FrameworkAwareStorage.
110177
"""
111178

@@ -124,7 +191,7 @@ def __init__(self, mode: TuskDriftMode = TuskDriftMode.DISABLED, enabled: bool =
124191
super().__init__(
125192
name="KindeInstrumentation",
126193
module_name="kinde_sdk",
127-
supported_versions="*",
194+
supported_versions=">=2.0.1",
128195
enabled=should_enable,
129196
)
130197

@@ -143,8 +210,12 @@ def patch(self, module: ModuleType) -> None:
143210
logger.debug("[KindeInstrumentation] Not in REPLAY mode, skipping patch")
144211
return
145212

213+
# Primary patch: handle device ID mismatch in StorageManager
146214
self._patch_storage_manager_get()
147215

216+
# Fallback patches: if StorageManager patch doesn't help, force is_authenticated to return True
217+
self._patch_all_is_authenticated_methods()
218+
148219
def _patch_storage_manager_get(self) -> None:
149220
"""Patch StorageManager.get() to handle device ID mismatch during replay."""
150221
try:
@@ -208,3 +279,47 @@ def patched_get(self: StorageManager, key: str) -> dict | None:
208279

209280
StorageManager.get = patched_get
210281
logger.debug("[KindeInstrumentation] Patched StorageManager.get()")
282+
283+
def _patch_all_is_authenticated_methods(self) -> None:
284+
"""Patch all is_authenticated methods/functions in Kinde SDK.
285+
286+
This patches:
287+
- OAuth.is_authenticated()
288+
- UserSession.is_authenticated()
289+
- Tokens.is_authenticated()
290+
- helpers.is_authenticated()
291+
292+
Each patch wraps the original to return True as a fallback when the
293+
original returns False, since we're replaying known-good authenticated requests.
294+
"""
295+
# Patch OAuth.is_authenticated
296+
try:
297+
from kinde_sdk.auth.oauth import OAuth
298+
299+
_patch_is_authenticated_method(OAuth, "is_authenticated", "OAuth")
300+
except ImportError:
301+
logger.debug("[KindeInstrumentation] Could not import OAuth, skipping patch")
302+
303+
# Patch UserSession.is_authenticated
304+
try:
305+
from kinde_sdk.auth.user_session import UserSession
306+
307+
_patch_is_authenticated_method(UserSession, "is_authenticated", "UserSession")
308+
except ImportError:
309+
logger.debug("[KindeInstrumentation] Could not import UserSession, skipping patch")
310+
311+
# Patch Tokens.is_authenticated
312+
try:
313+
from kinde_sdk.auth.tokens import Tokens
314+
315+
_patch_is_authenticated_method(Tokens, "is_authenticated", "Tokens")
316+
except ImportError:
317+
logger.debug("[KindeInstrumentation] Could not import Tokens, skipping patch")
318+
319+
# Patch helpers.is_authenticated (standalone function)
320+
try:
321+
from kinde_sdk.core import helpers
322+
323+
_patch_is_authenticated_function(helpers, "is_authenticated", "helpers")
324+
except ImportError:
325+
logger.debug("[KindeInstrumentation] Could not import helpers, skipping patch")

0 commit comments

Comments
 (0)