belgie-core 0.1.0__tar.gz
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.
- belgie_core-0.1.0/PKG-INFO +13 -0
- belgie_core-0.1.0/README.md +3 -0
- belgie_core-0.1.0/pyproject.toml +19 -0
- belgie_core-0.1.0/src/belgie_core/__init__.py +65 -0
- belgie_core-0.1.0/src/belgie_core/core/__init__.py +0 -0
- belgie_core-0.1.0/src/belgie_core/core/belgie.py +407 -0
- belgie_core-0.1.0/src/belgie_core/core/client.py +250 -0
- belgie_core-0.1.0/src/belgie_core/core/exceptions.py +26 -0
- belgie_core-0.1.0/src/belgie_core/core/hooks.py +87 -0
- belgie_core-0.1.0/src/belgie_core/core/protocols.py +19 -0
- belgie_core-0.1.0/src/belgie_core/core/settings.py +100 -0
- belgie_core-0.1.0/src/belgie_core/providers/__init__.py +10 -0
- belgie_core-0.1.0/src/belgie_core/providers/google.py +284 -0
- belgie_core-0.1.0/src/belgie_core/providers/protocols.py +43 -0
- belgie_core-0.1.0/src/belgie_core/py.typed +0 -0
- belgie_core-0.1.0/src/belgie_core/session/__init__.py +3 -0
- belgie_core-0.1.0/src/belgie_core/session/manager.py +168 -0
- belgie_core-0.1.0/src/belgie_core/utils/__init__.py +9 -0
- belgie_core-0.1.0/src/belgie_core/utils/crypto.py +10 -0
- belgie_core-0.1.0/src/belgie_core/utils/scopes.py +49 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: belgie-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Core authentication logic for Belgie
|
|
5
|
+
Author: Matt LeMay
|
|
6
|
+
Author-email: Matt LeMay <mplemay@users.noreply.github.com>
|
|
7
|
+
Requires-Dist: belgie-proto
|
|
8
|
+
Requires-Python: >=3.12, <3.15
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# Belgie Core
|
|
12
|
+
|
|
13
|
+
Core authentication logic for Belgie.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "belgie-core"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Core authentication logic for Belgie"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Matt LeMay", email = "mplemay@users.noreply.github.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12,<3.15"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"belgie-proto",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = ["uv_build>=0.9.10,<0.10.0"]
|
|
16
|
+
build-backend = "uv_build"
|
|
17
|
+
|
|
18
|
+
[tool.uv.build-backend]
|
|
19
|
+
source-exclude = ["**/__tests__/**"]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Belgie Core - Authentication components."""
|
|
2
|
+
|
|
3
|
+
from belgie_proto import DBConnection
|
|
4
|
+
|
|
5
|
+
from belgie_core.core.belgie import Belgie
|
|
6
|
+
from belgie_core.core.client import BelgieClient
|
|
7
|
+
from belgie_core.core.exceptions import (
|
|
8
|
+
AuthenticationError,
|
|
9
|
+
AuthorizationError,
|
|
10
|
+
BelgieError,
|
|
11
|
+
ConfigurationError,
|
|
12
|
+
InvalidStateError,
|
|
13
|
+
OAuthError,
|
|
14
|
+
SessionExpiredError,
|
|
15
|
+
)
|
|
16
|
+
from belgie_core.core.hooks import HookContext, HookEvent, HookRunner, Hooks
|
|
17
|
+
from belgie_core.core.settings import (
|
|
18
|
+
BelgieSettings,
|
|
19
|
+
CookieSettings,
|
|
20
|
+
SessionSettings,
|
|
21
|
+
URLSettings,
|
|
22
|
+
)
|
|
23
|
+
from belgie_core.providers.google import GoogleOAuthProvider, GoogleProviderSettings, GoogleUserInfo
|
|
24
|
+
from belgie_core.providers.protocols import OAuthProviderProtocol, Providers
|
|
25
|
+
from belgie_core.session.manager import SessionManager
|
|
26
|
+
from belgie_core.utils.crypto import generate_session_id, generate_state_token
|
|
27
|
+
from belgie_core.utils.scopes import parse_scopes, validate_scopes
|
|
28
|
+
|
|
29
|
+
__all__ = [ # noqa: RUF022
|
|
30
|
+
# Core
|
|
31
|
+
"Belgie",
|
|
32
|
+
"BelgieClient",
|
|
33
|
+
"BelgieSettings",
|
|
34
|
+
"Hooks",
|
|
35
|
+
"HookContext",
|
|
36
|
+
"HookEvent",
|
|
37
|
+
"HookRunner",
|
|
38
|
+
# Adapters
|
|
39
|
+
"DBConnection",
|
|
40
|
+
# Session
|
|
41
|
+
"SessionManager",
|
|
42
|
+
# Providers
|
|
43
|
+
"GoogleOAuthProvider",
|
|
44
|
+
"GoogleProviderSettings",
|
|
45
|
+
"GoogleUserInfo",
|
|
46
|
+
"OAuthProviderProtocol",
|
|
47
|
+
"Providers",
|
|
48
|
+
# Settings
|
|
49
|
+
"SessionSettings",
|
|
50
|
+
"CookieSettings",
|
|
51
|
+
"URLSettings",
|
|
52
|
+
# Exceptions
|
|
53
|
+
"BelgieError",
|
|
54
|
+
"AuthenticationError",
|
|
55
|
+
"AuthorizationError",
|
|
56
|
+
"SessionExpiredError",
|
|
57
|
+
"InvalidStateError",
|
|
58
|
+
"OAuthError",
|
|
59
|
+
"ConfigurationError",
|
|
60
|
+
# Utils
|
|
61
|
+
"generate_session_id",
|
|
62
|
+
"generate_state_token",
|
|
63
|
+
"parse_scopes",
|
|
64
|
+
"validate_scopes",
|
|
65
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncGenerator, Callable # noqa: TC003
|
|
4
|
+
from typing import Protocol, cast
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from belgie_proto import (
|
|
8
|
+
AccountProtocol,
|
|
9
|
+
AdapterProtocol,
|
|
10
|
+
DBConnection,
|
|
11
|
+
OAuthStateProtocol,
|
|
12
|
+
SessionProtocol,
|
|
13
|
+
UserProtocol,
|
|
14
|
+
)
|
|
15
|
+
from fastapi import APIRouter, Depends, Request, status
|
|
16
|
+
from fastapi.responses import RedirectResponse
|
|
17
|
+
from fastapi.security import SecurityScopes # noqa: TC002
|
|
18
|
+
|
|
19
|
+
from belgie_core.core.client import BelgieClient
|
|
20
|
+
from belgie_core.core.hooks import HookRunner, Hooks
|
|
21
|
+
from belgie_core.core.protocols import Plugin
|
|
22
|
+
from belgie_core.core.settings import BelgieSettings # noqa: TC001
|
|
23
|
+
from belgie_core.providers.protocols import OAuthProviderProtocol, Providers # noqa: TC001
|
|
24
|
+
from belgie_core.session.manager import SessionManager
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DBDependencyProvider(Protocol):
|
|
28
|
+
"""Protocol for database dependency providers.
|
|
29
|
+
|
|
30
|
+
This allows Belgie to work with any dependency injection system
|
|
31
|
+
without coupling to a specific implementation.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def dependency(self) -> Callable[[], DBConnection | AsyncGenerator[DBConnection, None]]:
|
|
36
|
+
"""FastAPI dependency that provides database connections."""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _BelgieCallable:
|
|
41
|
+
"""Descriptor that makes Belgie instances callable with instance-specific dependencies.
|
|
42
|
+
|
|
43
|
+
This allows Depends(belgie) to work seamlessly - each Belgie instance gets its own
|
|
44
|
+
callable that has the Belgie instance's database dependency baked into the signature.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __get__(self, obj: Belgie | None, objtype: type | None = None) -> object:
|
|
48
|
+
"""Return instance-specific callable when accessed through an instance."""
|
|
49
|
+
if obj is None:
|
|
50
|
+
# Accessed through class, return descriptor itself
|
|
51
|
+
return self
|
|
52
|
+
|
|
53
|
+
# Return a callable with this instance's db.dependency
|
|
54
|
+
if obj.db is None:
|
|
55
|
+
msg = "Belgie.db must be configured with a dependency"
|
|
56
|
+
raise RuntimeError(msg)
|
|
57
|
+
dependency = obj.db.dependency
|
|
58
|
+
|
|
59
|
+
def __call__( # noqa: N807
|
|
60
|
+
db: DBConnection = Depends(dependency), # noqa: B008
|
|
61
|
+
) -> BelgieClient:
|
|
62
|
+
return BelgieClient(
|
|
63
|
+
db=db,
|
|
64
|
+
adapter=obj.adapter,
|
|
65
|
+
session_manager=obj.session_manager,
|
|
66
|
+
cookie_settings=obj.settings.cookie,
|
|
67
|
+
hook_runner=obj.hook_runner,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return __call__
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Belgie[
|
|
74
|
+
UserT: UserProtocol,
|
|
75
|
+
AccountT: AccountProtocol,
|
|
76
|
+
SessionT: SessionProtocol,
|
|
77
|
+
OAuthStateT: OAuthStateProtocol,
|
|
78
|
+
]:
|
|
79
|
+
"""Main authentication orchestrator for Belgie.
|
|
80
|
+
|
|
81
|
+
The Belgie class provides a complete OAuth 2.0 authentication solution with session management,
|
|
82
|
+
user creation, and FastAPI integration. It automatically loads OAuth providers from environment
|
|
83
|
+
variables and creates router endpoints for authentication.
|
|
84
|
+
|
|
85
|
+
Type Parameters:
|
|
86
|
+
UserT: User model type implementing UserProtocol
|
|
87
|
+
AccountT: Account model type implementing AccountProtocol
|
|
88
|
+
SessionT: Session model type implementing SessionProtocol
|
|
89
|
+
OAuthStateT: OAuth state model type implementing OAuthStateProtocol
|
|
90
|
+
|
|
91
|
+
Attributes:
|
|
92
|
+
settings: Authentication configuration settings
|
|
93
|
+
adapter: Database adapter for persistence operations
|
|
94
|
+
session_manager: Session manager instance for session operations
|
|
95
|
+
providers: Dictionary of registered OAuth providers keyed by provider_id
|
|
96
|
+
router: FastAPI router with authentication endpoints (cached property)
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
>>> from belgie_core import Belgie, BelgieSettings
|
|
100
|
+
>>> from belgie_alchemy import AlchemyAdapter
|
|
101
|
+
>>> from belgie_core.providers.google import GoogleOAuthProvider, GoogleProviderSettings
|
|
102
|
+
>>> from myapp.models import User, Account, Session, OAuthState
|
|
103
|
+
>>>
|
|
104
|
+
>>> settings = BelgieSettings(
|
|
105
|
+
... secret="your-secret-key",
|
|
106
|
+
... base_url="http://localhost:8000",
|
|
107
|
+
... )
|
|
108
|
+
>>>
|
|
109
|
+
>>> adapter = AlchemyAdapter(
|
|
110
|
+
... user=User,
|
|
111
|
+
... account=Account,
|
|
112
|
+
... session=Session,
|
|
113
|
+
... oauth_state=OAuthState,
|
|
114
|
+
... )
|
|
115
|
+
>>> db = DatabaseSettings(dialect={"type": "sqlite", "database": ":memory:"})
|
|
116
|
+
>>>
|
|
117
|
+
>>> # Explicitly pass provider settings
|
|
118
|
+
>>> providers: Providers = {
|
|
119
|
+
... "google": GoogleProviderSettings(
|
|
120
|
+
... client_id="your-client-id",
|
|
121
|
+
... client_secret="your-client-secret",
|
|
122
|
+
... redirect_uri="http://localhost:8000/auth/provider/google/callback",
|
|
123
|
+
... ),
|
|
124
|
+
... }
|
|
125
|
+
>>> belgie = Belgie(settings=settings, adapter=adapter, providers=providers, db=db)
|
|
126
|
+
>>> app.include_router(belgie.router)
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
# Use descriptor to make each instance callable with its own dependency
|
|
130
|
+
__call__: Callable[..., BelgieClient] = cast("Callable[..., BelgieClient]", _BelgieCallable())
|
|
131
|
+
|
|
132
|
+
def __init__(
|
|
133
|
+
self,
|
|
134
|
+
settings: BelgieSettings,
|
|
135
|
+
adapter: AdapterProtocol[UserT, AccountT, SessionT, OAuthStateT],
|
|
136
|
+
db: DBDependencyProvider,
|
|
137
|
+
providers: Providers | None = None,
|
|
138
|
+
hooks: Hooks | None = None,
|
|
139
|
+
) -> None:
|
|
140
|
+
"""Initialize the Belgie instance.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
settings: Authentication configuration including session, cookie, and URL settings
|
|
144
|
+
adapter: Database adapter for user, account, session, and OAuth state persistence
|
|
145
|
+
db: Database dependency provider
|
|
146
|
+
providers: Dictionary of provider settings. Each setting is callable and returns its provider.
|
|
147
|
+
If None, no providers are registered.
|
|
148
|
+
|
|
149
|
+
Raises:
|
|
150
|
+
"""
|
|
151
|
+
self.settings = settings
|
|
152
|
+
self.adapter = adapter
|
|
153
|
+
self.db = db
|
|
154
|
+
|
|
155
|
+
self.session_manager = SessionManager(
|
|
156
|
+
adapter=adapter,
|
|
157
|
+
max_age=settings.session.max_age,
|
|
158
|
+
update_age=settings.session.update_age,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
self.hook_runner = HookRunner(hooks or Hooks())
|
|
162
|
+
|
|
163
|
+
self.plugins: list[Plugin] = []
|
|
164
|
+
|
|
165
|
+
# Instantiate providers by calling the settings
|
|
166
|
+
self.providers: dict[str, OAuthProviderProtocol] = (
|
|
167
|
+
{provider_id: provider_settings() for provider_id, provider_settings in providers.items()} # ty: ignore[call-non-callable]
|
|
168
|
+
if providers
|
|
169
|
+
else {}
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def add_plugin[P: Plugin, **PParams](
|
|
173
|
+
self,
|
|
174
|
+
plugin: Callable[PParams, P],
|
|
175
|
+
*args: PParams.args,
|
|
176
|
+
**kwargs: PParams.kwargs,
|
|
177
|
+
) -> P:
|
|
178
|
+
"""Register and instantiate a plugin.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
plugin: The class of the plugin to register.
|
|
182
|
+
*args: Plugin initialization arguments.
|
|
183
|
+
**kwargs: Plugin initialization keyword arguments.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
The instantiated plugin.
|
|
187
|
+
"""
|
|
188
|
+
instance = plugin(*args, **kwargs)
|
|
189
|
+
|
|
190
|
+
self.plugins.append(instance) # ty: ignore[arg-type]
|
|
191
|
+
|
|
192
|
+
return instance
|
|
193
|
+
|
|
194
|
+
def router(self) -> APIRouter:
|
|
195
|
+
"""FastAPI router with all provider routes.
|
|
196
|
+
|
|
197
|
+
Creates a router with the following structure:
|
|
198
|
+
- /auth/provider/{provider_id}/signin - Provider signin endpoints
|
|
199
|
+
- /auth/provider/{provider_id}/callback - Provider callback endpoints
|
|
200
|
+
- /auth/signout - Global signout endpoint
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
APIRouter with all authentication endpoints
|
|
204
|
+
"""
|
|
205
|
+
main_router = APIRouter(prefix="/auth", tags=["auth"])
|
|
206
|
+
provider_router = APIRouter(prefix="/provider")
|
|
207
|
+
|
|
208
|
+
if self.db is None:
|
|
209
|
+
msg = "Belgie.db must be configured with a dependency"
|
|
210
|
+
raise RuntimeError(msg)
|
|
211
|
+
dependency = self.db.dependency
|
|
212
|
+
|
|
213
|
+
# Include all registered provider routers
|
|
214
|
+
for provider in self.providers.values():
|
|
215
|
+
# Provider's router has prefix /{provider_id}
|
|
216
|
+
# Combined with provider_router prefix: /auth/provider/{provider_id}/...
|
|
217
|
+
provider_specific_router = provider.get_router(
|
|
218
|
+
self.adapter,
|
|
219
|
+
self.settings.cookie,
|
|
220
|
+
session_max_age=self.settings.session.max_age,
|
|
221
|
+
signin_redirect=self.settings.urls.signin_redirect,
|
|
222
|
+
signout_redirect=self.settings.urls.signout_redirect,
|
|
223
|
+
hook_runner=self.hook_runner,
|
|
224
|
+
db_dependency=dependency,
|
|
225
|
+
)
|
|
226
|
+
provider_router.include_router(provider_specific_router)
|
|
227
|
+
|
|
228
|
+
# Include all registered plugin routers
|
|
229
|
+
for plugin in self.plugins:
|
|
230
|
+
main_router.include_router(plugin.router(self))
|
|
231
|
+
|
|
232
|
+
# Add signout endpoint to main router (not provider-specific)
|
|
233
|
+
async def _get_db(db: DBConnection = Depends(dependency)) -> DBConnection: # noqa: B008
|
|
234
|
+
return db
|
|
235
|
+
|
|
236
|
+
@main_router.post("/signout")
|
|
237
|
+
async def signout(
|
|
238
|
+
request: Request,
|
|
239
|
+
db: DBConnection = Depends(_get_db), # noqa: B008, FAST002
|
|
240
|
+
) -> RedirectResponse:
|
|
241
|
+
session_id_str = request.cookies.get(self.settings.cookie.name)
|
|
242
|
+
|
|
243
|
+
if session_id_str:
|
|
244
|
+
try:
|
|
245
|
+
session_id = UUID(session_id_str)
|
|
246
|
+
await self.sign_out(db, session_id)
|
|
247
|
+
except ValueError:
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
response = RedirectResponse(
|
|
251
|
+
url=self.settings.urls.signout_redirect,
|
|
252
|
+
status_code=status.HTTP_302_FOUND,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
response.delete_cookie(
|
|
256
|
+
key=self.settings.cookie.name,
|
|
257
|
+
domain=self.settings.cookie.domain,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
return response
|
|
261
|
+
|
|
262
|
+
# Include provider router in main router
|
|
263
|
+
main_router.include_router(provider_router)
|
|
264
|
+
root_router = APIRouter()
|
|
265
|
+
root_router.include_router(main_router)
|
|
266
|
+
|
|
267
|
+
for plugin in self.plugins:
|
|
268
|
+
root_router.include_router(plugin.public_router(self))
|
|
269
|
+
|
|
270
|
+
return root_router
|
|
271
|
+
|
|
272
|
+
async def get_user_from_session(
|
|
273
|
+
self,
|
|
274
|
+
db: DBConnection,
|
|
275
|
+
session_id: UUID,
|
|
276
|
+
) -> UserT | None:
|
|
277
|
+
"""Retrieve user from a session ID.
|
|
278
|
+
|
|
279
|
+
This method maintains backward compatibility by delegating to BelgieClient internally.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
db: Database connection
|
|
283
|
+
session_id: UUID of the session
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
User object if session is valid and user exists, None otherwise
|
|
287
|
+
|
|
288
|
+
Example:
|
|
289
|
+
>>> user = await belgie.get_user_from_session(db, session_id)
|
|
290
|
+
>>> if user:
|
|
291
|
+
... print(f"Found user: {user.email}")
|
|
292
|
+
"""
|
|
293
|
+
client = self.__call__(db)
|
|
294
|
+
return await client.get_user_from_session(session_id)
|
|
295
|
+
|
|
296
|
+
async def sign_out(
|
|
297
|
+
self,
|
|
298
|
+
db: DBConnection,
|
|
299
|
+
session_id: UUID,
|
|
300
|
+
) -> bool:
|
|
301
|
+
"""Sign out a user by deleting their session.
|
|
302
|
+
|
|
303
|
+
This method maintains backward compatibility by delegating to BelgieClient internally.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
db: Database connection
|
|
307
|
+
session_id: UUID of the session to delete
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
True if session was deleted, False if session didn't exist
|
|
311
|
+
|
|
312
|
+
Example:
|
|
313
|
+
>>> success = await belgie.sign_out(db, session_id)
|
|
314
|
+
>>> if success:
|
|
315
|
+
... print("User signed out successfully")
|
|
316
|
+
"""
|
|
317
|
+
client = self.__call__(db)
|
|
318
|
+
return await client.sign_out(session_id)
|
|
319
|
+
|
|
320
|
+
async def _get_session_from_cookie(
|
|
321
|
+
self,
|
|
322
|
+
request: Request,
|
|
323
|
+
db: DBConnection,
|
|
324
|
+
) -> SessionT | None:
|
|
325
|
+
"""Extract and validate session from request cookies.
|
|
326
|
+
|
|
327
|
+
This method delegates to BelgieClient for consistency.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
request: FastAPI Request object
|
|
331
|
+
db: Database connection
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Session if valid, None otherwise
|
|
335
|
+
"""
|
|
336
|
+
client = self.__call__(db)
|
|
337
|
+
return await client._get_session_from_cookie(request) # noqa: SLF001
|
|
338
|
+
|
|
339
|
+
async def user(
|
|
340
|
+
self,
|
|
341
|
+
security_scopes: SecurityScopes,
|
|
342
|
+
request: Request,
|
|
343
|
+
db: DBConnection,
|
|
344
|
+
) -> UserT:
|
|
345
|
+
"""FastAPI dependency for retrieving the authenticated user.
|
|
346
|
+
|
|
347
|
+
Extracts the session from cookies, validates it, and returns the authenticated user.
|
|
348
|
+
Optionally validates user-level scopes if specified.
|
|
349
|
+
|
|
350
|
+
This method maintains backward compatibility by delegating to BelgieClient internally.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
security_scopes: FastAPI SecurityScopes for scope validation
|
|
354
|
+
request: FastAPI Request object containing cookies
|
|
355
|
+
db: Database connection
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Authenticated user object
|
|
359
|
+
|
|
360
|
+
Raises:
|
|
361
|
+
HTTPException: 401 if not authenticated or session invalid
|
|
362
|
+
HTTPException: 403 if required scopes are not granted
|
|
363
|
+
|
|
364
|
+
Example:
|
|
365
|
+
>>> from fastapi import Depends, Security
|
|
366
|
+
>>>
|
|
367
|
+
>>> @app.get("/protected")
|
|
368
|
+
>>> async def protected_route(user: User = Depends(belgie.user)):
|
|
369
|
+
... return {"email": user.email}
|
|
370
|
+
>>>
|
|
371
|
+
>>> @app.get("/resource")
|
|
372
|
+
>>> async def resource_route(user: User = Security(belgie.user, scopes=[Scope.READ])):
|
|
373
|
+
... return {"data": "..."}
|
|
374
|
+
"""
|
|
375
|
+
client = self.__call__(db)
|
|
376
|
+
return await client.get_user(security_scopes, request)
|
|
377
|
+
|
|
378
|
+
async def session(
|
|
379
|
+
self,
|
|
380
|
+
request: Request,
|
|
381
|
+
db: DBConnection,
|
|
382
|
+
) -> SessionT:
|
|
383
|
+
"""FastAPI dependency for retrieving the current session.
|
|
384
|
+
|
|
385
|
+
Extracts and validates the session from cookies.
|
|
386
|
+
|
|
387
|
+
This method maintains backward compatibility by delegating to BelgieClient internally.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
request: FastAPI Request object containing cookies
|
|
391
|
+
db: Database connection
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
Active session object
|
|
395
|
+
|
|
396
|
+
Raises:
|
|
397
|
+
HTTPException: 401 if not authenticated or session invalid/expired
|
|
398
|
+
|
|
399
|
+
Example:
|
|
400
|
+
>>> from fastapi import Depends
|
|
401
|
+
>>>
|
|
402
|
+
>>> @app.get("/session-info")
|
|
403
|
+
>>> async def session_info(session: Session = Depends(belgie.session)):
|
|
404
|
+
... return {"session_id": str(session.id), "expires_at": session.expires_at.isoformat()}
|
|
405
|
+
"""
|
|
406
|
+
client = self.__call__(db)
|
|
407
|
+
return await client.get_session(request)
|