fastapi-sso 0.17.0__tar.gz → 0.18.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.
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/PKG-INFO +5 -5
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/pkce.py +1 -2
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/base.py +55 -33
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/bitbucket.py +2 -2
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/discord.py +2 -2
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/generic.py +4 -4
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/gitlab.py +3 -3
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/linkedin.py +6 -2
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/microsoft.py +2 -2
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/naver.py +2 -2
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/pyproject.toml +6 -5
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/LICENSE.md +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/README.md +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/__init__.py +6 -6
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/py.typed +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/__init__.py +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/facebook.py +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/fitbit.py +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/github.py +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/google.py +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/kakao.py +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/line.py +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/notion.py +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/seznam.py +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/spotify.py +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/twitter.py +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/sso/yandex.py +0 -0
- {fastapi_sso-0.17.0 → fastapi_sso-0.18.0}/fastapi_sso/state.py +0 -0
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: fastapi-sso
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.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
|
-
Home-page: https://tomasvotava.github.io/fastapi-sso/
|
|
6
5
|
License: MIT
|
|
7
6
|
Keywords: fastapi,sso,oauth,google,facebook,spotify,linkedin
|
|
8
7
|
Author: Tomas Votava
|
|
9
8
|
Author-email: info@tomasvotava.eu
|
|
10
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.9,<4.0
|
|
11
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
11
|
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
14
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
15
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -20,8 +18,10 @@ Requires-Dist: fastapi (>=0.80)
|
|
|
20
18
|
Requires-Dist: httpx (>=0.23.0)
|
|
21
19
|
Requires-Dist: oauthlib (>=3.1.0)
|
|
22
20
|
Requires-Dist: pydantic[email] (>=1.8.0)
|
|
21
|
+
Requires-Dist: pyjwt (>=2.10.1,<3.0.0)
|
|
23
22
|
Requires-Dist: typing-extensions (>=4.12.2,<5.0.0) ; python_version < "3.10"
|
|
24
23
|
Project-URL: Documentation, https://tomasvotava.github.io/fastapi-sso/
|
|
24
|
+
Project-URL: Homepage, https://tomasvotava.github.io/fastapi-sso/
|
|
25
25
|
Project-URL: Repository, https://github.com/tomasvotava/fastapi-sso
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import base64
|
|
4
4
|
import hashlib
|
|
5
5
|
import os
|
|
6
|
-
from typing import Tuple
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
def get_code_verifier(length: int = 96) -> str:
|
|
@@ -13,7 +12,7 @@ def get_code_verifier(length: int = 96) -> str:
|
|
|
13
12
|
return base64.urlsafe_b64encode(os.urandom(bytes_length)).decode("utf-8").replace("=", "")[:length]
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
def get_pkce_challenge_pair(verifier_length: int = 96) ->
|
|
15
|
+
def get_pkce_challenge_pair(verifier_length: int = 96) -> tuple[str, str]:
|
|
17
16
|
"""Get tuple of (verifier, challenge) for PKCE challenge."""
|
|
18
17
|
code_verifier = get_code_verifier(verifier_length)
|
|
19
18
|
code_challenge = (
|
|
@@ -7,9 +7,10 @@ import os
|
|
|
7
7
|
import sys
|
|
8
8
|
import warnings
|
|
9
9
|
from types import TracebackType
|
|
10
|
-
from typing import Any, ClassVar,
|
|
10
|
+
from typing import Any, ClassVar, Literal, Optional, TypedDict, TypeVar, Union, overload
|
|
11
11
|
|
|
12
12
|
import httpx
|
|
13
|
+
import jwt
|
|
13
14
|
import pydantic
|
|
14
15
|
from oauthlib.oauth2 import WebApplicationClient
|
|
15
16
|
from starlette.exceptions import HTTPException
|
|
@@ -33,6 +34,10 @@ T = TypeVar("T")
|
|
|
33
34
|
P = ParamSpec("P")
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
def _decode_id_token(id_token: str, verify: bool = False) -> dict:
|
|
38
|
+
return jwt.decode(id_token, options={"verify_signature": verify})
|
|
39
|
+
|
|
40
|
+
|
|
36
41
|
class DiscoveryDocument(TypedDict):
|
|
37
42
|
"""Discovery document."""
|
|
38
43
|
|
|
@@ -95,10 +100,11 @@ class SSOBase:
|
|
|
95
100
|
client_id: str = NotImplemented
|
|
96
101
|
client_secret: str = NotImplemented
|
|
97
102
|
redirect_uri: Optional[Union[pydantic.AnyHttpUrl, str]] = NotImplemented
|
|
98
|
-
scope: ClassVar[
|
|
99
|
-
additional_headers: ClassVar[Optional[
|
|
103
|
+
scope: ClassVar[list[str]] = []
|
|
104
|
+
additional_headers: ClassVar[Optional[dict[str, Any]]] = None
|
|
100
105
|
uses_pkce: bool = False
|
|
101
106
|
requires_state: bool = False
|
|
107
|
+
use_id_token_for_user_info: ClassVar[bool] = False
|
|
102
108
|
|
|
103
109
|
_pkce_challenge_length: int = 96
|
|
104
110
|
|
|
@@ -109,7 +115,7 @@ class SSOBase:
|
|
|
109
115
|
redirect_uri: Optional[Union[pydantic.AnyHttpUrl, str]] = None,
|
|
110
116
|
allow_insecure_http: bool = False,
|
|
111
117
|
use_state: bool = False,
|
|
112
|
-
scope: Optional[
|
|
118
|
+
scope: Optional[list[str]] = None,
|
|
113
119
|
):
|
|
114
120
|
"""Base class (mixin) for all SSO providers."""
|
|
115
121
|
self.client_id: str = client_id
|
|
@@ -224,6 +230,18 @@ class SSOBase:
|
|
|
224
230
|
"""
|
|
225
231
|
raise NotImplementedError(f"Provider {self.provider} not supported")
|
|
226
232
|
|
|
233
|
+
async def openid_from_token(self, id_token: dict, session: Optional[httpx.AsyncClient] = None) -> OpenID:
|
|
234
|
+
"""Converts an ID token from the provider's token endpoint to an OpenID object.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
id_token (dict): The id token data retrieved from the token endpoint.
|
|
238
|
+
session: (Optional[httpx.AsyncClient]): The HTTPX AsyncClient session.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
OpenID: The user information in a standardized format.
|
|
242
|
+
"""
|
|
243
|
+
raise NotImplementedError(f"Provider {self.provider} not supported")
|
|
244
|
+
|
|
227
245
|
async def get_discovery_document(self) -> DiscoveryDocument:
|
|
228
246
|
"""Retrieves the discovery document containing useful URLs.
|
|
229
247
|
|
|
@@ -257,14 +275,14 @@ class SSOBase:
|
|
|
257
275
|
self,
|
|
258
276
|
*,
|
|
259
277
|
redirect_uri: Optional[Union[pydantic.AnyHttpUrl, str]] = None,
|
|
260
|
-
params: Optional[
|
|
278
|
+
params: Optional[dict[str, Any]] = None,
|
|
261
279
|
state: Optional[str] = None,
|
|
262
280
|
) -> str:
|
|
263
281
|
"""Generates and returns the prepared login URL.
|
|
264
282
|
|
|
265
283
|
Args:
|
|
266
284
|
redirect_uri (Optional[str]): Overrides the `redirect_uri` specified on this instance.
|
|
267
|
-
params (Optional[
|
|
285
|
+
params (Optional[dict[str, Any]]): Additional query parameters to add to the login request.
|
|
268
286
|
state (Optional[str]): The state parameter for the OAuth 2.0 authorization request.
|
|
269
287
|
|
|
270
288
|
Raises:
|
|
@@ -304,14 +322,14 @@ class SSOBase:
|
|
|
304
322
|
self,
|
|
305
323
|
*,
|
|
306
324
|
redirect_uri: Optional[str] = None,
|
|
307
|
-
params: Optional[
|
|
325
|
+
params: Optional[dict[str, Any]] = None,
|
|
308
326
|
state: Optional[str] = None,
|
|
309
327
|
) -> RedirectResponse:
|
|
310
328
|
"""Constructs and returns a redirect response to the login page of OAuth SSO provider.
|
|
311
329
|
|
|
312
330
|
Args:
|
|
313
331
|
redirect_uri (Optional[str]): Overrides the `redirect_uri` specified on this instance.
|
|
314
|
-
params (Optional[
|
|
332
|
+
params (Optional[dict[str, Any]]): Additional query parameters to add to the login request.
|
|
315
333
|
state (Optional[str]): The state parameter for the OAuth 2.0 authorization request.
|
|
316
334
|
|
|
317
335
|
Returns:
|
|
@@ -330,8 +348,8 @@ class SSOBase:
|
|
|
330
348
|
self,
|
|
331
349
|
request: Request,
|
|
332
350
|
*,
|
|
333
|
-
params: Optional[
|
|
334
|
-
headers: Optional[
|
|
351
|
+
params: Optional[dict[str, Any]] = None,
|
|
352
|
+
headers: Optional[dict[str, Any]] = None,
|
|
335
353
|
redirect_uri: Optional[str] = None,
|
|
336
354
|
convert_response: Literal[True] = True,
|
|
337
355
|
) -> Optional[OpenID]: ...
|
|
@@ -341,28 +359,28 @@ class SSOBase:
|
|
|
341
359
|
self,
|
|
342
360
|
request: Request,
|
|
343
361
|
*,
|
|
344
|
-
params: Optional[
|
|
345
|
-
headers: Optional[
|
|
362
|
+
params: Optional[dict[str, Any]] = None,
|
|
363
|
+
headers: Optional[dict[str, Any]] = None,
|
|
346
364
|
redirect_uri: Optional[str] = None,
|
|
347
365
|
convert_response: Literal[False],
|
|
348
|
-
) -> Optional[
|
|
366
|
+
) -> Optional[dict[str, Any]]: ...
|
|
349
367
|
|
|
350
368
|
@requires_async_context
|
|
351
369
|
async def verify_and_process(
|
|
352
370
|
self,
|
|
353
371
|
request: Request,
|
|
354
372
|
*,
|
|
355
|
-
params: Optional[
|
|
356
|
-
headers: Optional[
|
|
373
|
+
params: Optional[dict[str, Any]] = None,
|
|
374
|
+
headers: Optional[dict[str, Any]] = None,
|
|
357
375
|
redirect_uri: Optional[str] = None,
|
|
358
376
|
convert_response: Union[Literal[True], Literal[False]] = True,
|
|
359
|
-
) -> Union[Optional[OpenID], Optional[
|
|
377
|
+
) -> Union[Optional[OpenID], Optional[dict[str, Any]]]:
|
|
360
378
|
"""Processes the login given a FastAPI (Starlette) Request object. This should be used for the /callback path.
|
|
361
379
|
|
|
362
380
|
Args:
|
|
363
381
|
request (Request): FastAPI or Starlette request object.
|
|
364
|
-
params (Optional[
|
|
365
|
-
headers (Optional[
|
|
382
|
+
params (Optional[dict[str, Any]]): Additional query parameters to pass to the provider.
|
|
383
|
+
headers (Optional[dict[str, Any]]): Additional headers to pass to the provider.
|
|
366
384
|
redirect_uri (Optional[str]): Overrides the `redirect_uri` specified on this instance.
|
|
367
385
|
convert_response (bool): If True, userinfo response is converted to OpenID object.
|
|
368
386
|
|
|
@@ -371,7 +389,7 @@ class SSOBase:
|
|
|
371
389
|
|
|
372
390
|
Returns:
|
|
373
391
|
Optional[OpenID]: User information as OpenID instance (if convert_response == True)
|
|
374
|
-
Optional[
|
|
392
|
+
Optional[dict[str, Any]]: The original JSON response from the API.
|
|
375
393
|
"""
|
|
376
394
|
headers = headers or {}
|
|
377
395
|
code = request.query_params.get("code")
|
|
@@ -433,7 +451,7 @@ class SSOBase:
|
|
|
433
451
|
|
|
434
452
|
async def __aexit__(
|
|
435
453
|
self,
|
|
436
|
-
_exc_type: Optional[
|
|
454
|
+
_exc_type: Optional[type[BaseException]],
|
|
437
455
|
_exc_val: Optional[BaseException],
|
|
438
456
|
_exc_tb: Optional[TracebackType],
|
|
439
457
|
) -> None:
|
|
@@ -442,14 +460,14 @@ class SSOBase:
|
|
|
442
460
|
|
|
443
461
|
def __exit__(
|
|
444
462
|
self,
|
|
445
|
-
_exc_type: Optional[
|
|
463
|
+
_exc_type: Optional[type[BaseException]],
|
|
446
464
|
_exc_val: Optional[BaseException],
|
|
447
465
|
_exc_tb: Optional[TracebackType],
|
|
448
466
|
) -> None:
|
|
449
467
|
return None
|
|
450
468
|
|
|
451
469
|
@property
|
|
452
|
-
def _extra_query_params(self) ->
|
|
470
|
+
def _extra_query_params(self) -> dict:
|
|
453
471
|
return {}
|
|
454
472
|
|
|
455
473
|
@overload
|
|
@@ -458,8 +476,8 @@ class SSOBase:
|
|
|
458
476
|
code: str,
|
|
459
477
|
request: Request,
|
|
460
478
|
*,
|
|
461
|
-
params: Optional[
|
|
462
|
-
additional_headers: Optional[
|
|
479
|
+
params: Optional[dict[str, Any]] = None,
|
|
480
|
+
additional_headers: Optional[dict[str, Any]] = None,
|
|
463
481
|
redirect_uri: Optional[str] = None,
|
|
464
482
|
pkce_code_verifier: Optional[str] = None,
|
|
465
483
|
convert_response: Literal[True] = True,
|
|
@@ -471,12 +489,12 @@ class SSOBase:
|
|
|
471
489
|
code: str,
|
|
472
490
|
request: Request,
|
|
473
491
|
*,
|
|
474
|
-
params: Optional[
|
|
475
|
-
additional_headers: Optional[
|
|
492
|
+
params: Optional[dict[str, Any]] = None,
|
|
493
|
+
additional_headers: Optional[dict[str, Any]] = None,
|
|
476
494
|
redirect_uri: Optional[str] = None,
|
|
477
495
|
pkce_code_verifier: Optional[str] = None,
|
|
478
496
|
convert_response: Literal[False],
|
|
479
|
-
) -> Optional[
|
|
497
|
+
) -> Optional[dict[str, Any]]: ...
|
|
480
498
|
|
|
481
499
|
@requires_async_context
|
|
482
500
|
async def process_login(
|
|
@@ -484,20 +502,20 @@ class SSOBase:
|
|
|
484
502
|
code: str,
|
|
485
503
|
request: Request,
|
|
486
504
|
*,
|
|
487
|
-
params: Optional[
|
|
488
|
-
additional_headers: Optional[
|
|
505
|
+
params: Optional[dict[str, Any]] = None,
|
|
506
|
+
additional_headers: Optional[dict[str, Any]] = None,
|
|
489
507
|
redirect_uri: Optional[str] = None,
|
|
490
508
|
pkce_code_verifier: Optional[str] = None,
|
|
491
509
|
convert_response: Union[Literal[True], Literal[False]] = True,
|
|
492
|
-
) -> Union[Optional[OpenID], Optional[
|
|
510
|
+
) -> Union[Optional[OpenID], Optional[dict[str, Any]]]:
|
|
493
511
|
"""Processes login from the callback endpoint to verify the user and request user info endpoint.
|
|
494
512
|
It's a lower-level method, typically, you should use `verify_and_process` instead.
|
|
495
513
|
|
|
496
514
|
Args:
|
|
497
515
|
code (str): The authorization code.
|
|
498
516
|
request (Request): FastAPI or Starlette request object.
|
|
499
|
-
params (Optional[
|
|
500
|
-
additional_headers (Optional[
|
|
517
|
+
params (Optional[dict[str, Any]]): Additional query parameters to pass to the provider.
|
|
518
|
+
additional_headers (Optional[dict[str, Any]]): Additional headers to be added to all requests.
|
|
501
519
|
redirect_uri (Optional[str]): Overrides the `redirect_uri` specified on this instance.
|
|
502
520
|
pkce_code_verifier (Optional[str]): A PKCE code verifier sent to the server to verify the login request.
|
|
503
521
|
convert_response (bool): If True, userinfo response is converted to OpenID object.
|
|
@@ -507,7 +525,7 @@ class SSOBase:
|
|
|
507
525
|
|
|
508
526
|
Returns:
|
|
509
527
|
Optional[OpenID]: User information in OpenID format if the login was successful (convert_response == True).
|
|
510
|
-
Optional[
|
|
528
|
+
Optional[dict[str, Any]]: Original userinfo API endpoint response.
|
|
511
529
|
"""
|
|
512
530
|
if self._oauth_client is not None: # pragma: no cover
|
|
513
531
|
self._oauth_client = None
|
|
@@ -565,5 +583,9 @@ class SSOBase:
|
|
|
565
583
|
response = await session.get(uri)
|
|
566
584
|
content = response.json()
|
|
567
585
|
if convert_response:
|
|
586
|
+
if self.use_id_token_for_user_info:
|
|
587
|
+
if not self._id_token:
|
|
588
|
+
raise SSOLoginError(401, f"Provider {self.provider!r} did not return id token.")
|
|
589
|
+
return await self.openid_from_token(_decode_id_token(self._id_token), session)
|
|
568
590
|
return await self.openid_from_response(content, session)
|
|
569
591
|
return content
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""BitBucket SSO Oauth Helper class"""
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, ClassVar,
|
|
3
|
+
from typing import TYPE_CHECKING, ClassVar, Optional, Union
|
|
4
4
|
|
|
5
5
|
import pydantic
|
|
6
6
|
|
|
@@ -23,7 +23,7 @@ class BitbucketSSO(SSOBase):
|
|
|
23
23
|
client_secret: str,
|
|
24
24
|
redirect_uri: Optional[Union[pydantic.AnyHttpUrl, str]] = None,
|
|
25
25
|
allow_insecure_http: bool = False,
|
|
26
|
-
scope: Optional[
|
|
26
|
+
scope: Optional[list[str]] = None,
|
|
27
27
|
):
|
|
28
28
|
super().__init__(
|
|
29
29
|
client_id=client_id,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Discord SSO Oauth Helper class"""
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, ClassVar,
|
|
3
|
+
from typing import TYPE_CHECKING, ClassVar, Optional, Union
|
|
4
4
|
|
|
5
5
|
import pydantic
|
|
6
6
|
|
|
@@ -22,7 +22,7 @@ class DiscordSSO(SSOBase):
|
|
|
22
22
|
client_secret: str,
|
|
23
23
|
redirect_uri: Optional[Union[pydantic.AnyHttpUrl, str]] = None,
|
|
24
24
|
allow_insecure_http: bool = False,
|
|
25
|
-
scope: Optional[
|
|
25
|
+
scope: Optional[list[str]] = None,
|
|
26
26
|
):
|
|
27
27
|
super().__init__(
|
|
28
28
|
client_id=client_id,
|
|
@@ -3,7 +3,7 @@ with close to no code.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Callable,
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
|
|
7
7
|
|
|
8
8
|
from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase
|
|
9
9
|
|
|
@@ -16,10 +16,10 @@ logger = logging.getLogger(__name__)
|
|
|
16
16
|
def create_provider(
|
|
17
17
|
*,
|
|
18
18
|
name: str = "generic",
|
|
19
|
-
default_scope: Optional[
|
|
19
|
+
default_scope: Optional[list[str]] = None,
|
|
20
20
|
discovery_document: Union[DiscoveryDocument, Callable[[SSOBase], DiscoveryDocument]],
|
|
21
|
-
response_convertor: Optional[Callable[[
|
|
22
|
-
) ->
|
|
21
|
+
response_convertor: Optional[Callable[[dict[str, Any], Optional["httpx.AsyncClient"]], OpenID]] = None
|
|
22
|
+
) -> type[SSOBase]:
|
|
23
23
|
"""A factory to create a generic OAuth client usable with almost any OAuth provider.
|
|
24
24
|
Returns a class.
|
|
25
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Gitlab SSO Oauth Helper class."""
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, ClassVar,
|
|
3
|
+
from typing import TYPE_CHECKING, ClassVar, Optional, Union
|
|
4
4
|
from urllib.parse import urljoin
|
|
5
5
|
|
|
6
6
|
import pydantic
|
|
@@ -26,7 +26,7 @@ class GitlabSSO(SSOBase):
|
|
|
26
26
|
redirect_uri: Optional[Union[pydantic.AnyHttpUrl, str]] = None,
|
|
27
27
|
allow_insecure_http: bool = False,
|
|
28
28
|
use_state: bool = False, # TODO: Remove use_state argument
|
|
29
|
-
scope: Optional[
|
|
29
|
+
scope: Optional[list[str]] = None,
|
|
30
30
|
base_endpoint_url: Optional[str] = None,
|
|
31
31
|
) -> None:
|
|
32
32
|
super().__init__(
|
|
@@ -47,7 +47,7 @@ class GitlabSSO(SSOBase):
|
|
|
47
47
|
"userinfo_endpoint": urljoin(self.base_endpoint_url, "/api/v4/user"),
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
def _parse_name(self, full_name: Optional[str]) ->
|
|
50
|
+
def _parse_name(self, full_name: Optional[str]) -> tuple[Union[str, None], Union[str, None]]:
|
|
51
51
|
"""Parses the full name from Gitlab into the first and last name."""
|
|
52
52
|
if not full_name or not isinstance(full_name, str):
|
|
53
53
|
return None, None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""LinkedIn SSO Oauth Helper class."""
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, ClassVar,
|
|
3
|
+
from typing import TYPE_CHECKING, ClassVar, Optional
|
|
4
4
|
|
|
5
5
|
from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase
|
|
6
6
|
|
|
@@ -14,9 +14,10 @@ class LinkedInSSO(SSOBase):
|
|
|
14
14
|
provider = "linkedin"
|
|
15
15
|
scope: ClassVar = ["openid", "profile", "email"]
|
|
16
16
|
additional_headers: ClassVar = {"accept": "application/json"}
|
|
17
|
+
use_id_token_for_user_info: ClassVar = True
|
|
17
18
|
|
|
18
19
|
@property
|
|
19
|
-
def _extra_query_params(self) ->
|
|
20
|
+
def _extra_query_params(self) -> dict:
|
|
20
21
|
return {"client_secret": self.client_secret}
|
|
21
22
|
|
|
22
23
|
async def get_discovery_document(self) -> DiscoveryDocument:
|
|
@@ -26,6 +27,9 @@ class LinkedInSSO(SSOBase):
|
|
|
26
27
|
"userinfo_endpoint": "https://api.linkedin.com/v2/userinfo",
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
async def openid_from_token(self, id_token: dict, session: Optional["httpx.AsyncClient"] = None) -> OpenID:
|
|
31
|
+
return await self.openid_from_response(id_token, session)
|
|
32
|
+
|
|
29
33
|
async def openid_from_response(self, response: dict, session: Optional["httpx.AsyncClient"] = None) -> OpenID:
|
|
30
34
|
return OpenID(
|
|
31
35
|
email=response.get("email"),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Microsoft SSO Oauth Helper class."""
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, ClassVar,
|
|
3
|
+
from typing import TYPE_CHECKING, ClassVar, Optional, Union
|
|
4
4
|
|
|
5
5
|
import pydantic
|
|
6
6
|
|
|
@@ -25,7 +25,7 @@ class MicrosoftSSO(SSOBase):
|
|
|
25
25
|
redirect_uri: Optional[Union[pydantic.AnyHttpUrl, str]] = None,
|
|
26
26
|
allow_insecure_http: bool = False,
|
|
27
27
|
use_state: bool = False, # TODO: Remove use_state argument
|
|
28
|
-
scope: Optional[
|
|
28
|
+
scope: Optional[list[str]] = None,
|
|
29
29
|
tenant: Optional[str] = None,
|
|
30
30
|
):
|
|
31
31
|
super().__init__(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Naver SSO Oauth Helper class."""
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, ClassVar,
|
|
3
|
+
from typing import TYPE_CHECKING, ClassVar, Optional
|
|
4
4
|
|
|
5
5
|
from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ class NaverSSO(SSOBase):
|
|
|
12
12
|
"""Class providing login using Naver OAuth."""
|
|
13
13
|
|
|
14
14
|
provider = "naver"
|
|
15
|
-
scope: ClassVar[
|
|
15
|
+
scope: ClassVar[list[str]] = []
|
|
16
16
|
additional_headers: ClassVar = {"accept": "application/json"}
|
|
17
17
|
|
|
18
18
|
async def get_discovery_document(self) -> DiscoveryDocument:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "fastapi-sso"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.18.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"
|
|
@@ -36,7 +36,7 @@ addopts = [
|
|
|
36
36
|
line-length = 120
|
|
37
37
|
|
|
38
38
|
[tool.ruff]
|
|
39
|
-
target-version = "
|
|
39
|
+
target-version = "py39"
|
|
40
40
|
line-length = 120
|
|
41
41
|
|
|
42
42
|
[tool.ruff.lint]
|
|
@@ -97,21 +97,22 @@ markdown-include = "^0.8.1"
|
|
|
97
97
|
mkdocs-material = { extras = ["imaging"], version = "^9.3.2" }
|
|
98
98
|
mkdocstrings = { extras = ["python"], version = ">=0.23,<0.27" }
|
|
99
99
|
mypy = "^1"
|
|
100
|
-
poethepoet = ">=0.21.1,<0.
|
|
100
|
+
poethepoet = ">=0.21.1,<0.31.0"
|
|
101
101
|
pre-commit = "^3"
|
|
102
102
|
pytest = ">=7,<9"
|
|
103
103
|
pytest-asyncio = "^0.24"
|
|
104
104
|
pytest-cov = ">=4,<6"
|
|
105
105
|
uvicorn = ">=0.23.1"
|
|
106
|
-
ruff = ">=0.4.2,<0.
|
|
106
|
+
ruff = ">=0.4.2,<0.12.0"
|
|
107
107
|
|
|
108
108
|
[tool.poetry.dependencies]
|
|
109
109
|
fastapi = ">=0.80"
|
|
110
110
|
httpx = ">=0.23.0"
|
|
111
111
|
oauthlib = ">=3.1.0"
|
|
112
112
|
pydantic = { extras = ["email"], version = ">=1.8.0" }
|
|
113
|
-
python = ">=3.
|
|
113
|
+
python = ">=3.9,<4.0"
|
|
114
114
|
typing-extensions = { version = "^4.12.2", python = "<3.10" }
|
|
115
|
+
pyjwt = "^2.10.1"
|
|
115
116
|
|
|
116
117
|
[build-system]
|
|
117
118
|
requires = ["poetry-core>=1.0.0"]
|
|
File without changes
|
|
File without changes
|
|
@@ -22,12 +22,10 @@ from .sso.spotify import SpotifySSO
|
|
|
22
22
|
from .sso.twitter import TwitterSSO
|
|
23
23
|
|
|
24
24
|
__all__ = [
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"SSOLoginError",
|
|
25
|
+
"BitbucketSSO",
|
|
26
|
+
"DiscordSSO",
|
|
28
27
|
"FacebookSSO",
|
|
29
28
|
"FitbitSSO",
|
|
30
|
-
"create_provider",
|
|
31
29
|
"GithubSSO",
|
|
32
30
|
"GitlabSSO",
|
|
33
31
|
"GoogleSSO",
|
|
@@ -37,8 +35,10 @@ __all__ = [
|
|
|
37
35
|
"MicrosoftSSO",
|
|
38
36
|
"NaverSSO",
|
|
39
37
|
"NotionSSO",
|
|
38
|
+
"OpenID",
|
|
39
|
+
"SSOBase",
|
|
40
|
+
"SSOLoginError",
|
|
40
41
|
"SpotifySSO",
|
|
41
42
|
"TwitterSSO",
|
|
42
|
-
"
|
|
43
|
-
"DiscordSSO",
|
|
43
|
+
"create_provider",
|
|
44
44
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|