fastapi-rtk 0.2.27__py3-none-any.whl → 1.0.13__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 (98) hide show
  1. fastapi_rtk/__init__.py +39 -35
  2. fastapi_rtk/_version.py +1 -0
  3. fastapi_rtk/api/model_rest_api.py +476 -221
  4. fastapi_rtk/auth/auth.py +0 -9
  5. fastapi_rtk/backends/generic/__init__.py +6 -0
  6. fastapi_rtk/backends/generic/column.py +21 -12
  7. fastapi_rtk/backends/generic/db.py +42 -7
  8. fastapi_rtk/backends/generic/filters.py +21 -16
  9. fastapi_rtk/backends/generic/interface.py +14 -8
  10. fastapi_rtk/backends/generic/model.py +19 -11
  11. fastapi_rtk/backends/sqla/__init__.py +1 -0
  12. fastapi_rtk/backends/sqla/db.py +77 -17
  13. fastapi_rtk/backends/sqla/extensions/audit/audit.py +401 -189
  14. fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +15 -12
  15. fastapi_rtk/backends/sqla/filters.py +50 -21
  16. fastapi_rtk/backends/sqla/interface.py +96 -34
  17. fastapi_rtk/backends/sqla/model.py +56 -39
  18. fastapi_rtk/bases/__init__.py +20 -0
  19. fastapi_rtk/bases/db.py +94 -7
  20. fastapi_rtk/bases/file_manager.py +47 -3
  21. fastapi_rtk/bases/filter.py +22 -0
  22. fastapi_rtk/bases/interface.py +49 -5
  23. fastapi_rtk/bases/model.py +3 -0
  24. fastapi_rtk/bases/session.py +2 -0
  25. fastapi_rtk/cli/cli.py +62 -9
  26. fastapi_rtk/cli/commands/__init__.py +23 -0
  27. fastapi_rtk/cli/{db.py → commands/db/__init__.py} +107 -50
  28. fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/env.py +2 -3
  29. fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/env.py +10 -9
  30. fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/script.py.mako +3 -1
  31. fastapi_rtk/cli/{export.py → commands/export.py} +12 -10
  32. fastapi_rtk/cli/{security.py → commands/security.py} +73 -7
  33. fastapi_rtk/cli/commands/translate.py +299 -0
  34. fastapi_rtk/cli/decorators.py +9 -4
  35. fastapi_rtk/cli/utils.py +46 -0
  36. fastapi_rtk/config.py +41 -1
  37. fastapi_rtk/const.py +29 -1
  38. fastapi_rtk/db.py +76 -40
  39. fastapi_rtk/decorators.py +1 -1
  40. fastapi_rtk/dependencies.py +134 -62
  41. fastapi_rtk/exceptions.py +51 -1
  42. fastapi_rtk/fastapi_react_toolkit.py +186 -171
  43. fastapi_rtk/file_managers/file_manager.py +8 -6
  44. fastapi_rtk/file_managers/s3_file_manager.py +69 -33
  45. fastapi_rtk/globals.py +22 -12
  46. fastapi_rtk/lang/__init__.py +3 -0
  47. fastapi_rtk/lang/babel/__init__.py +4 -0
  48. fastapi_rtk/lang/babel/cli.py +40 -0
  49. fastapi_rtk/lang/babel/config.py +17 -0
  50. fastapi_rtk/lang/babel.cfg +1 -0
  51. fastapi_rtk/lang/lazy_text.py +120 -0
  52. fastapi_rtk/lang/messages.pot +238 -0
  53. fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  54. fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +248 -0
  55. fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  56. fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +244 -0
  57. fastapi_rtk/manager.py +355 -37
  58. fastapi_rtk/mixins.py +12 -0
  59. fastapi_rtk/routers.py +208 -72
  60. fastapi_rtk/schemas.py +142 -39
  61. fastapi_rtk/security/sqla/apis.py +39 -13
  62. fastapi_rtk/security/sqla/models.py +8 -23
  63. fastapi_rtk/security/sqla/security_manager.py +369 -11
  64. fastapi_rtk/setting.py +446 -88
  65. fastapi_rtk/types.py +94 -27
  66. fastapi_rtk/utils/__init__.py +8 -0
  67. fastapi_rtk/utils/async_task_runner.py +286 -61
  68. fastapi_rtk/utils/csv_json_converter.py +243 -40
  69. fastapi_rtk/utils/hooks.py +34 -0
  70. fastapi_rtk/utils/merge_schema.py +3 -3
  71. fastapi_rtk/utils/multiple_async_contexts.py +21 -0
  72. fastapi_rtk/utils/pydantic.py +46 -1
  73. fastapi_rtk/utils/run_utils.py +31 -1
  74. fastapi_rtk/utils/self_dependencies.py +1 -1
  75. fastapi_rtk/utils/use_default_when_none.py +1 -1
  76. fastapi_rtk/version.py +6 -1
  77. fastapi_rtk-1.0.13.dist-info/METADATA +28 -0
  78. fastapi_rtk-1.0.13.dist-info/RECORD +133 -0
  79. {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/WHEEL +1 -2
  80. fastapi_rtk/backends/gremlinpython/__init__.py +0 -108
  81. fastapi_rtk/backends/gremlinpython/column.py +0 -208
  82. fastapi_rtk/backends/gremlinpython/db.py +0 -228
  83. fastapi_rtk/backends/gremlinpython/exceptions.py +0 -34
  84. fastapi_rtk/backends/gremlinpython/filters.py +0 -461
  85. fastapi_rtk/backends/gremlinpython/interface.py +0 -734
  86. fastapi_rtk/backends/gremlinpython/model.py +0 -364
  87. fastapi_rtk/backends/gremlinpython/session.py +0 -23
  88. fastapi_rtk/cli/commands.py +0 -295
  89. fastapi_rtk-0.2.27.dist-info/METADATA +0 -23
  90. fastapi_rtk-0.2.27.dist-info/RECORD +0 -126
  91. fastapi_rtk-0.2.27.dist-info/top_level.txt +0 -1
  92. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/README +0 -0
  93. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/alembic.ini.mako +0 -0
  94. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/script.py.mako +0 -0
  95. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/README +0 -0
  96. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/alembic.ini.mako +0 -0
  97. {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/entry_points.txt +0 -0
  98. {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/licenses/LICENSE +0 -0
fastapi_rtk/routers.py CHANGED
@@ -1,21 +1,29 @@
1
- from typing import Any, Dict, List, Optional, Tuple, Type
1
+ import typing
2
2
 
3
3
  import jwt
4
+ import pydantic
4
5
  from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
5
6
  from fastapi.responses import HTMLResponse, RedirectResponse
6
7
  from fastapi.security import OAuth2PasswordRequestForm
7
8
  from fastapi_users import models, schemas
8
9
  from fastapi_users.authentication import AuthenticationBackend, Authenticator, Strategy
9
10
  from fastapi_users.exceptions import UserAlreadyExists
10
- from fastapi_users.jwt import SecretType, decode_jwt, generate_jwt
11
- from fastapi_users.manager import BaseUserManager, UserManagerDependency
11
+ from fastapi_users.jwt import SecretType, decode_jwt
12
+ from fastapi_users.manager import UserManagerDependency
12
13
  from fastapi_users.openapi import OpenAPIResponseType
13
14
  from fastapi_users.router.common import ErrorCode, ErrorModel
14
- from fastapi_users.router.oauth import STATE_TOKEN_AUDIENCE
15
- from httpx_oauth.integrations.fastapi import OAuth2AuthorizeCallback
15
+ from fastapi_users.router.oauth import STATE_TOKEN_AUDIENCE, generate_state_token
16
+ from httpx_oauth.integrations.fastapi import (
17
+ OAuth2AuthorizeCallback as BaseOAuth2AuthorizeCallback,
18
+ )
19
+ from httpx_oauth.integrations.fastapi import OAuth2AuthorizeCallbackError
16
20
  from httpx_oauth.oauth2 import BaseOAuth2, OAuth2Token
17
21
 
22
+ from .const import AuthType
23
+ from .const import ErrorCode as _ErrorCode
18
24
  from .globals import g
25
+ from .manager import UserManager
26
+ from .utils import smart_run
19
27
 
20
28
  __all__ = ["get_oauth_router", "get_oauth_associate_router", "get_auth_router"]
21
29
 
@@ -32,49 +40,124 @@ HTML_SUCCESS_RESPONSE = """
32
40
  </html>
33
41
  """
34
42
 
35
- POPUP_DICT = {}
36
43
 
44
+ class OAuth2AuthorizeCallback(BaseOAuth2AuthorizeCallback):
45
+ """
46
+ Modified version of the `OAuth2AuthorizeCallback` from `httpx_oauth.integrations.fastapi`.
47
+ - Adds `state_secret` parameter to support for decoding `state` JWT to get `redirect_url`.
48
+ """
49
+
50
+ state_secret: SecretType | None
51
+
52
+ def __init__(
53
+ self,
54
+ client,
55
+ route_name=None,
56
+ redirect_url=None,
57
+ state_secret: SecretType | None = None,
58
+ ):
59
+ self.state_secret = state_secret
60
+ # Use `redirect_url` from `state`
61
+ if self.state_secret:
62
+ route_name = None
63
+ redirect_url = ""
64
+ super().__init__(client, route_name, redirect_url)
65
+
66
+ async def __call__(
67
+ self,
68
+ request: Request,
69
+ code: typing.Optional[str] = None,
70
+ code_verifier: typing.Optional[str] = None,
71
+ state: typing.Optional[str] = None,
72
+ error: typing.Optional[str] = None,
73
+ ):
74
+ original_redirect_url = self.redirect_url
75
+ if self.state_secret:
76
+ # Try to decode JWT
77
+ try:
78
+ state_data = decode_jwt(
79
+ state, self.state_secret, [STATE_TOKEN_AUDIENCE]
80
+ )
81
+ request.state.state_data = state_data
82
+ self.redirect_url = state_data.get(
83
+ "redirect_url", original_redirect_url
84
+ )
85
+ except jwt.PyJWTError:
86
+ raise OAuth2AuthorizeCallbackError(
87
+ status.HTTP_400_BAD_REQUEST,
88
+ "Invalid state token",
89
+ )
37
90
 
38
- def generate_state_token(
39
- data: Dict[str, str], secret: SecretType, lifetime_seconds: int = 3600
40
- ) -> str:
41
- data["aud"] = STATE_TOKEN_AUDIENCE
42
- return generate_jwt(data, secret, lifetime_seconds)
91
+ try:
92
+ return await super().__call__(request, code, code_verifier, state, error)
93
+ finally:
94
+ self.redirect_url = original_redirect_url
43
95
 
44
96
 
45
97
  def get_oauth_router(
46
98
  oauth_client: BaseOAuth2,
47
- backend: AuthenticationBackend,
99
+ backend: AuthenticationBackend[models.UP, models.ID],
48
100
  get_user_manager: UserManagerDependency[models.UP, models.ID],
49
101
  state_secret: SecretType,
50
- redirect_url: Optional[str] = None,
102
+ redirect_url: typing.Optional[str] = None,
103
+ redirect_url_factory: typing.Optional[
104
+ typing.Callable[[Request, list[str]], str]
105
+ ] = None,
106
+ redirect_url_after_callback: typing.Optional[str] = None,
51
107
  associate_by_email: bool = False,
52
108
  is_verified_by_default: bool = False,
53
- **kwargs: Dict[str, Any],
109
+ **kwargs: dict[str, typing.Any],
54
110
  ) -> APIRouter:
55
111
  """
56
- Modified version of the original function `get_oauth_router` in `fastapi_users.router.oauth` to add:
57
- - `OAUTH_REDIRECT_URI` in config is used to redirect the user to a specific URL after logging in.
58
- - add `**kwargs` to accept additional arguments for the user manager's `oauth_callback` method.
112
+ Modified version of the `get_oauth_router` from `fastapi_users.router.oauth`.
113
+ - Updates the `login` to redirect to `authorization_url` directly instead of returning a JSONResponse with key `authorization_url`.
114
+ - Adds `redirect_url_factory` to allow dynamic generation of the redirect URL.
115
+ - Adds `redirect_url_after_callback` to redirect user to the application after OAuth callback.
116
+ - Adds `**kwargs` to provide additional arguments for the user manager's `oauth_callback` method.
59
117
 
60
118
  Generate a router with the OAuth routes.
61
119
  """
62
120
  router = APIRouter()
63
121
  callback_route_name = f"oauth:{oauth_client.name}.{backend.name}.callback"
64
122
 
65
- oauth2_authorize_callback = OAuth2AuthorizeCallback(
66
- oauth_client,
67
- route_name=callback_route_name,
68
- )
123
+ if redirect_url is not None:
124
+ oauth2_authorize_callback = OAuth2AuthorizeCallback(
125
+ oauth_client,
126
+ redirect_url=redirect_url,
127
+ )
128
+ elif redirect_url_factory is not None:
129
+ oauth2_authorize_callback = OAuth2AuthorizeCallback(
130
+ oauth_client,
131
+ state_secret=state_secret,
132
+ )
133
+ else:
134
+ oauth2_authorize_callback = OAuth2AuthorizeCallback(
135
+ oauth_client,
136
+ route_name=callback_route_name,
137
+ )
69
138
 
70
139
  @router.get(
71
140
  f"/login/{oauth_client.name}",
72
141
  name=f"oauth:{oauth_client.name}.{backend.name}.authorize",
73
142
  )
74
- async def authorize(request: Request, scopes: List[str] = Query(None)):
75
- authorize_redirect_url = str(request.url_for(callback_route_name))
143
+ async def authorize(request: Request, scopes: list[str] = Query(None)):
144
+ if redirect_url is not None:
145
+ authorize_redirect_url = redirect_url
146
+ elif redirect_url_factory is not None:
147
+ authorize_redirect_url = await smart_run(
148
+ redirect_url_factory, request, scopes
149
+ )
150
+ else:
151
+ authorize_redirect_url = str(request.url_for(callback_route_name))
76
152
 
77
- state_data: Dict[str, str] = {}
153
+ state_data = dict[str, str]()
154
+ if redirect_url_factory:
155
+ state_data["redirect_url"] = authorize_redirect_url
156
+ if (
157
+ request.query_params.get("popup") == "1"
158
+ or request.query_params.get("popup") == "true"
159
+ ):
160
+ state_data["popup"] = "true"
78
161
  state = generate_state_token(state_data, state_secret)
79
162
  authorization_url = await oauth_client.get_authorization_url(
80
163
  authorize_redirect_url,
@@ -82,13 +165,6 @@ def get_oauth_router(
82
165
  scopes,
83
166
  )
84
167
 
85
- if (
86
- request.query_params.get("popup") == "1"
87
- or request.query_params.get("popup") == "true"
88
- ):
89
- # Store the popup in POPUP_DICT
90
- POPUP_DICT[state] = True
91
-
92
168
  return RedirectResponse(authorization_url)
93
169
 
94
170
  @router.get(
@@ -117,10 +193,10 @@ def get_oauth_router(
117
193
  )
118
194
  async def callback(
119
195
  request: Request,
120
- access_token_state: Tuple[OAuth2Token, str] = Depends(
196
+ access_token_state: tuple[OAuth2Token, str] = Depends(
121
197
  oauth2_authorize_callback
122
198
  ),
123
- user_manager: BaseUserManager[models.UP, models.ID] = Depends(get_user_manager),
199
+ user_manager: UserManager = Depends(get_user_manager),
124
200
  strategy: Strategy[models.UP, models.ID] = Depends(backend.get_strategy),
125
201
  ):
126
202
  token, state = access_token_state
@@ -134,10 +210,13 @@ def get_oauth_router(
134
210
  detail=ErrorCode.OAUTH_NOT_AVAILABLE_EMAIL,
135
211
  )
136
212
 
137
- try:
138
- decode_jwt(state, state_secret, [STATE_TOKEN_AUDIENCE])
139
- except jwt.DecodeError:
140
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
213
+ # Try to use existing state data from request.state
214
+ state_data = getattr(request.state, "state_data", None)
215
+ if state_data is None:
216
+ try:
217
+ state_data = decode_jwt(state, state_secret, [STATE_TOKEN_AUDIENCE])
218
+ except jwt.DecodeError:
219
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
141
220
 
142
221
  try:
143
222
  user = await user_manager.oauth_callback(
@@ -169,17 +248,17 @@ def get_oauth_router(
169
248
  response = await backend.login(strategy, user)
170
249
  await user_manager.on_after_login(user, request, response)
171
250
 
172
- if state in POPUP_DICT:
173
- # Remove the popup from POPUP_DICT
174
- POPUP_DICT.pop(state)
175
-
251
+ # Check if the request is from a popup
252
+ if state_data.get("popup") == "true":
176
253
  return HTMLResponse(
177
254
  content=HTML_SUCCESS_RESPONSE,
178
255
  headers=response.headers,
179
256
  )
180
257
 
181
- if redirect_url:
182
- return RedirectResponse(redirect_url, headers=response.headers)
258
+ if redirect_url_after_callback:
259
+ return RedirectResponse(
260
+ redirect_url_after_callback, headers=response.headers
261
+ )
183
262
 
184
263
  return response
185
264
 
@@ -188,17 +267,24 @@ def get_oauth_router(
188
267
 
189
268
  def get_oauth_associate_router(
190
269
  oauth_client: BaseOAuth2,
191
- authenticator: Authenticator,
270
+ authenticator: Authenticator[models.UP, models.ID],
192
271
  get_user_manager: UserManagerDependency[models.UP, models.ID],
193
- user_schema: Type[schemas.U],
272
+ user_schema: type[schemas.U],
194
273
  state_secret: SecretType,
195
- redirect_url: Optional[str] = None,
274
+ redirect_url: typing.Optional[str] = None,
275
+ redirect_url_factory: typing.Optional[
276
+ typing.Callable[[Request, list[str]], str]
277
+ ] = None,
278
+ redirect_url_after_callback: typing.Optional[str] = None,
196
279
  requires_verification: bool = False,
197
280
  ) -> APIRouter:
198
281
  """
199
- Generate a router with the OAuth routes to associate an authenticated user.
282
+ Modified version of the `get_oauth_associate_router` from `fastapi_users.router.oauth`.
283
+ - Updates the `login` to redirect to `authorization_url` directly instead of returning a JSONResponse with key `authorization_url`.
284
+ - Adds `redirect_url_factory` to allow dynamic generation of the redirect URL.
285
+ - Adds `redirect_url_after_callback` to redirect user to the application after OAuth callback.
200
286
 
201
- Modified version of the original function in fastapi_users.router.oauth so that it redirects the user directly to the OAuth provider's login page.
287
+ Generate a router with the OAuth routes to associate an authenticated user.
202
288
  """
203
289
  router = APIRouter()
204
290
 
@@ -208,10 +294,21 @@ def get_oauth_associate_router(
208
294
 
209
295
  callback_route_name = f"oauth-associate:{oauth_client.name}.callback"
210
296
 
211
- oauth2_authorize_callback = OAuth2AuthorizeCallback(
212
- oauth_client,
213
- route_name=callback_route_name,
214
- )
297
+ if redirect_url is not None:
298
+ oauth2_authorize_callback = OAuth2AuthorizeCallback(
299
+ oauth_client,
300
+ redirect_url=redirect_url,
301
+ )
302
+ elif redirect_url_factory is not None:
303
+ oauth2_authorize_callback = OAuth2AuthorizeCallback(
304
+ oauth_client,
305
+ state_secret=state_secret,
306
+ )
307
+ else:
308
+ oauth2_authorize_callback = OAuth2AuthorizeCallback(
309
+ oauth_client,
310
+ route_name=callback_route_name,
311
+ )
215
312
 
216
313
  @router.get(
217
314
  f"/login/{oauth_client.name}",
@@ -219,12 +316,21 @@ def get_oauth_associate_router(
219
316
  )
220
317
  async def authorize(
221
318
  request: Request,
222
- scopes: List[str] = Query(None),
319
+ scopes: list[str] = Query(None),
223
320
  user: models.UP = Depends(get_current_active_user),
224
321
  ):
225
- authorize_redirect_url = str(request.url_for(callback_route_name))
322
+ if redirect_url is not None:
323
+ authorize_redirect_url = redirect_url
324
+ elif redirect_url_factory is not None:
325
+ authorize_redirect_url = await smart_run(
326
+ redirect_url_factory, request, scopes
327
+ )
328
+ else:
329
+ authorize_redirect_url = str(request.url_for(callback_route_name))
226
330
 
227
- state_data: Dict[str, str] = {"sub": str(user.id)}
331
+ state_data = {"sub": str(user.id)}
332
+ if redirect_url_factory:
333
+ state_data["redirect_url"] = authorize_redirect_url
228
334
  state = generate_state_token(state_data, state_secret)
229
335
  authorization_url = await oauth_client.get_authorization_url(
230
336
  authorize_redirect_url,
@@ -257,10 +363,10 @@ def get_oauth_associate_router(
257
363
  async def callback(
258
364
  request: Request,
259
365
  user: models.UP = Depends(get_current_active_user),
260
- access_token_state: Tuple[OAuth2Token, str] = Depends(
366
+ access_token_state: tuple[OAuth2Token, str] = Depends(
261
367
  oauth2_authorize_callback
262
368
  ),
263
- user_manager: BaseUserManager[models.UP, models.ID] = Depends(get_user_manager),
369
+ user_manager: UserManager = Depends(get_user_manager),
264
370
  ):
265
371
  token, state = access_token_state
266
372
  account_id, account_email = await oauth_client.get_id_email(
@@ -273,10 +379,13 @@ def get_oauth_associate_router(
273
379
  detail=ErrorCode.OAUTH_NOT_AVAILABLE_EMAIL,
274
380
  )
275
381
 
276
- try:
277
- state_data = decode_jwt(state, state_secret, [STATE_TOKEN_AUDIENCE])
278
- except jwt.DecodeError:
279
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
382
+ # Try to use existing state data from request.state
383
+ state_data = getattr(request.state, "state_data", None)
384
+ if state_data is None:
385
+ try:
386
+ state_data = decode_jwt(state, state_secret, [STATE_TOKEN_AUDIENCE])
387
+ except jwt.DecodeError:
388
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
280
389
 
281
390
  if state_data["sub"] != str(user.id):
282
391
  raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
@@ -292,8 +401,8 @@ def get_oauth_associate_router(
292
401
  request,
293
402
  )
294
403
 
295
- if redirect_url:
296
- return RedirectResponse(redirect_url)
404
+ if redirect_url_after_callback:
405
+ return RedirectResponse(redirect_url_after_callback)
297
406
 
298
407
  return schemas.model_validate(user_schema, user)
299
408
 
@@ -301,14 +410,18 @@ def get_oauth_associate_router(
301
410
 
302
411
 
303
412
  def get_auth_router(
304
- backend: AuthenticationBackend,
413
+ backend: AuthenticationBackend[models.UP, models.ID],
305
414
  get_user_manager: UserManagerDependency[models.UP, models.ID],
306
- authenticator: Authenticator,
415
+ authenticator: Authenticator[models.UP, models.ID],
307
416
  requires_verification: bool = False,
417
+ auth_type: str | None = None,
308
418
  ) -> APIRouter:
309
- """Generate a router with login/logout routes for an authentication backend.
419
+ """
420
+ Modified version of the `get_auth_router` from `fastapi_users.router.auth`.
421
+ - Sets `g.user` to `None` after logout.
422
+ - Adds `auth_type` parameter to set various authentication methods like `ldap`.
310
423
 
311
- Modified version of the original function in fastapi_users.router.auth so that it sets g.user to None after logout.
424
+ Generate a router with login/logout routes for an authentication backend.
312
425
  """
313
426
  router = APIRouter()
314
427
  get_current_user_token = authenticator.current_user_token(
@@ -344,10 +457,13 @@ def get_auth_router(
344
457
  async def login(
345
458
  request: Request,
346
459
  credentials: OAuth2PasswordRequestForm = Depends(),
347
- user_manager: BaseUserManager[models.UP, models.ID] = Depends(get_user_manager),
460
+ user_manager: UserManager = Depends(get_user_manager),
348
461
  strategy: Strategy[models.UP, models.ID] = Depends(backend.get_strategy),
349
462
  ):
350
- user = await user_manager.authenticate(credentials)
463
+ if auth_type == AuthType.LDAP:
464
+ user = await user_manager.authenticate_ldap(credentials)
465
+ else:
466
+ user = await user_manager.authenticate(credentials)
351
467
 
352
468
  if user is None or not user.is_active:
353
469
  raise HTTPException(
@@ -363,10 +479,30 @@ def get_auth_router(
363
479
  await user_manager.on_after_login(user, request, response)
364
480
  return response
365
481
 
482
+ class LoginBody(pydantic.BaseModel):
483
+ username: str
484
+ password: str
485
+
486
+ @router.post(
487
+ "/login-json",
488
+ name=f"auth:{backend.name}.login-json",
489
+ responses=login_responses,
490
+ )
491
+ async def login_with_json(
492
+ request: Request,
493
+ credentials: LoginBody,
494
+ user_manager: UserManager = Depends(get_user_manager),
495
+ strategy: Strategy[models.UP, models.ID] = Depends(backend.get_strategy),
496
+ ):
497
+ credentials = OAuth2PasswordRequestForm(
498
+ username=credentials.username, password=credentials.password
499
+ )
500
+ return await login(request, credentials, user_manager, strategy)
501
+
366
502
  logout_responses: OpenAPIResponseType = {
367
503
  **{
368
504
  status.HTTP_401_UNAUTHORIZED: {
369
- "description": "Missing token or inactive user."
505
+ "description": _ErrorCode.GET_USER_MISSING_TOKEN_OR_INACTIVE_USER
370
506
  }
371
507
  },
372
508
  **backend.transport.get_openapi_logout_responses_success(),
@@ -377,8 +513,8 @@ def get_auth_router(
377
513
  )
378
514
  async def logout(
379
515
  request: Request,
380
- user_token: Tuple[models.UP, str] = Depends(get_current_user_token),
381
- user_manager: BaseUserManager[models.UP, models.ID] = Depends(get_user_manager),
516
+ user_token: tuple[models.UP, str] = Depends(get_current_user_token),
517
+ user_manager: UserManager = Depends(get_user_manager),
382
518
  strategy: Strategy[models.UP, models.ID] = Depends(backend.get_strategy),
383
519
  ):
384
520
  user, token = user_token