Skip to content
19 changes: 10 additions & 9 deletions kinde_fastapi/framework/fastapi_framework.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Optional, Dict, Any
from fastapi import FastAPI, Request, Depends
from typing import Optional
from fastapi import FastAPI, Request
from fastapi.responses import RedirectResponse, HTMLResponse
from kinde_sdk.core.framework.framework_interface import FrameworkInterface
from kinde_sdk.auth.oauth import OAuth
Expand Down Expand Up @@ -119,11 +119,11 @@ def _register_kinde_routes(self) -> None:
Register all Kinde-specific routes with the FastAPI application.
"""
# Helper function to get current user
async def get_current_user(request: Request):
if not self._oauth.is_authenticated(request):
async def get_current_user():
if not self._oauth.is_authenticated():
return None
try:
return self._oauth.get_user_info(request)
return self._oauth.get_user_info()
except ValueError:
return None

Expand Down Expand Up @@ -221,17 +221,17 @@ async def logout(request: Request):

# Register route
@self.app.get("/register")
async def register(request: Request):
async def register():
"""Redirect to Kinde registration page."""
return RedirectResponse(url=await self._oauth.register())

# User info route
@self.app.get("/user")
async def get_user(request: Request):
async def get_user():
"""Get the current user's information."""
if not self._oauth.is_authenticated(request):
if not self._oauth.is_authenticated():
return RedirectResponse(url=await self._oauth.login())
return self._oauth.get_user_info(request)
return self._oauth.get_user_info()

def can_auto_detect(self) -> bool:
"""
Expand All @@ -242,6 +242,7 @@ def can_auto_detect(self) -> bool:
"""
try:
import fastapi
_ = fastapi # Import check only
return True
except ImportError:
return False
10 changes: 4 additions & 6 deletions kinde_flask/framework/flask_framework.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
from typing import Optional, Dict, Any, TYPE_CHECKING
from typing import Optional, TYPE_CHECKING
from flask import Flask, request, redirect, session
from kinde_sdk.core.framework.framework_interface import FrameworkInterface
from kinde_sdk.auth.oauth import OAuth
from ..middleware.framework_middleware import FrameworkMiddleware
import os
import uuid
import asyncio
import threading
import logging
import nest_asyncio
from flask_session import Session

if TYPE_CHECKING:
from flask import Request
Expand Down Expand Up @@ -237,7 +234,7 @@ def register():
def get_user():
"""Get the current user's information."""
try:
if not self._oauth.is_authenticated(request):
if not self._oauth.is_authenticated():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
Expand All @@ -246,7 +243,7 @@ def get_user():
finally:
loop.close()

return self._oauth.get_user_info(request)
return self._oauth.get_user_info()
except Exception as e:
return f"Failed to get user info: {str(e)}", 400

Expand All @@ -259,6 +256,7 @@ def can_auto_detect(self) -> bool:
"""
try:
import flask
_ = flask # Import check only
return True
except ImportError:
return False
15 changes: 4 additions & 11 deletions kinde_sdk/auth/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@
import requests
import logging
import time
import asyncio
from typing import Any, Dict, List, Optional, Tuple, Union
from urllib.parse import urlencode, urlparse, quote
from typing import Any, Dict, Optional
from urllib.parse import urlencode

from .user_session import UserSession
from kinde_sdk.core.storage.storage_manager import StorageManager
from kinde_sdk.core.storage.storage_factory import StorageFactory
from kinde_sdk.core.framework.framework_factory import FrameworkFactory
from .config_loader import load_config
from .enums import GrantType, IssuerRouteTypes, PromptTypes
from .enums import IssuerRouteTypes, PromptTypes
from .login_options import LoginOptions
from kinde_sdk.core.helpers import generate_random_string, base64_url_encode, generate_pkce_pair, get_user_details as helper_get_user_details, get_user_details_sync
from kinde_sdk.core.helpers import generate_random_string, generate_pkce_pair, get_user_details as helper_get_user_details, get_user_details_sync
from kinde_sdk.core.exceptions import (
KindeConfigurationException,
KindeLoginException,
Expand Down Expand Up @@ -126,9 +125,6 @@ def is_authenticated(self) -> bool:
"""
Check if the user is authenticated using the session manager.

Args:
request (Optional[Any]): The current request object

Returns:
bool: True if the user is authenticated, False otherwise
"""
Expand All @@ -148,9 +144,6 @@ def get_user_info(self) -> Dict[str, Any]:
"""
Get the user information from the session.

Args:
request (Optional[Any]): The current request object

Returns:
Dict[str, Any]: The user information

Expand Down
165 changes: 164 additions & 1 deletion testv2/testv2_auth/test_oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import pytest
import time
import os
from unittest.mock import patch, MagicMock, AsyncMock
import inspect
from unittest.mock import patch, MagicMock, AsyncMock, Mock
from urllib.parse import urlparse, parse_qs
import requests

Expand Down Expand Up @@ -130,5 +131,167 @@ def mock_get_side_effect(key):
self.oauth._framework = self.mock_framework


class TestOAuthMethodSignatures(unittest.TestCase):
"""Test OAuth method signatures to verify the fix for incorrect request parameter passing."""

@patch.dict('os.environ', {
'KINDE_CLIENT_ID': 'test_client_id',
'KINDE_CLIENT_SECRET': 'test_client_secret',
'KINDE_REDIRECT_URI': 'http://localhost:8000/callback',
'KINDE_HOST': 'https://test.kinde.com'
})
@patch('kinde_sdk.auth.oauth.requests.get')
@patch('kinde_sdk.auth.oauth.StorageFactory')
@patch('kinde_sdk.auth.oauth.FrameworkFactory')
def test_is_authenticated_signature_has_no_parameters(self, mock_framework_factory, mock_storage_factory, mock_get):
"""
Test that is_authenticated() accepts no parameters beyond self.

This verifies the fix for the bug where FastAPI/Flask were passing
a request parameter that is_authenticated() doesn't accept.
"""
# Mock OpenID configuration to prevent real network calls
mock_response = Mock()
mock_response.status_code = 200
mock_response.json = Mock(return_value={
'authorization_endpoint': 'https://test.kinde.com/oauth2/auth',
'token_endpoint': 'https://test.kinde.com/oauth2/token',
'end_session_endpoint': 'https://test.kinde.com/logout',
'userinfo_endpoint': 'https://test.kinde.com/oauth2/userinfo'
})
mock_get.return_value = mock_response

# Configure factory mocks to prevent blocking during OAuth initialization
mock_storage_factory.create_storage.return_value = MagicMock()
mock_framework_factory.create_framework.return_value = None

oauth = OAuth(framework=None)
sig = inspect.signature(oauth.is_authenticated)
params = list(sig.parameters.keys())

# Should have no parameters (self is implicit)
self.assertEqual(len(params), 0,
f"is_authenticated() should accept no parameters, found: {params}")

@patch.dict('os.environ', {
'KINDE_CLIENT_ID': 'test_client_id',
'KINDE_CLIENT_SECRET': 'test_client_secret',
'KINDE_REDIRECT_URI': 'http://localhost:8000/callback',
'KINDE_HOST': 'https://test.kinde.com'
})
@patch('kinde_sdk.auth.oauth.requests.get')
@patch('kinde_sdk.auth.oauth.StorageFactory')
@patch('kinde_sdk.auth.oauth.FrameworkFactory')
def test_get_user_info_signature_has_no_parameters(self, mock_framework_factory, mock_storage_factory, mock_get):
"""
Test that get_user_info() accepts no parameters beyond self.

This verifies the fix for the bug where FastAPI/Flask were passing
a request parameter that get_user_info() doesn't accept.
"""
# Mock OpenID configuration to prevent real network calls
mock_response = Mock()
mock_response.status_code = 200
mock_response.json = Mock(return_value={
'authorization_endpoint': 'https://test.kinde.com/oauth2/auth',
'token_endpoint': 'https://test.kinde.com/oauth2/token',
'end_session_endpoint': 'https://test.kinde.com/logout',
'userinfo_endpoint': 'https://test.kinde.com/oauth2/userinfo'
})
mock_get.return_value = mock_response

# Configure factory mocks to prevent blocking during OAuth initialization
mock_storage_factory.create_storage.return_value = MagicMock()
mock_framework_factory.create_framework.return_value = None

oauth = OAuth(framework=None)
sig = inspect.signature(oauth.get_user_info)
params = list(sig.parameters.keys())

# Should have no parameters (self is implicit)
self.assertEqual(len(params), 0,
f"get_user_info() should accept no parameters, found: {params}")

@patch.dict('os.environ', {
'KINDE_CLIENT_ID': 'test_client_id',
'KINDE_CLIENT_SECRET': 'test_client_secret',
'KINDE_REDIRECT_URI': 'http://localhost:8000/callback',
'KINDE_HOST': 'https://test.kinde.com'
})
@patch('kinde_sdk.auth.oauth.StorageFactory')
@patch('kinde_sdk.auth.oauth.FrameworkFactory')
@patch('kinde_sdk.auth.oauth.requests.get')
def test_is_authenticated_rejects_extra_parameters(self, mock_get, mock_framework_factory, mock_storage_factory):
"""
Test that passing extra parameters to is_authenticated() raises TypeError.

This ensures if someone accidentally adds request parameter back,
the test will catch it and prevent regression.
"""
# Mock OpenID configuration
mock_response = Mock()
mock_response.status_code = 200
mock_response.json = Mock(return_value={
'authorization_endpoint': 'https://test.kinde.com/oauth2/auth',
'token_endpoint': 'https://test.kinde.com/oauth2/token',
'end_session_endpoint': 'https://test.kinde.com/logout',
'userinfo_endpoint': 'https://test.kinde.com/oauth2/userinfo'
})
mock_get.return_value = mock_response

# Configure factory mocks to prevent blocking during OAuth initialization
mock_storage_factory.create_storage.return_value = MagicMock()
mock_framework_factory.create_framework.return_value = None

oauth = OAuth(framework=None)

# Passing a parameter should raise TypeError
with self.assertRaises(TypeError) as context:
oauth.is_authenticated("unexpected_parameter")

# Verify error message mentions positional arguments
self.assertIn("positional argument", str(context.exception).lower())

@patch.dict('os.environ', {
'KINDE_CLIENT_ID': 'test_client_id',
'KINDE_CLIENT_SECRET': 'test_client_secret',
'KINDE_REDIRECT_URI': 'http://localhost:8000/callback',
'KINDE_HOST': 'https://test.kinde.com'
})
@patch('kinde_sdk.auth.oauth.StorageFactory')
@patch('kinde_sdk.auth.oauth.FrameworkFactory')
@patch('kinde_sdk.auth.oauth.requests.get')
def test_get_user_info_rejects_extra_parameters(self, mock_get, mock_framework_factory, mock_storage_factory):
"""
Test that passing extra parameters to get_user_info() raises TypeError.

This ensures if someone accidentally adds request parameter back,
the test will catch it and prevent regression.
"""
# Mock OpenID configuration
mock_response = Mock()
mock_response.status_code = 200
mock_response.json = Mock(return_value={
'authorization_endpoint': 'https://test.kinde.com/oauth2/auth',
'token_endpoint': 'https://test.kinde.com/oauth2/token',
'end_session_endpoint': 'https://test.kinde.com/logout',
'userinfo_endpoint': 'https://test.kinde.com/oauth2/userinfo'
})
mock_get.return_value = mock_response

# Configure factory mocks to prevent blocking during OAuth initialization
mock_storage_factory.create_storage.return_value = MagicMock()
mock_framework_factory.create_framework.return_value = None

oauth = OAuth(framework=None)

# Passing a parameter should raise TypeError
with self.assertRaises(TypeError) as context:
oauth.get_user_info("unexpected_parameter")

# Verify error message mentions positional arguments
self.assertIn("positional argument", str(context.exception).lower())


if __name__ == "__main__":
unittest.main()