fastapi-sso 0.15.0__tar.gz → 0.17.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.
Files changed (28) hide show
  1. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/PKG-INFO +51 -7
  2. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/README.md +48 -6
  3. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/__init__.py +4 -0
  4. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/base.py +70 -1
  5. fastapi_sso-0.17.0/fastapi_sso/sso/bitbucket.py +60 -0
  6. fastapi_sso-0.17.0/fastapi_sso/sso/discord.py +56 -0
  7. fastapi_sso-0.17.0/fastapi_sso/sso/seznam.py +39 -0
  8. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/pyproject.toml +6 -9
  9. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/LICENSE.md +0 -0
  10. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/pkce.py +0 -0
  11. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/py.typed +0 -0
  12. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/__init__.py +0 -0
  13. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/facebook.py +0 -0
  14. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/fitbit.py +0 -0
  15. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/generic.py +0 -0
  16. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/github.py +0 -0
  17. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/gitlab.py +0 -0
  18. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/google.py +0 -0
  19. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/kakao.py +0 -0
  20. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/line.py +0 -0
  21. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/linkedin.py +0 -0
  22. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/microsoft.py +0 -0
  23. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/naver.py +0 -0
  24. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/notion.py +0 -0
  25. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/spotify.py +0 -0
  26. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/twitter.py +0 -0
  27. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/sso/yandex.py +0 -0
  28. {fastapi_sso-0.15.0 → fastapi_sso-0.17.0}/fastapi_sso/state.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi-sso
3
- Version: 0.15.0
3
+ Version: 0.17.0
4
4
  Summary: FastAPI plugin to enable SSO to most common providers (such as Facebook login, Google login and login via Microsoft Office 365 Account)
5
5
  Home-page: https://tomasvotava.github.io/fastapi-sso/
6
6
  License: MIT
@@ -15,10 +15,12 @@ Classifier: Programming Language :: Python :: 3.9
15
15
  Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
18
19
  Requires-Dist: fastapi (>=0.80)
19
20
  Requires-Dist: httpx (>=0.23.0)
20
21
  Requires-Dist: oauthlib (>=3.1.0)
21
22
  Requires-Dist: pydantic[email] (>=1.8.0)
23
+ Requires-Dist: typing-extensions (>=4.12.2,<5.0.0) ; python_version < "3.10"
22
24
  Project-URL: Documentation, https://tomasvotava.github.io/fastapi-sso/
23
25
  Project-URL: Repository, https://github.com/tomasvotava/fastapi-sso
24
26
  Description-Content-Type: text/markdown
@@ -61,14 +63,53 @@ Quick links for the eager ones:
61
63
  - [Demo site](https://fastapi-sso-example.vercel.app/)
62
64
  - [Medium articles](https://medium.com/@christos.karvouniaris247)
63
65
 
64
- ## Security warning
66
+ ## Security Notice
65
67
 
66
- Please note that versions preceding `0.7.0` had a security vulnerability.
67
- The SSO instance could share state between requests, which could lead to security issues.
68
- **Please update to `0.7.0` or newer**.
68
+ ### Version `0.16.0` Update: Race Condition Bug Fix & Context Manager Change
69
69
 
70
- Also, the preferred way of using the SSO instances is to use `with` statement, which will ensure the state is cleared.
71
- See example below.
70
+ A race condition bug in the login flow that could, in rare cases, allow one user
71
+ to assume the identity of another due to concurrent login requests was recently discovered
72
+ by [@parikls](https://github.com/parikls).
73
+ This issue was reported in [#186](https://github.com/tomasvotava/fastapi-sso/issues/186) and has been resolved
74
+ in version `0.16.0`.
75
+
76
+ **Details of the Fix:**
77
+
78
+ The bug was mitigated by introducing an async lock mechanism that ensures only one user can attempt the login
79
+ process at any given time. This prevents race conditions that could lead to unintended user identity crossover.
80
+
81
+ **Important Change:**
82
+
83
+ To fully support this fix, **users must now use the SSO instance within an `async with`
84
+ context manager**. This adjustment is necessary for proper handling of asynchronous operations.
85
+
86
+ The synchronous `with` context manager is now deprecated and will produce a warning.
87
+ It will be removed in future versions to ensure best practices for async handling.
88
+
89
+ **Impact:**
90
+
91
+ This bug could potentially affect deployments with high concurrency or scenarios where multiple users initiate
92
+ login requests simultaneously. To prevent potential issues and deprecation warnings, **update to
93
+ version `0.16.0` or later and modify your code to use the async with context**.
94
+
95
+ Code Example Update:
96
+
97
+ ```python
98
+ # Before (deprecated)
99
+ with sso:
100
+ openid = await sso.verify_and_process(request)
101
+
102
+ # After (recommended)
103
+ async with sso:
104
+ openid = await sso.verify_and_process(request)
105
+ ```
106
+
107
+ Thanks to both [@parikls](https://github.com/parikls) and the community for helping me identify and improve the
108
+ security of `fastapi-sso`. If you encounter any issues or potential vulnerabilities, please report them
109
+ immediately so they can be addressed.
110
+
111
+ For more details, refer to Issue [#186](https://github.com/tomasvotava/fastapi-sso/issues/186)
112
+ and PR [#189](https://github.com/tomasvotava/fastapi-sso/pull/189).
72
113
 
73
114
  ## Support this project
74
115
 
@@ -101,6 +142,9 @@ I tend to process Pull Requests faster when properly caffeinated 😉.
101
142
  - Line (by Jimmy Yeh) - [jimmyyyeh](https://github.com/jimmyyyeh)
102
143
  - LinkedIn (by Alessandro Pischedda) - [Cereal84](https://github.com/Cereal84)
103
144
  - Yandex (by Akim Faskhutdinov) – [akimrx](https://github.com/akimrx)
145
+ - Seznam (by Tomas Koutek) - [TomasKoutek](https://github.com/TomasKoutek)
146
+ - Discord (by Kaelian Baudelet) - [afi-dev](https://github.com/afi-dev)
147
+ - Bitbucket (by Kaelian Baudelet) - [afi-dev](https://github.com/afi-dev)
104
148
 
105
149
  See [Contributing](#contributing) for a guide on how to contribute your own login provider.
106
150
 
@@ -36,14 +36,53 @@ Quick links for the eager ones:
36
36
  - [Demo site](https://fastapi-sso-example.vercel.app/)
37
37
  - [Medium articles](https://medium.com/@christos.karvouniaris247)
38
38
 
39
- ## Security warning
39
+ ## Security Notice
40
40
 
41
- Please note that versions preceding `0.7.0` had a security vulnerability.
42
- The SSO instance could share state between requests, which could lead to security issues.
43
- **Please update to `0.7.0` or newer**.
41
+ ### Version `0.16.0` Update: Race Condition Bug Fix & Context Manager Change
44
42
 
45
- Also, the preferred way of using the SSO instances is to use `with` statement, which will ensure the state is cleared.
46
- See example below.
43
+ A race condition bug in the login flow that could, in rare cases, allow one user
44
+ to assume the identity of another due to concurrent login requests was recently discovered
45
+ by [@parikls](https://github.com/parikls).
46
+ This issue was reported in [#186](https://github.com/tomasvotava/fastapi-sso/issues/186) and has been resolved
47
+ in version `0.16.0`.
48
+
49
+ **Details of the Fix:**
50
+
51
+ The bug was mitigated by introducing an async lock mechanism that ensures only one user can attempt the login
52
+ process at any given time. This prevents race conditions that could lead to unintended user identity crossover.
53
+
54
+ **Important Change:**
55
+
56
+ To fully support this fix, **users must now use the SSO instance within an `async with`
57
+ context manager**. This adjustment is necessary for proper handling of asynchronous operations.
58
+
59
+ The synchronous `with` context manager is now deprecated and will produce a warning.
60
+ It will be removed in future versions to ensure best practices for async handling.
61
+
62
+ **Impact:**
63
+
64
+ This bug could potentially affect deployments with high concurrency or scenarios where multiple users initiate
65
+ login requests simultaneously. To prevent potential issues and deprecation warnings, **update to
66
+ version `0.16.0` or later and modify your code to use the async with context**.
67
+
68
+ Code Example Update:
69
+
70
+ ```python
71
+ # Before (deprecated)
72
+ with sso:
73
+ openid = await sso.verify_and_process(request)
74
+
75
+ # After (recommended)
76
+ async with sso:
77
+ openid = await sso.verify_and_process(request)
78
+ ```
79
+
80
+ Thanks to both [@parikls](https://github.com/parikls) and the community for helping me identify and improve the
81
+ security of `fastapi-sso`. If you encounter any issues or potential vulnerabilities, please report them
82
+ immediately so they can be addressed.
83
+
84
+ For more details, refer to Issue [#186](https://github.com/tomasvotava/fastapi-sso/issues/186)
85
+ and PR [#189](https://github.com/tomasvotava/fastapi-sso/pull/189).
47
86
 
48
87
  ## Support this project
49
88
 
@@ -76,6 +115,9 @@ I tend to process Pull Requests faster when properly caffeinated 😉.
76
115
  - Line (by Jimmy Yeh) - [jimmyyyeh](https://github.com/jimmyyyeh)
77
116
  - LinkedIn (by Alessandro Pischedda) - [Cereal84](https://github.com/Cereal84)
78
117
  - Yandex (by Akim Faskhutdinov) – [akimrx](https://github.com/akimrx)
118
+ - Seznam (by Tomas Koutek) - [TomasKoutek](https://github.com/TomasKoutek)
119
+ - Discord (by Kaelian Baudelet) - [afi-dev](https://github.com/afi-dev)
120
+ - Bitbucket (by Kaelian Baudelet) - [afi-dev](https://github.com/afi-dev)
79
121
 
80
122
  See [Contributing](#contributing) for a guide on how to contribute your own login provider.
81
123
 
@@ -4,6 +4,8 @@
4
4
  """
5
5
 
6
6
  from .sso.base import OpenID, SSOBase, SSOLoginError
7
+ from .sso.bitbucket import BitbucketSSO
8
+ from .sso.discord import DiscordSSO
7
9
  from .sso.facebook import FacebookSSO
8
10
  from .sso.fitbit import FitbitSSO
9
11
  from .sso.generic import create_provider
@@ -37,4 +39,6 @@ __all__ = [
37
39
  "NotionSSO",
38
40
  "SpotifySSO",
39
41
  "TwitterSSO",
42
+ "BitbucketSSO",
43
+ "DiscordSSO",
40
44
  ]
@@ -1,11 +1,13 @@
1
1
  """SSO login base dependency."""
2
2
 
3
+ import asyncio
3
4
  import json
4
5
  import logging
5
6
  import os
7
+ import sys
6
8
  import warnings
7
9
  from types import TracebackType
8
- from typing import Any, ClassVar, Dict, List, Literal, Optional, Type, TypedDict, Union, overload
10
+ from typing import Any, ClassVar, Dict, List, Literal, Optional, Type, TypedDict, TypeVar, Union, overload
9
11
 
10
12
  import httpx
11
13
  import pydantic
@@ -17,8 +19,19 @@ from starlette.responses import RedirectResponse
17
19
  from fastapi_sso.pkce import get_pkce_challenge_pair
18
20
  from fastapi_sso.state import generate_random_state
19
21
 
22
+ if sys.version_info < (3, 10):
23
+ from typing import Callable # pragma: no cover
24
+
25
+ from typing_extensions import ParamSpec # pragma: no cover
26
+ else:
27
+ from collections.abc import Callable
28
+ from typing import ParamSpec
29
+
20
30
  logger = logging.getLogger(__name__)
21
31
 
32
+ T = TypeVar("T")
33
+ P = ParamSpec("P")
34
+
22
35
 
23
36
  class DiscoveryDocument(TypedDict):
24
37
  """Discovery document."""
@@ -55,6 +68,26 @@ class OpenID(pydantic.BaseModel):
55
68
  provider: Optional[str] = None
56
69
 
57
70
 
71
+ class SecurityWarning(UserWarning):
72
+ """Raised when insecure usage is detected"""
73
+
74
+
75
+ def requires_async_context(func: Callable[P, T]) -> Callable[P, T]:
76
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
77
+ if not args or not isinstance(args[0], SSOBase):
78
+ return func(*args, **kwargs)
79
+ if not args[0]._in_stack:
80
+ warnings.warn(
81
+ "Please make sure you are using SSO provider in an async context (using 'async with provider:'). "
82
+ "See https://github.com/tomasvotava/fastapi-sso/issues/186 for more information.",
83
+ category=SecurityWarning,
84
+ stacklevel=1,
85
+ )
86
+ return func(*args, **kwargs)
87
+
88
+ return wrapper
89
+
90
+
58
91
  class SSOBase:
59
92
  """Base class for all SSO providers."""
60
93
 
@@ -83,6 +116,8 @@ class SSOBase:
83
116
  self.client_secret: str = client_secret
84
117
  self.redirect_uri: Optional[Union[pydantic.AnyHttpUrl, str]] = redirect_uri
85
118
  self.allow_insecure_http: bool = allow_insecure_http
119
+ self._login_lock = asyncio.Lock()
120
+ self._in_stack = False
86
121
  self._oauth_client: Optional[WebApplicationClient] = None
87
122
  self._generated_state: Optional[str] = None
88
123
 
@@ -128,6 +163,7 @@ class SSOBase:
128
163
  return self._state
129
164
 
130
165
  @property
166
+ @requires_async_context
131
167
  def oauth_client(self) -> WebApplicationClient:
132
168
  """Retrieves the OAuth Client to aid in generating requests and parsing responses.
133
169
 
@@ -144,6 +180,7 @@ class SSOBase:
144
180
  return self._oauth_client
145
181
 
146
182
  @property
183
+ @requires_async_context
147
184
  def access_token(self) -> Optional[str]:
148
185
  """Retrieves the access token from token endpoint.
149
186
 
@@ -153,6 +190,7 @@ class SSOBase:
153
190
  return self.oauth_client.access_token
154
191
 
155
192
  @property
193
+ @requires_async_context
156
194
  def refresh_token(self) -> Optional[str]:
157
195
  """Retrieves the refresh token if returned from provider.
158
196
 
@@ -162,6 +200,7 @@ class SSOBase:
162
200
  return self._refresh_token or self.oauth_client.refresh_token
163
201
 
164
202
  @property
203
+ @requires_async_context
165
204
  def id_token(self) -> Optional[str]:
166
205
  """Retrieves the id token if returned from provider.
167
206
 
@@ -308,6 +347,7 @@ class SSOBase:
308
347
  convert_response: Literal[False],
309
348
  ) -> Optional[Dict[str, Any]]: ...
310
349
 
350
+ @requires_async_context
311
351
  async def verify_and_process(
312
352
  self,
313
353
  request: Request,
@@ -362,6 +402,12 @@ class SSOBase:
362
402
  )
363
403
 
364
404
  def __enter__(self) -> "SSOBase":
405
+ warnings.warn(
406
+ "SSO Providers are supposed to be used in async context, please change 'with provider' to "
407
+ "'async with provider'. See https://github.com/tomasvotava/fastapi-sso/issues/186 for more information.",
408
+ DeprecationWarning,
409
+ stacklevel=1,
410
+ )
365
411
  self._oauth_client = None
366
412
  self._refresh_token = None
367
413
  self._id_token = None
@@ -372,6 +418,28 @@ class SSOBase:
372
418
  self._pkce_code_verifier, self._pkce_code_challenge = get_pkce_challenge_pair(self._pkce_challenge_length)
373
419
  return self
374
420
 
421
+ async def __aenter__(self) -> "SSOBase":
422
+ await self._login_lock.acquire()
423
+ self._in_stack = True
424
+ self._oauth_client = None
425
+ self._refresh_token = None
426
+ self._id_token = None
427
+ self._state = None
428
+ if self.requires_state:
429
+ self._generated_state = generate_random_state()
430
+ if self.uses_pkce:
431
+ self._pkce_code_verifier, self._pkce_code_challenge = get_pkce_challenge_pair(self._pkce_challenge_length)
432
+ return self
433
+
434
+ async def __aexit__(
435
+ self,
436
+ _exc_type: Optional[Type[BaseException]],
437
+ _exc_val: Optional[BaseException],
438
+ _exc_tb: Optional[TracebackType],
439
+ ) -> None:
440
+ self._in_stack = False
441
+ self._login_lock.release()
442
+
375
443
  def __exit__(
376
444
  self,
377
445
  _exc_type: Optional[Type[BaseException]],
@@ -410,6 +478,7 @@ class SSOBase:
410
478
  convert_response: Literal[False],
411
479
  ) -> Optional[Dict[str, Any]]: ...
412
480
 
481
+ @requires_async_context
413
482
  async def process_login(
414
483
  self,
415
484
  code: str,
@@ -0,0 +1,60 @@
1
+ """BitBucket SSO Oauth Helper class"""
2
+
3
+ from typing import TYPE_CHECKING, ClassVar, List, Optional, Union
4
+
5
+ import pydantic
6
+
7
+ from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase
8
+
9
+ if TYPE_CHECKING:
10
+ import httpx # pragma: no cover
11
+
12
+
13
+ class BitbucketSSO(SSOBase):
14
+ """Class providing login using BitBucket OAuth"""
15
+
16
+ provider = "bitbucket"
17
+ scope: ClassVar = ["account", "email"]
18
+ version = "2.0"
19
+
20
+ def __init__(
21
+ self,
22
+ client_id: str,
23
+ client_secret: str,
24
+ redirect_uri: Optional[Union[pydantic.AnyHttpUrl, str]] = None,
25
+ allow_insecure_http: bool = False,
26
+ scope: Optional[List[str]] = None,
27
+ ):
28
+ super().__init__(
29
+ client_id=client_id,
30
+ client_secret=client_secret,
31
+ redirect_uri=redirect_uri,
32
+ allow_insecure_http=allow_insecure_http,
33
+ scope=scope,
34
+ )
35
+
36
+ async def get_useremail(self, session: Optional["httpx.AsyncClient"] = None) -> dict:
37
+ """Get user email"""
38
+ if session is None:
39
+ raise ValueError("Session is required to make HTTP requests")
40
+
41
+ response = await session.get(f"https://api.bitbucket.org/{self.version}/user/emails")
42
+ return response.json()
43
+
44
+ async def get_discovery_document(self) -> DiscoveryDocument:
45
+ return {
46
+ "authorization_endpoint": "https://bitbucket.org/site/oauth2/authorize",
47
+ "token_endpoint": "https://bitbucket.org/site/oauth2/access_token",
48
+ "userinfo_endpoint": f"https://api.bitbucket.org/{self.version}/user",
49
+ }
50
+
51
+ async def openid_from_response(self, response: dict, session: Optional["httpx.AsyncClient"] = None) -> OpenID:
52
+ email = await self.get_useremail(session=session)
53
+ return OpenID(
54
+ email=email["values"][0]["email"],
55
+ display_name=response.get("display_name"),
56
+ provider=self.provider,
57
+ id=str(response.get("uuid")).strip("{}"),
58
+ first_name=response.get("nickname"),
59
+ picture=response.get("links", {}).get("avatar", {}).get("href"),
60
+ )
@@ -0,0 +1,56 @@
1
+ """Discord SSO Oauth Helper class"""
2
+
3
+ from typing import TYPE_CHECKING, ClassVar, List, Optional, Union
4
+
5
+ import pydantic
6
+
7
+ from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase
8
+
9
+ if TYPE_CHECKING:
10
+ import httpx # pragma: no cover
11
+
12
+
13
+ class DiscordSSO(SSOBase):
14
+ """Class providing login using Discord OAuth"""
15
+
16
+ provider = "discord"
17
+ scope: ClassVar = ["identify", "email", "openid"]
18
+
19
+ def __init__(
20
+ self,
21
+ client_id: str,
22
+ client_secret: str,
23
+ redirect_uri: Optional[Union[pydantic.AnyHttpUrl, str]] = None,
24
+ allow_insecure_http: bool = False,
25
+ scope: Optional[List[str]] = None,
26
+ ):
27
+ super().__init__(
28
+ client_id=client_id,
29
+ client_secret=client_secret,
30
+ redirect_uri=redirect_uri,
31
+ allow_insecure_http=allow_insecure_http,
32
+ scope=scope,
33
+ )
34
+
35
+ async def get_discovery_document(self) -> DiscoveryDocument:
36
+ return {
37
+ "authorization_endpoint": "https://discord.com/oauth2/authorize",
38
+ "token_endpoint": "https://discord.com/api/oauth2/token",
39
+ "userinfo_endpoint": "https://discord.com/api/users/@me",
40
+ }
41
+
42
+ async def openid_from_response(self, response: dict, session: Optional["httpx.AsyncClient"] = None) -> OpenID:
43
+ user_id = response.get("id")
44
+ avatar = response.get("avatar")
45
+ picture = None
46
+ if user_id and avatar:
47
+ picture = f"https://cdn.discordapp.com/avatars/{user_id}/{avatar}.png"
48
+
49
+ return OpenID(
50
+ email=response.get("email"),
51
+ display_name=response.get("global_name"),
52
+ provider=self.provider,
53
+ id=user_id,
54
+ first_name=response.get("username"),
55
+ picture=picture,
56
+ )
@@ -0,0 +1,39 @@
1
+ """Seznam SSO Login Helper."""
2
+
3
+ from typing import TYPE_CHECKING, ClassVar, Optional
4
+
5
+ from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase
6
+
7
+ if TYPE_CHECKING:
8
+ import httpx # pragma: no cover
9
+
10
+
11
+ # https://vyvojari.seznam.cz/oauth/doc
12
+
13
+
14
+ class SeznamSSO(SSOBase):
15
+ """Class providing login via Seznam OAuth."""
16
+
17
+ provider = "seznam"
18
+ base_url = "https://login.szn.cz/api/v1"
19
+ scope: ClassVar = ["identity", "avatar"] # + ["contact-phone", "adulthood", "birthday", "gender"]
20
+
21
+ async def get_discovery_document(self) -> DiscoveryDocument:
22
+ """Get document containing handy urls."""
23
+ return {
24
+ "authorization_endpoint": f"{self.base_url}/oauth/auth",
25
+ "token_endpoint": f"{self.base_url}/oauth/token",
26
+ "userinfo_endpoint": f"{self.base_url}/user",
27
+ }
28
+
29
+ async def openid_from_response(self, response: dict, session: Optional["httpx.AsyncClient"] = None) -> OpenID:
30
+ """Return OpenID from user information provided by Seznam."""
31
+ return OpenID(
32
+ email=response.get("email"),
33
+ first_name=response.get("firstname"),
34
+ last_name=response.get("lastname"),
35
+ display_name=response.get("accountDisplayName"),
36
+ provider=self.provider,
37
+ id=response.get("oauth_user_id"),
38
+ picture=response.get("avatar_url"),
39
+ )
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "fastapi-sso"
3
- version = "0.15.0"
3
+ version = "0.17.0"
4
4
  description = "FastAPI plugin to enable SSO to most common providers (such as Facebook login, Google login and login via Microsoft Office 365 Account)"
5
5
  authors = ["Tomas Votava <info@tomasvotava.eu>"]
6
6
  readme = "README.md"
@@ -29,8 +29,6 @@ addopts = [
29
29
  "--cov-report=xml:coverage.xml",
30
30
  "--cov-report=json:coverage.json",
31
31
  "--cov-report=term-missing",
32
- "-n",
33
- "auto",
34
32
  ]
35
33
 
36
34
 
@@ -97,17 +95,15 @@ black = ">=23.7.0"
97
95
  isort = "^5"
98
96
  markdown-include = "^0.8.1"
99
97
  mkdocs-material = { extras = ["imaging"], version = "^9.3.2" }
100
- mkdocstrings = { extras = ["python"], version = ">=0.23,<0.26" }
98
+ mkdocstrings = { extras = ["python"], version = ">=0.23,<0.27" }
101
99
  mypy = "^1"
102
- poethepoet = ">=0.21.1,<0.27.0"
100
+ poethepoet = ">=0.21.1,<0.30.0"
103
101
  pre-commit = "^3"
104
102
  pytest = ">=7,<9"
105
- pytest-asyncio = ">=0.21.1,<0.24.0"
103
+ pytest-asyncio = "^0.24"
106
104
  pytest-cov = ">=4,<6"
107
- pytest-xdist = "^3"
108
- tox = "^4"
109
105
  uvicorn = ">=0.23.1"
110
- ruff = "^0.4.2"
106
+ ruff = ">=0.4.2,<0.8.0"
111
107
 
112
108
  [tool.poetry.dependencies]
113
109
  fastapi = ">=0.80"
@@ -115,6 +111,7 @@ httpx = ">=0.23.0"
115
111
  oauthlib = ">=3.1.0"
116
112
  pydantic = { extras = ["email"], version = ">=1.8.0" }
117
113
  python = ">=3.8,<4.0"
114
+ typing-extensions = { version = "^4.12.2", python = "<3.10" }
118
115
 
119
116
  [build-system]
120
117
  requires = ["poetry-core>=1.0.0"]
File without changes