14
14
15
15
import json
16
16
import math
17
+ import pkce
18
+ import random
17
19
import datetime
18
20
import python_jwt as jwt
19
21
import jwcrypto .jwk as jwk
22
+ from hashlib import sha256
20
23
from urllib .parse import parse_qs
21
24
from cryptography .hazmat .primitives .serialization import Encoding , NoEncryption , PrivateFormat
22
25
@@ -50,6 +53,8 @@ def __init__(self, api_key, credentials, requests, client_secret=None):
50
53
51
54
self .provider_id = None
52
55
self .session_id = None
56
+ self .__code_verifier = None
57
+ self .__nonce = None
53
58
54
59
if client_secret :
55
60
self .client_secret = _load_client_secret (client_secret )
@@ -63,6 +68,15 @@ def authenticate_login_with_google(self):
63
68
"""
64
69
return self .create_authentication_uri ('google.com' )
65
70
71
+ def authenticate_login_with_facebook (self ):
72
+ """ Redirect the user to Facebook's OAuth 2.0 server to
73
+ initiate the authentication and authorization process.
74
+
75
+ :return: Facebook Sign In URL
76
+ :rtype: str
77
+ """
78
+ return self .create_authentication_uri ('facebook.com' )
79
+
66
80
def create_authentication_uri (self , provider_id ):
67
81
""" Creates an authentication URI for the given social
68
82
provider.
@@ -91,17 +105,22 @@ def create_authentication_uri(self, provider_id):
91
105
request_ref = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuthUri?key={0}" .format (self .api_key )
92
106
93
107
data = {
94
- "authFlowType" : 'CODE_FLOW' ,
95
108
"clientId" : self .client_secret ['client_id' ],
96
109
"providerId" : provider_id ,
97
110
"continueUri" : self .client_secret ['redirect_uris' ][0 ],
98
- "customParameter" : {
99
- "access_type" : 'offline' ,
100
- "prompt" : 'select_account' ,
101
- "include_granted_scopes" : 'true' ,
102
- }
103
111
}
104
112
113
+ self .__nonce = "" .join (random .choice ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ) for _ in range (20 ))
114
+
115
+ if provider_id == 'google.com' :
116
+ data ['authFlowType' ] = 'CODE_FLOW'
117
+ data ['customParameter' ] = {"access_type" : 'offline' , "prompt" : 'select_account' , "include_granted_scopes" : 'true' , "nonce" : self .__nonce }
118
+
119
+ if provider_id == 'facebook.com' :
120
+ self .__code_verifier , code_challenge = pkce .generate_pkce_pair ()
121
+ data ['oauthScope' ] = 'openid'
122
+ data ['customParameter' ] = {"code_challenge" : code_challenge , "code_challenge_method" : 'S256' , "nonce" : sha256 (self .__nonce .encode ('utf' )).hexdigest ()}
123
+
105
124
headers = {"content-type" : "application/json; charset=UTF-8" }
106
125
request_object = self .requests .post (request_ref , headers = headers , json = data )
107
126
@@ -221,7 +240,7 @@ def sign_in_with_custom_token(self, token):
221
240
:rtype: dict
222
241
"""
223
242
224
- request_ref = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key={0}" .format (self .api_key ) # noqa
243
+ request_ref = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key={0}" .format (self .api_key ) # noqa
225
244
226
245
headers = {"content-type" : "application/json; charset=UTF-8" }
227
246
data = json .dumps ({"returnSecureToken" : True , "token" : token })
@@ -447,7 +466,7 @@ def sign_in_with_oauth_credential(self, oauth2callback_url):
447
466
448
467
token = self ._token_from_auth_url (oauth2callback_url )
449
468
data = {
450
- 'postBody' : ' providerId={0}&{1}={2}' . format ( self .provider_id , token ['type' ], token ['value' ]) ,
469
+ 'postBody' : f" providerId={ self .provider_id } & { token ['type' ]} = { token ['value' ]} &nonce= { self . __nonce } " ,
451
470
'autoCreate' : 'true' ,
452
471
'requestUri' : self .client_secret ['redirect_uris' ][0 ],
453
472
'sessionId' : self .session_id ,
@@ -476,18 +495,22 @@ def _token_from_auth_url(self, url):
476
495
:rtype: dict
477
496
"""
478
497
479
- request_ref = 'https://www.googleapis.com/oauth2/v4/token'
498
+ request_ref = _token_host ( self . provider_id )
480
499
481
500
auth_url_values = parse_qs (url [url .index ('?' ) + 1 :])
482
501
483
502
data = {
484
503
'client_id' : self .client_secret ['client_id' ],
485
504
'client_secret' : self .client_secret ['client_secret' ],
486
505
'code' : auth_url_values ['code' ][0 ],
487
- 'grant_type' : 'authorization_code' ,
488
506
'redirect_uri' : self .client_secret ['redirect_uris' ][0 ],
489
507
}
490
508
509
+ if self .provider_id == 'google.com' :
510
+ data ['grant_type' ] = 'authorization_code'
511
+ elif self .provider_id == 'facebook.com' :
512
+ data ['code_verifier' ] = self .__code_verifier
513
+
491
514
headers = {"content-type" : "application/x-www-form-urlencoded; charset=UTF-8" }
492
515
request_object = self .requests .post (request_ref , headers = headers , data = data )
493
516
@@ -557,7 +580,8 @@ def _load_client_secret(secret):
557
580
558
581
# Google client secrets are stored within 'web' key
559
582
# We will remove the key, and replace it with the dict type value of it
560
- secret = secret ['web' ]
583
+ if secret .get ('web' ):
584
+ secret = secret ['web' ]
561
585
562
586
return secret
563
587
@@ -574,7 +598,15 @@ def _token_expire_time(user):
574
598
:return: Token dictionary with an ``expiresAt`` key.
575
599
:rtype: dict
576
600
"""
577
-
578
- user ['expiresAt' ] = math .floor (datetime .datetime .today ().timestamp () + int (user .get ('expiresIn' )) - 60 )
579
-
601
+
602
+ user ['expiresAt' ] = math .floor (datetime .datetime .today ().timestamp () + int (user .get ('expiresIn' , 3600 )) - 60 )
603
+
580
604
return user
605
+
606
+
607
+ def _token_host (provider ):
608
+ if provider == 'google.com' :
609
+ return 'https://www.googleapis.com/oauth2/v4/token'
610
+
611
+ elif provider == 'facebook.com' :
612
+ return 'https://graph.facebook.com/v14.0/oauth/access_token'
0 commit comments