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.
- kinde_fastapi/examples/example_app.py +83 -244
- kinde_fastapi/framework/fastapi_framework.py +19 -11
- kinde_flask/examples/example_app.py +2 -2
- kinde_flask/framework/flask_framework.py +71 -28
- {kinde_python_sdk-2.2.0.dist-info → kinde_python_sdk-2.3.0.dist-info}/METADATA +34 -5
- {kinde_python_sdk-2.2.0.dist-info → kinde_python_sdk-2.3.0.dist-info}/RECORD +56 -76
- {kinde_python_sdk-2.2.0.dist-info → kinde_python_sdk-2.3.0.dist-info}/WHEEL +1 -1
- kinde_sdk/__init__.py +1 -1
- kinde_sdk/auth/login_options.py +4 -0
- kinde_sdk/auth/oauth.py +24 -19
- kinde_sdk/management/__init__.py +528 -280
- kinde_sdk/management/api/__init__.py +1 -4
- kinde_sdk/management/api/api_keys_api.py +1789 -0
- kinde_sdk/management/api/apis_api.py +7 -7
- kinde_sdk/management/api/organizations_api.py +0 -3
- kinde_sdk/management/api/permissions_api.py +0 -287
- kinde_sdk/management/api/properties_api.py +0 -287
- kinde_sdk/management/api/roles_api.py +0 -287
- kinde_sdk/management/api/search_api.py +17 -0
- kinde_sdk/management/api/users_api.py +33 -6
- kinde_sdk/management/api_client.py +15 -8
- kinde_sdk/management/configuration.py +8 -2
- kinde_sdk/management/exceptions.py +6 -3
- kinde_sdk/management/management_client.py +484 -548
- kinde_sdk/management/models/__init__.py +15 -32
- kinde_sdk/management/models/{get_feature_flags_response_data_feature_flags_inner.py → create_api_key_request.py} +38 -18
- kinde_sdk/management/models/create_api_key_response.py +96 -0
- kinde_sdk/management/models/{get_user_roles_response_data_roles_inner.py → create_api_key_response_api_key.py} +7 -9
- kinde_sdk/management/models/create_connection_request.py +2 -2
- kinde_sdk/management/models/create_connection_request_options_one_of2.py +5 -3
- kinde_sdk/management/models/create_role_request.py +10 -2
- kinde_sdk/management/models/{get_entitlement_response_data.py → get_api_key_response.py} +15 -13
- kinde_sdk/management/models/get_api_key_response_api_key.py +152 -0
- kinde_sdk/management/models/{get_user_properties_response_metadata.py → get_api_keys_response.py} +19 -7
- kinde_sdk/management/models/get_api_keys_response_api_keys_inner.py +131 -0
- kinde_sdk/management/models/get_api_response_api.py +1 -1
- kinde_sdk/management/models/get_apis_response_apis_inner.py +1 -1
- kinde_sdk/management/models/get_environment_response_environment.py +1 -1
- kinde_sdk/management/models/get_organization_response.py +4 -2
- kinde_sdk/management/models/organization_user.py +4 -2
- kinde_sdk/management/models/replace_connection_request_options_one_of1.py +5 -3
- kinde_sdk/management/models/{get_entitlement_response.py → rotate_api_key_response.py} +16 -14
- kinde_sdk/management/models/{get_user_permissions_response_data_permissions_inner.py → rotate_api_key_response_api_key.py} +7 -9
- kinde_sdk/management/models/search_users_response_results_inner.py +12 -2
- kinde_sdk/management/models/{get_feature_flags_response.py → search_users_response_results_inner_api_scopes_inner.py} +12 -12
- kinde_sdk/management/models/update_organization_sessions_request.py +2 -2
- kinde_sdk/management/models/update_roles_request.py +10 -2
- kinde_sdk/management/models/user.py +8 -2
- kinde_sdk/management/models/{token_error_response.py → user_billing.py} +8 -10
- kinde_sdk/management/models/users_response_users_inner.py +16 -1
- kinde_sdk/management/models/{get_entitlements_response_data_plans_inner.py → users_response_users_inner_last_organization_sign_ins_inner.py} +9 -11
- kinde_sdk/management/models/{portal_link.py → verify_api_key_request.py} +8 -8
- kinde_sdk/management/models/{user_profile_v2.py → verify_api_key_response.py} +35 -36
- kinde_sdk/management/rest.py +7 -2
- kinde_sdk/management/api/billing_api.py +0 -594
- kinde_sdk/management/api/feature_flags0_api.py +0 -326
- kinde_sdk/management/api/o_auth_api.py +0 -923
- kinde_sdk/management/api/self_serve_portal_api.py +0 -326
- kinde_sdk/management/custom_exceptions.py +0 -32
- kinde_sdk/management/models/get_entitlement_response_data_entitlement.py +0 -122
- kinde_sdk/management/models/get_entitlements_response.py +0 -98
- kinde_sdk/management/models/get_entitlements_response_data.py +0 -108
- kinde_sdk/management/models/get_entitlements_response_data_entitlements_inner.py +0 -122
- kinde_sdk/management/models/get_entitlements_response_metadata.py +0 -90
- kinde_sdk/management/models/get_feature_flags_response_data.py +0 -96
- kinde_sdk/management/models/get_feature_flags_response_data_feature_flags_inner_value.py +0 -178
- kinde_sdk/management/models/get_user_permissions_response.py +0 -98
- kinde_sdk/management/models/get_user_permissions_response_data.py +0 -98
- kinde_sdk/management/models/get_user_permissions_response_metadata.py +0 -90
- kinde_sdk/management/models/get_user_properties_response.py +0 -98
- kinde_sdk/management/models/get_user_properties_response_data.py +0 -96
- kinde_sdk/management/models/get_user_properties_response_data_properties_inner.py +0 -98
- kinde_sdk/management/models/get_user_properties_response_data_properties_inner_value.py +0 -161
- kinde_sdk/management/models/get_user_roles_response.py +0 -98
- kinde_sdk/management/models/get_user_roles_response_data.py +0 -98
- kinde_sdk/management/models/get_user_roles_response_metadata.py +0 -90
- kinde_sdk/management/models/token_introspect.py +0 -96
- kinde_sdk/management/schemas.py +0 -2476
- {kinde_python_sdk-2.2.0.dist-info → kinde_python_sdk-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
2
|
+
Kinde FastAPI Example Application
|
|
3
3
|
|
|
4
|
-
This example demonstrates how to use the
|
|
5
|
-
|
|
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
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
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
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
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
|
|
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
|
|
52
|
-
#
|
|
53
|
-
kinde_oauth =
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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>
|
|
91
|
-
<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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
112
|
+
<h1>Welcome to the Kinde FastAPI Example</h1>
|
|
146
113
|
<p>You are not logged in.</p>
|
|
147
|
-
<p>This example demonstrates
|
|
148
|
-
<a href="/login">Login
|
|
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
|
-
|
|
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("/
|
|
167
|
-
|
|
121
|
+
@app.get("/protected")
|
|
122
|
+
def protected_route():
|
|
168
123
|
"""
|
|
169
|
-
|
|
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
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
return
|
|
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=
|
|
143
|
+
uvicorn.run(app, host="127.0.0.1", port=8000)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
from fastapi import FastAPI, Request
|
|
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(
|
|
123
|
-
if not self._oauth.is_authenticated(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
235
|
+
async def get_user():
|
|
229
236
|
"""Get the current user's information."""
|
|
230
|
-
if not self._oauth.is_authenticated(
|
|
237
|
+
if not self._oauth.is_authenticated():
|
|
231
238
|
return RedirectResponse(url=await self._oauth.login())
|
|
232
|
-
return self._oauth.get_user_info(
|
|
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,
|
|
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
|
|
12
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
239
|
-
|
|
240
|
-
|
|
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(
|
|
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
|