kinde-python-sdk 2.2.0__py3-none-any.whl → 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. kinde_fastapi/examples/example_app.py +83 -244
  2. kinde_fastapi/framework/fastapi_framework.py +19 -11
  3. kinde_flask/examples/example_app.py +2 -2
  4. kinde_flask/framework/flask_framework.py +71 -28
  5. {kinde_python_sdk-2.2.0.dist-info → kinde_python_sdk-2.3.0.dist-info}/METADATA +34 -5
  6. {kinde_python_sdk-2.2.0.dist-info → kinde_python_sdk-2.3.0.dist-info}/RECORD +56 -76
  7. {kinde_python_sdk-2.2.0.dist-info → kinde_python_sdk-2.3.0.dist-info}/WHEEL +1 -1
  8. kinde_sdk/__init__.py +1 -1
  9. kinde_sdk/auth/login_options.py +4 -0
  10. kinde_sdk/auth/oauth.py +24 -19
  11. kinde_sdk/management/__init__.py +528 -280
  12. kinde_sdk/management/api/__init__.py +1 -4
  13. kinde_sdk/management/api/api_keys_api.py +1789 -0
  14. kinde_sdk/management/api/apis_api.py +7 -7
  15. kinde_sdk/management/api/organizations_api.py +0 -3
  16. kinde_sdk/management/api/permissions_api.py +0 -287
  17. kinde_sdk/management/api/properties_api.py +0 -287
  18. kinde_sdk/management/api/roles_api.py +0 -287
  19. kinde_sdk/management/api/search_api.py +17 -0
  20. kinde_sdk/management/api/users_api.py +33 -6
  21. kinde_sdk/management/api_client.py +15 -8
  22. kinde_sdk/management/configuration.py +8 -2
  23. kinde_sdk/management/exceptions.py +6 -3
  24. kinde_sdk/management/management_client.py +484 -548
  25. kinde_sdk/management/models/__init__.py +15 -32
  26. kinde_sdk/management/models/{get_feature_flags_response_data_feature_flags_inner.py → create_api_key_request.py} +38 -18
  27. kinde_sdk/management/models/create_api_key_response.py +96 -0
  28. kinde_sdk/management/models/{get_user_roles_response_data_roles_inner.py → create_api_key_response_api_key.py} +7 -9
  29. kinde_sdk/management/models/create_connection_request.py +2 -2
  30. kinde_sdk/management/models/create_connection_request_options_one_of2.py +5 -3
  31. kinde_sdk/management/models/create_role_request.py +10 -2
  32. kinde_sdk/management/models/{get_entitlement_response_data.py → get_api_key_response.py} +15 -13
  33. kinde_sdk/management/models/get_api_key_response_api_key.py +152 -0
  34. kinde_sdk/management/models/{get_user_properties_response_metadata.py → get_api_keys_response.py} +19 -7
  35. kinde_sdk/management/models/get_api_keys_response_api_keys_inner.py +131 -0
  36. kinde_sdk/management/models/get_api_response_api.py +1 -1
  37. kinde_sdk/management/models/get_apis_response_apis_inner.py +1 -1
  38. kinde_sdk/management/models/get_environment_response_environment.py +1 -1
  39. kinde_sdk/management/models/get_organization_response.py +4 -2
  40. kinde_sdk/management/models/organization_user.py +4 -2
  41. kinde_sdk/management/models/replace_connection_request_options_one_of1.py +5 -3
  42. kinde_sdk/management/models/{get_entitlement_response.py → rotate_api_key_response.py} +16 -14
  43. kinde_sdk/management/models/{get_user_permissions_response_data_permissions_inner.py → rotate_api_key_response_api_key.py} +7 -9
  44. kinde_sdk/management/models/search_users_response_results_inner.py +12 -2
  45. kinde_sdk/management/models/{get_feature_flags_response.py → search_users_response_results_inner_api_scopes_inner.py} +12 -12
  46. kinde_sdk/management/models/update_organization_sessions_request.py +2 -2
  47. kinde_sdk/management/models/update_roles_request.py +10 -2
  48. kinde_sdk/management/models/user.py +8 -2
  49. kinde_sdk/management/models/{token_error_response.py → user_billing.py} +8 -10
  50. kinde_sdk/management/models/users_response_users_inner.py +16 -1
  51. kinde_sdk/management/models/{get_entitlements_response_data_plans_inner.py → users_response_users_inner_last_organization_sign_ins_inner.py} +9 -11
  52. kinde_sdk/management/models/{portal_link.py → verify_api_key_request.py} +8 -8
  53. kinde_sdk/management/models/{user_profile_v2.py → verify_api_key_response.py} +35 -36
  54. kinde_sdk/management/rest.py +7 -2
  55. kinde_sdk/management/api/billing_api.py +0 -594
  56. kinde_sdk/management/api/feature_flags0_api.py +0 -326
  57. kinde_sdk/management/api/o_auth_api.py +0 -923
  58. kinde_sdk/management/api/self_serve_portal_api.py +0 -326
  59. kinde_sdk/management/custom_exceptions.py +0 -32
  60. kinde_sdk/management/models/get_entitlement_response_data_entitlement.py +0 -122
  61. kinde_sdk/management/models/get_entitlements_response.py +0 -98
  62. kinde_sdk/management/models/get_entitlements_response_data.py +0 -108
  63. kinde_sdk/management/models/get_entitlements_response_data_entitlements_inner.py +0 -122
  64. kinde_sdk/management/models/get_entitlements_response_metadata.py +0 -90
  65. kinde_sdk/management/models/get_feature_flags_response_data.py +0 -96
  66. kinde_sdk/management/models/get_feature_flags_response_data_feature_flags_inner_value.py +0 -178
  67. kinde_sdk/management/models/get_user_permissions_response.py +0 -98
  68. kinde_sdk/management/models/get_user_permissions_response_data.py +0 -98
  69. kinde_sdk/management/models/get_user_permissions_response_metadata.py +0 -90
  70. kinde_sdk/management/models/get_user_properties_response.py +0 -98
  71. kinde_sdk/management/models/get_user_properties_response_data.py +0 -96
  72. kinde_sdk/management/models/get_user_properties_response_data_properties_inner.py +0 -98
  73. kinde_sdk/management/models/get_user_properties_response_data_properties_inner_value.py +0 -161
  74. kinde_sdk/management/models/get_user_roles_response.py +0 -98
  75. kinde_sdk/management/models/get_user_roles_response_data.py +0 -98
  76. kinde_sdk/management/models/get_user_roles_response_metadata.py +0 -90
  77. kinde_sdk/management/models/token_introspect.py +0 -96
  78. kinde_sdk/management/schemas.py +0 -2476
  79. {kinde_python_sdk-2.2.0.dist-info → kinde_python_sdk-2.3.0.dist-info}/licenses/LICENSE +0 -0
  80. {kinde_python_sdk-2.2.0.dist-info → kinde_python_sdk-2.3.0.dist-info}/top_level.txt +0 -0
@@ -1,304 +1,143 @@
1
1
  """
2
- SmartOAuth FastAPI Example Application
2
+ Kinde FastAPI Example Application
3
3
 
4
- This example demonstrates how to use the new SmartOAuth client in a FastAPI application.
5
- SmartOAuth automatically detects the execution context (sync vs async) and uses the
6
- appropriate methods, providing a consistent API across different frameworks.
4
+ This example demonstrates how to use the Kinde OAuth client in a FastAPI application.
5
+ The OAuth class automatically registers authentication routes and handles the OAuth flow.
7
6
 
8
7
  Key Features Demonstrated:
9
- - Automatic context detection (async in FastAPI)
10
- - Both sync and async method usage
11
- - Warning system for suboptimal usage
12
- - Integration with auth modules (sync and async)
13
- - Factory function usage
14
- - Real-world authentication flow
8
+ - Simple OAuth setup with FastAPI
9
+ - Automatic route registration (/login, /logout, /callback, /register, /user)
10
+ - Authentication status checking
11
+ - User information retrieval
15
12
 
16
13
  Usage:
17
- 1. Set up your environment variables (see .env.example)
18
- 2. Run: python -m uvicorn kinde_fastapi.examples.example_app:app --reload
19
- 3. Visit http://localhost:5000
14
+ 1. Set up your environment variables in a .env file:
15
+ - KINDE_CLIENT_ID=your_client_id
16
+ - KINDE_CLIENT_SECRET=your_client_secret
17
+ - KINDE_REDIRECT_URI=http://localhost:8000/callback
18
+ - KINDE_HOST=https://your-domain.kinde.com
19
+
20
+ 2. Run from the SDK root directory:
21
+ python -m uvicorn kinde_fastapi.examples.example_app:app --reload --port 8000
22
+
23
+ 3. Visit http://localhost:8000
24
+
25
+ Available Routes (automatically registered):
26
+ - /login - Redirects to Kinde login
27
+ - /logout - Logs out the user
28
+ - /callback - Handles OAuth callback
29
+ - /register - Redirects to Kinde registration
30
+ - /user - Returns user information (redirects to login if not authenticated)
20
31
  """
21
32
 
22
- from fastapi import FastAPI, Request
23
- from fastapi.responses import HTMLResponse
24
- from starlette.middleware.sessions import SessionMiddleware
33
+ from fastapi import FastAPI
34
+ from fastapi.responses import HTMLResponse, RedirectResponse
25
35
  from .session import InMemorySessionMiddleware
26
- from pathlib import Path
27
36
  import os
37
+ import html
28
38
  from dotenv import load_dotenv
29
39
  import logging
30
- from kinde_sdk import SmartOAuth, create_oauth_client
31
- from kinde_sdk.auth import claims, feature_flags, permissions, tokens, async_claims
32
- from kinde_sdk.management import ManagementClient;
33
- from kinde_sdk.management.management_token_manager import ManagementTokenManager
34
- import requests
40
+ from kinde_sdk.auth.oauth import OAuth
35
41
 
36
42
  logger = logging.getLogger(__name__)
37
43
 
38
- # Load environment variables from .env file
39
- load_dotenv()
44
+ # Load environment variables from .env file located alongside this script
45
+ load_dotenv(os.path.join(os.path.dirname(__file__), ".env"))
40
46
 
41
47
  # Initialize FastAPI app
42
48
  app = FastAPI(title="Kinde FastAPI Example")
43
49
 
44
50
  # Add session middleware with proper configuration
51
+ # This is required for storing session data between requests
45
52
  app.add_middleware(
46
53
  InMemorySessionMiddleware,
47
54
  max_age=3600, # 1 hour
48
55
  https_only=False
49
56
  )
50
57
 
51
- # Initialize Kinde SmartOAuth with FastAPI framework
52
- # SmartOAuth automatically detects the async context and uses the appropriate methods
53
- kinde_oauth = SmartOAuth(
58
+ # Initialize Kinde OAuth with FastAPI framework
59
+ # This automatically registers /login, /logout, /callback, /register, /user routes
60
+ kinde_oauth = OAuth(
54
61
  framework="fastapi",
55
62
  app=app
56
63
  )
57
64
 
58
- # Alternative: You can also use the factory function
59
- # kinde_oauth = create_oauth_client(
60
- # async_mode=None, # None means auto-detect (SmartOAuth)
61
- # framework="fastapi",
62
- # app=app
63
- # )
64
-
65
65
  # Example home route
66
66
  @app.get("/", response_class=HTMLResponse)
67
- async def home(request: Request):
67
+ def home():
68
68
  """
69
69
  Home page that shows different content based on authentication status.
70
70
  """
71
71
  if kinde_oauth.is_authenticated():
72
- # In FastAPI (async context), SmartOAuth will use async methods automatically
73
- # You can use either sync or async methods - SmartOAuth will handle the context
74
-
75
- # Option 1: Use async methods (recommended in async context)
76
- user_async = await kinde_oauth.get_user_info_async()
77
-
78
- # Use the async version for better performance
79
- user = user_async
80
-
81
- # Validate environment variables
82
- domain = os.getenv("KINDE_DOMAIN")
83
- client_id = os.getenv("KINDE_MANAGEMENT_CLIENT_ID")
84
- client_secret = os.getenv("KINDE_MANAGEMENT_CLIENT_SECRET")
85
-
86
- if not all([domain, client_id, client_secret]):
87
- return """
72
+ try:
73
+ user = kinde_oauth.get_user_info()
74
+ except Exception as e:
75
+ error_msg = html.escape(str(e))
76
+ logger.exception("Error getting user info")
77
+ return f"""
88
78
  <html>
89
79
  <body>
90
- <h1>Configuration Error</h1>
91
- <p>Missing required environment variables for management client.</p>
80
+ <h1>Error</h1>
81
+ <p>Failed to get user information: {error_msg}</p>
92
82
  <a href="/logout">Logout</a>
93
83
  </body>
94
84
  </html>
95
85
  """
96
-
97
- management_client = ManagementClient(
98
- domain=domain,
99
- client_id=client_id,
100
- client_secret=client_secret
101
- )
102
- try:
103
- api_response = management_client.get_users()
104
- user_count = len(api_response.users) if hasattr(api_response, 'users') else 0
105
- except Exception as e:
106
- logger.error(f"Failed to fetch users: {e}")
107
- user_count = 0
108
-
109
- # Demonstrate both sync and async auth module usage
110
- claims_sync = await claims.get_all_claims()
111
- claims_async = await async_claims.get_all_claims()
112
- feature_flags_data = await feature_flags.get_all_flags()
113
- permissions_data = await permissions.get_permissions()
114
- access_token = tokens.get_token_manager().get_access_token()
115
-
116
- return f"""
117
- <html>
118
- <body>
119
- <h1>Welcome, {user.get('email')}!</h1>
120
- <h2>SmartOAuth Demo - FastAPI (Async Context)</h2>
121
- <p><strong>User Info (async):</strong> {user.get('email')}</p>
122
- <p><strong>Claims (sync):</strong> {claims_sync}</p>
123
- <p><strong>Claims (async):</strong> {claims_async}</p>
124
- <p><strong>Feature Flags:</strong> {feature_flags_data}</p>
125
- <p><strong>Permissions:</strong> {permissions_data}</p>
126
- <p><strong>Access Token:</strong> {access_token[:20]}...</p>
127
- <p><strong>Management Users:</strong> {user_count} user(s) found</p>
128
- <p><em>Note: SmartOAuth automatically detected the async context and used async methods.</em></p>
129
- <p>You are logged in.</p>
130
- <hr>
131
- <h3>Demo Routes:</h3>
132
- <ul>
133
- <li><a href="/demo_smart_oauth">SmartOAuth Demo (JSON)</a></li>
134
- <li><a href="/demo_auth_modules">Auth Modules Demo (JSON)</a></li>
135
- <li><a href="/call_management_users">Call Management Users</a></li>
136
- </ul>
137
- <hr>
138
- <a href="/logout">Logout</a>
139
- </body>
140
- </html>
141
- """
86
+ else:
87
+ email = html.escape(user.get('email', 'N/A'))
88
+ given_name = html.escape(user.get('given_name', ''))
89
+ family_name = html.escape(user.get('family_name', ''))
90
+ user_id = html.escape(user.get('sub', 'N/A'))
91
+ return f"""
92
+ <html>
93
+ <body>
94
+ <h1>Welcome, {email}!</h1>
95
+ <p>You are logged in.</p>
96
+ <h3>User Information:</h3>
97
+ <ul>
98
+ <li><strong>Email:</strong> {email}</li>
99
+ <li><strong>Name:</strong> {given_name} {family_name}</li>
100
+ <li><strong>ID:</strong> {user_id}</li>
101
+ </ul>
102
+ <hr>
103
+ <p><a href="/user">View Full User Info (JSON)</a></p>
104
+ <p><a href="/logout">Logout</a></p>
105
+ </body>
106
+ </html>
107
+ """
108
+
142
109
  return """
143
110
  <html>
144
111
  <body>
145
- <h1>Welcome to the SmartOAuth Example App</h1>
112
+ <h1>Welcome to the Kinde FastAPI Example</h1>
146
113
  <p>You are not logged in.</p>
147
- <p>This example demonstrates SmartOAuth in a FastAPI application.</p>
148
- <a href="/login">Login with SmartOAuth</a>
114
+ <p>This example demonstrates Kinde OAuth integration with FastAPI.</p>
115
+ <p><a href="/login">Login</a> | <a href="/register">Register</a></p>
149
116
  </body>
150
- </html>
117
+ </html>
151
118
  """
152
119
 
153
- @app.get("/login")
154
- async def login():
155
- """
156
- Initiate login with SmartOAuth.
157
- """
158
- try:
159
- # SmartOAuth will automatically use async methods in FastAPI context
160
- login_url = await kinde_oauth.login()
161
- return {"login_url": login_url}
162
- except Exception as e:
163
- logger.error(f"Login error: {e}")
164
- return {"error": str(e)}
165
120
 
166
- @app.get("/logout")
167
- async def logout():
121
+ @app.get("/protected")
122
+ def protected_route():
168
123
  """
169
- Logout using SmartOAuth.
124
+ Example of a protected route that requires authentication.
125
+ Redirects to login if not authenticated.
170
126
  """
171
- try:
172
- # SmartOAuth will automatically use async methods in FastAPI context
173
- logout_url = await kinde_oauth.logout()
174
- return {"logout_url": logout_url}
175
- except Exception as e:
176
- logger.error(f"Logout error: {e}")
177
- return {"error": str(e)}
178
-
179
- @app.get("/call_management_users")
180
- async def call_management_users():
181
127
  if not kinde_oauth.is_authenticated():
182
- return {"error": "Not authenticated"}
183
-
184
- domain = os.getenv("KINDE_DOMAIN")
185
- client_id = os.getenv("KINDE_MANAGEMENT_CLIENT_ID")
186
- client_secret = os.getenv("KINDE_MANAGEMENT_CLIENT_SECRET")
187
-
188
- if not all([domain, client_id, client_secret]):
189
- return {"error": "Missing management credentials"}
128
+ return RedirectResponse("/login")
190
129
 
191
130
  try:
192
- token_manager = ManagementTokenManager(
193
- domain=domain,
194
- client_id=client_id,
195
- client_secret=client_secret
196
- )
197
- access_token = token_manager.get_access_token()
198
-
199
- headers = {
200
- "Authorization": f"Bearer {access_token}"
131
+ user = kinde_oauth.get_user_info()
132
+ return {
133
+ "message": "This is a protected route",
134
+ "user": user.get('email')
201
135
  }
202
- response = requests.get("http://localhost:8000/management/users", headers=headers)
203
- response.raise_for_status()
204
- return response.json()
205
- except Exception as e:
206
- logger.error(f"Failed to call management users: {e}")
207
- return {"error": str(e)}
136
+ except Exception:
137
+ logger.exception("Error getting user info in protected route")
138
+ return RedirectResponse("/login")
208
139
 
209
- @app.get("/demo_smart_oauth")
210
- async def demo_smart_oauth():
211
- """
212
- Demonstrate SmartOAuth features in FastAPI context.
213
- """
214
- if not kinde_oauth.is_authenticated():
215
- return {"error": "Not authenticated"}
216
-
217
- # Demonstrate different SmartOAuth usage patterns
218
- results = {}
219
-
220
- # 1. Async methods (recommended in async context)
221
- try:
222
- user_async = await kinde_oauth.get_user_info_async()
223
- results["user_async"] = user_async.get('email')
224
- except Exception as e:
225
- results["user_async_error"] = str(e)
226
-
227
- # 2. Sync methods (will show warning but still work)
228
- try:
229
- user_sync = kinde_oauth.get_user_info()
230
- results["user_sync"] = user_sync.get('email')
231
- except Exception as e:
232
- results["user_sync_error"] = str(e)
233
-
234
- # 3. Auth URL generation (async)
235
- try:
236
- auth_url = await kinde_oauth.generate_auth_url()
237
- results["auth_url"] = auth_url
238
- except Exception as e:
239
- results["auth_url_error"] = str(e)
240
-
241
- # 4. Token retrieval
242
- try:
243
- tokens_data = kinde_oauth.get_tokens(kinde_oauth._framework.get_user_id())
244
- results["tokens"] = {
245
- "has_access_token": bool(tokens_data.get('access_token')),
246
- "has_refresh_token": bool(tokens_data.get('refresh_token'))
247
- }
248
- except Exception as e:
249
- results["tokens_error"] = str(e)
250
-
251
- # 5. Context detection
252
- results["context_info"] = {
253
- "is_async_context": kinde_oauth._is_async_context(),
254
- "framework": "fastapi",
255
- "authenticated": kinde_oauth.is_authenticated()
256
- }
257
-
258
- return {
259
- "message": "SmartOAuth Demo Results",
260
- "results": results,
261
- "note": "SmartOAuth automatically detected the async context and used appropriate methods."
262
- }
263
-
264
- @app.get("/demo_auth_modules")
265
- async def demo_auth_modules():
266
- """
267
- Demonstrate both sync and async auth modules.
268
- """
269
- if not kinde_oauth.is_authenticated():
270
- return {"error": "Not authenticated"}
271
-
272
- results = {}
273
-
274
- # Sync auth modules
275
- try:
276
- results["claims_sync"] = await claims.get_all_claims()
277
- except Exception as e:
278
- results["claims_sync_error"] = str(e)
279
-
280
- # Async auth modules
281
- try:
282
- results["claims_async"] = await async_claims.get_all_claims()
283
- except Exception as e:
284
- results["claims_async_error"] = str(e)
285
-
286
- try:
287
- results["feature_flags"] = await feature_flags.get_all_flags()
288
- except Exception as e:
289
- results["feature_flags_error"] = str(e)
290
-
291
- try:
292
- results["permissions"] = await permissions.get_permissions()
293
- except Exception as e:
294
- results["permissions_error"] = str(e)
295
-
296
- return {
297
- "message": "Auth Modules Demo",
298
- "results": results,
299
- "note": "Both sync and async auth modules work in FastAPI context."
300
- }
301
140
 
302
141
  if __name__ == "__main__":
303
142
  import uvicorn
304
- uvicorn.run(app, host="127.0.0.1", port=5000)
143
+ uvicorn.run(app, host="127.0.0.1", port=8000)
@@ -1,5 +1,5 @@
1
- from typing import Optional, Dict, Any
2
- from fastapi import FastAPI, Request, Depends
1
+ from typing import Optional
2
+ from fastapi import FastAPI, Request
3
3
  from fastapi.responses import RedirectResponse, HTMLResponse
4
4
  from kinde_sdk.core.framework.framework_interface import FrameworkInterface
5
5
  from kinde_sdk.auth.oauth import OAuth
@@ -55,7 +55,6 @@ class FastAPIFramework(FrameworkInterface):
55
55
  """
56
56
  Start the framework.
57
57
  This method initializes any necessary FastAPI components and registers Kinde routes.
58
- This method initializes any necessary FastAPI components and registers Kinde routes.
59
58
  """
60
59
  if not self._initialized:
61
60
  # Add framework middleware
@@ -119,11 +118,11 @@ class FastAPIFramework(FrameworkInterface):
119
118
  Register all Kinde-specific routes with the FastAPI application.
120
119
  """
121
120
  # Helper function to get current user
122
- async def get_current_user(request: Request):
123
- if not self._oauth.is_authenticated(request):
121
+ async def get_current_user():
122
+ if not self._oauth.is_authenticated():
124
123
  return None
125
124
  try:
126
- return self._oauth.get_user_info(request)
125
+ return self._oauth.get_user_info()
127
126
  except ValueError:
128
127
  return None
129
128
 
@@ -131,7 +130,15 @@ class FastAPIFramework(FrameworkInterface):
131
130
  @self.app.get("/login")
132
131
  async def login(request: Request):
133
132
  """Redirect to Kinde login page."""
134
- url=await self._oauth.login()
133
+ # Build login options from query parameters
134
+ login_options = {}
135
+
136
+ # Check for invitation_code in query parameters
137
+ invitation_code = request.query_params.get('invitation_code')
138
+ if invitation_code:
139
+ login_options['invitation_code'] = invitation_code
140
+
141
+ url = await self._oauth.login(login_options)
135
142
  self._logger.warning(f"[Login] Session is: {request.session}")
136
143
  return RedirectResponse(url=url)
137
144
 
@@ -219,17 +226,17 @@ class FastAPIFramework(FrameworkInterface):
219
226
 
220
227
  # Register route
221
228
  @self.app.get("/register")
222
- async def register(request: Request):
229
+ async def register():
223
230
  """Redirect to Kinde registration page."""
224
231
  return RedirectResponse(url=await self._oauth.register())
225
232
 
226
233
  # User info route
227
234
  @self.app.get("/user")
228
- async def get_user(request: Request):
235
+ async def get_user():
229
236
  """Get the current user's information."""
230
- if not self._oauth.is_authenticated(request):
237
+ if not self._oauth.is_authenticated():
231
238
  return RedirectResponse(url=await self._oauth.login())
232
- return self._oauth.get_user_info(request)
239
+ return self._oauth.get_user_info()
233
240
 
234
241
  def can_auto_detect(self) -> bool:
235
242
  """
@@ -240,6 +247,7 @@ class FastAPIFramework(FrameworkInterface):
240
247
  """
241
248
  try:
242
249
  import fastapi
250
+ _ = fastapi # Import check only
243
251
  return True
244
252
  except ImportError:
245
253
  return False
@@ -4,8 +4,8 @@ from dotenv import load_dotenv
4
4
 
5
5
  from kinde_sdk.auth.oauth import OAuth
6
6
 
7
- # Load environment variables from .env file
8
- load_dotenv()
7
+ # Load environment variables from .env file located alongside this script
8
+ load_dotenv(os.path.join(os.path.dirname(__file__), ".env"))
9
9
 
10
10
  # Initialize Flask app
11
11
  app = Flask(__name__)
@@ -1,19 +1,21 @@
1
- from typing import Optional, Dict, Any, TYPE_CHECKING
1
+ from typing import Optional, TYPE_CHECKING
2
2
  from flask import Flask, request, redirect, session
3
+ from flask_session import Session
3
4
  from kinde_sdk.core.framework.framework_interface import FrameworkInterface
4
5
  from kinde_sdk.auth.oauth import OAuth
5
6
  from ..middleware.framework_middleware import FrameworkMiddleware
6
7
  import os
7
8
  import uuid
8
9
  import asyncio
9
- import threading
10
10
  import logging
11
- import nest_asyncio
12
- from flask_session import Session
11
+ import secrets
12
+ import tempfile
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from flask import Request
16
16
 
17
+ logger = logging.getLogger(__name__)
18
+
17
19
  class FlaskFramework(FrameworkInterface):
18
20
  """
19
21
  Flask framework implementation.
@@ -32,13 +34,35 @@ class FlaskFramework(FrameworkInterface):
32
34
  self._initialized = False
33
35
  self._oauth = None
34
36
 
35
- # Configure Flask session
36
- self.app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'your-secret-key')
37
- self.app.config['SESSION_TYPE'] = 'filesystem'
37
+ # Configure Flask session for server-side storage
38
+ # This is required because OAuth tokens can exceed cookie size limits (~4KB)
39
+ secret_key = os.getenv('SECRET_KEY')
40
+ if not secret_key:
41
+ secret_key = secrets.token_urlsafe(32)
42
+ logger.warning(
43
+ "SECRET_KEY not set. Generated a random key for this session. "
44
+ "Set SECRET_KEY environment variable for production use."
45
+ )
46
+ self.app.config['SECRET_KEY'] = secret_key
47
+ self.app.config['SESSION_TYPE'] = os.getenv('SESSION_TYPE', 'filesystem')
38
48
  self.app.config['SESSION_PERMANENT'] = False
39
49
 
40
- # Enable nested event loops
41
- nest_asyncio.apply()
50
+ session_file_dir = os.getenv('SESSION_FILE_DIR')
51
+ if not session_file_dir:
52
+ # Create a secure temporary directory with restrictive permissions (0700)
53
+ session_file_dir = tempfile.mkdtemp(prefix='kinde_flask_sessions_')
54
+ os.chmod(session_file_dir, 0o700)
55
+ logger.warning(
56
+ f"SESSION_FILE_DIR not set. Using temporary directory: {session_file_dir}. "
57
+ "Set SESSION_FILE_DIR environment variable for production use."
58
+ )
59
+ self.app.config['SESSION_FILE_DIR'] = session_file_dir
60
+
61
+ # Initialize Flask-Session extension
62
+ # Without this, SESSION_TYPE is ignored and Flask uses client-side cookie sessions
63
+ Session(self.app)
64
+ logger.debug("Flask-Session initialized with server-side storage")
65
+
42
66
 
43
67
  def get_name(self) -> str:
44
68
  """
@@ -121,6 +145,27 @@ class FlaskFramework(FrameworkInterface):
121
145
  """
122
146
  self._oauth = oauth
123
147
 
148
+ @staticmethod
149
+ def _run_async(coro):
150
+ """
151
+ Run an async coroutine in a new event loop, then close it.
152
+
153
+ This helper ensures consistent event loop handling across all routes.
154
+ Clears the event loop reference before closing to prevent use-after-close issues.
155
+
156
+ Args:
157
+ coro: The coroutine to run
158
+
159
+ Returns:
160
+ The result of the coroutine
161
+ """
162
+ loop = asyncio.new_event_loop()
163
+ try:
164
+ return loop.run_until_complete(coro)
165
+ finally:
166
+ asyncio.set_event_loop(None)
167
+ loop.close()
168
+
124
169
  def _register_kinde_routes(self) -> None:
125
170
  """
126
171
  Register all Kinde-specific routes with the Flask application.
@@ -129,8 +174,15 @@ class FlaskFramework(FrameworkInterface):
129
174
  @self.app.route('/login')
130
175
  def login():
131
176
  """Redirect to Kinde login page."""
132
- loop = asyncio.get_event_loop()
133
- login_url = loop.run_until_complete(self._oauth.login())
177
+ # Build login options from query parameters
178
+ login_options = {}
179
+
180
+ # Check for invitation_code in query parameters
181
+ invitation_code = request.args.get('invitation_code')
182
+ if invitation_code:
183
+ login_options['invitation_code'] = invitation_code
184
+
185
+ login_url = self._run_async(self._oauth.login(login_options))
134
186
  return redirect(login_url)
135
187
 
136
188
  # Callback route
@@ -198,10 +250,7 @@ class FlaskFramework(FrameworkInterface):
198
250
 
199
251
  # Handle async call to handle_redirect
200
252
  try:
201
- loop = asyncio.new_event_loop()
202
- asyncio.set_event_loop(loop)
203
- loop.run_until_complete(self._oauth.handle_redirect(code, user_id, state))
204
- loop.close()
253
+ self._run_async(self._oauth.handle_redirect(code, user_id, state))
205
254
  except Exception as e:
206
255
  return f"Authentication failed: {str(e)}", 400
207
256
 
@@ -218,16 +267,14 @@ class FlaskFramework(FrameworkInterface):
218
267
  """Logout the user and redirect to Kinde logout page."""
219
268
  user_id = session.get('user_id')
220
269
  session.clear()
221
- loop = asyncio.get_event_loop()
222
- logout_url = loop.run_until_complete(self._oauth.logout(user_id))
270
+ logout_url = self._run_async(self._oauth.logout(user_id))
223
271
  return redirect(logout_url)
224
272
 
225
273
  # Register route
226
274
  @self.app.route('/register')
227
275
  def register():
228
276
  """Redirect to Kinde registration page."""
229
- loop = asyncio.get_event_loop()
230
- register_url = loop.run_until_complete(self._oauth.register())
277
+ register_url = self._run_async(self._oauth.register())
231
278
  return redirect(register_url)
232
279
 
233
280
  # User info route
@@ -235,16 +282,11 @@ class FlaskFramework(FrameworkInterface):
235
282
  def get_user():
236
283
  """Get the current user's information."""
237
284
  try:
238
- if not self._oauth.is_authenticated(request):
239
- loop = asyncio.new_event_loop()
240
- asyncio.set_event_loop(loop)
241
- try:
242
- login_url = loop.run_until_complete(self._oauth.login())
243
- return redirect(login_url)
244
- finally:
245
- loop.close()
285
+ if not self._oauth.is_authenticated():
286
+ login_url = self._run_async(self._oauth.login())
287
+ return redirect(login_url)
246
288
 
247
- return self._oauth.get_user_info(request)
289
+ return self._oauth.get_user_info()
248
290
  except Exception as e:
249
291
  return f"Failed to get user info: {str(e)}", 400
250
292
 
@@ -257,6 +299,7 @@ class FlaskFramework(FrameworkInterface):
257
299
  """
258
300
  try:
259
301
  import flask
302
+ _ = flask # Import check only
260
303
  return True
261
304
  except ImportError:
262
305
  return False