3232from google .oauth2 .credentials import Credentials
3333from google_auth_oauthlib import flow
3434
35- CLIENT_SECRET_PATH = os .environ .get (' CLIENT_SECRET_PATH' , 'client_secret .json' )
36- JWT_SECRET = os .environ .get (' SESSION_SECRET' , ' notasecret' )
35+ CLIENT_SECRET_PATH = os .environ .get (" CLIENT_SECRET_PATH" , "client_secrets .json" )
36+ JWT_SECRET = os .environ .get (" SESSION_SECRET" , " notasecret" )
3737
38- mod = flask .Blueprint (' auth' , __name__ )
38+ mod = flask .Blueprint (" auth" , __name__ )
3939
4040# Scopes required to access the People API.
4141PEOPLE_API_SCOPES = [
42- ' openid' ,
43- ' https://www.googleapis.com/auth/user.emails.read' ,
44- ' https://www.googleapis.com/auth/user.addresses.read' ,
45- ' https://www.googleapis.com/auth/userinfo.profile' ,
46- ' https://www.googleapis.com/auth/userinfo.email' ,
47- ' https://www.googleapis.com/auth/user.phonenumbers.read' ,
42+ " openid" ,
43+ " https://www.googleapis.com/auth/user.emails.read" ,
44+ " https://www.googleapis.com/auth/user.addresses.read" ,
45+ " https://www.googleapis.com/auth/userinfo.profile" ,
46+ " https://www.googleapis.com/auth/userinfo.email" ,
47+ " https://www.googleapis.com/auth/user.phonenumbers.read" ,
4848]
4949
50+
5051class Store :
5152 """Manages storage in Google Cloud Datastore."""
5253
@@ -62,12 +63,15 @@ def get_user_credentials(self, user_name: str) -> Credentials | None:
6263 Returns:
6364 A Credentials object, or None if the user has not authorized the app.
6465 """
65- key = self .datastore_client .key ('RefreshToken' , user_name )
66- entity = self .datastore_client .get (key )
67-
68- if entity is None or 'credentials' not in entity :
66+ try :
67+ key = self .datastore_client .key ("RefreshToken" , user_name )
68+ entity = self .datastore_client .get (key )
69+ if entity is None or "credentials" not in entity :
70+ return None
71+ return Credentials (** entity ["credentials" ])
72+ except Exception as e :
73+ logging .exception ("Error retrieving credentials: %s" , e )
6974 return None
70- return Credentials (** entity ['credentials' ])
7175
7276 def put_user_credentials (self , user_name : str , creds : Credentials ) -> None :
7377 """Stores OAuth2 credentials for a user.
@@ -76,35 +80,51 @@ def put_user_credentials(self, user_name: str, creds: Credentials) -> None:
7680 user_name (str): The identifier for the associated user.
7781 creds (Credentials): The OAuth2 credentials obtained for the user.
7882 """
79- key = self .datastore_client .key ('RefreshToken' , user_name )
80- entity = datastore .Entity (key )
81- entity .update ({
82- 'credentials' : {
83- 'token' : creds .token ,
84- 'refresh_token' : creds .refresh_token ,
85- 'token_uri' : creds .token_uri ,
86- 'client_id' : creds .client_id ,
87- 'client_secret' : creds .client_secret ,
88- 'scopes' : creds .scopes ,
89- },
90- 'timestamp' : time .time ()
91- })
92- self .datastore_client .put (entity )
83+ try :
84+ key = self .datastore_client .key ("RefreshToken" , user_name )
85+ entity = datastore .Entity (key )
86+ entity .update (
87+ {
88+ "credentials" : {
89+ "token" : creds .token ,
90+ "refresh_token" : creds .refresh_token ,
91+ "token_uri" : creds .token_uri ,
92+ "client_id" : creds .client_id ,
93+ "client_secret" : creds .client_secret ,
94+ "scopes" : creds .scopes ,
95+ },
96+ "timestamp" : time .time (),
97+ }
98+ )
99+ self .datastore_client .put (entity )
100+ except Exception as e :
101+ logging .exception ("Error storing credentials: %s" , e )
93102
94103 def delete_user_credentials (self , user_name : str ) -> None :
95104 """Deleted stored OAuth2 credentials for a user.
96105
97106 Args:
98107 user_name (str): The identifier for the associated user.
99108 """
100- key = self .datastore_client .key ('RefreshToken' , user_name )
109+ try :
110+ key = self .datastore_client .key ("RefreshToken" , user_name )
111+ self .datastore_client .delete (key )
112+ except Exception as e :
113+ logging .exception ("Error deleting credentials: %s" , e )
114+
115+ key = self .datastore_client .key ("RefreshToken" , user_name )
101116 self .datastore_client .delete (key )
102117
103118
104119def get_user_credentials (user_name : str ) -> Credentials :
105120 """Gets stored crednetials for a user, if it exists."""
106- store = Store ()
107- return store .get_user_credentials (user_name )
121+ try :
122+ store = Store ()
123+ return store .get_user_credentials (user_name )
124+ except Exception as e :
125+ logging .exception ("Error getting credentials: %s" , e )
126+ return None
127+
108128
109129def get_config_url (event ) -> Any :
110130 """Gets the authorization URL to redirect the user to.
@@ -116,11 +136,14 @@ def get_config_url(event) -> Any:
116136 Returns:
117137 str: The authorization URL to direct the user to.
118138 """
119- payload = {
120- 'completion_url' : event ['configCompleteRedirectUrl' ]
121- }
122- token = jwt .encode (payload , JWT_SECRET , algorithm = 'HS256' )
123- return flask .url_for ('auth.start_auth' , token = token , _external = True )
139+ try :
140+ payload = {"completion_url" : event ["configCompleteRedirectUrl" ]}
141+ token = jwt .encode (payload , JWT_SECRET , algorithm = "HS256" )
142+ return flask .url_for ("auth.start_auth" , token = token , _external = True )
143+ except Exception as e :
144+ logging .exception ("Error getting config URL: %s" , e )
145+ return None
146+
124147
125148def logout (user_name : str ) -> None :
126149 """Logs out the user, removing their stored credentials and revoking the
@@ -129,66 +152,78 @@ def logout(user_name: str) -> None:
129152 Args:
130153 user_name (str): The identifier of the user.
131154 """
132- store = Store ()
133- user_credentials = store .get_user_credentials (user_name )
134- if user_credentials is None :
135- logging .info ('Ignoring logout request for user %s' , user_name )
136- return
137- logging .info ('Logging out user %s' , user_name )
138- store .delete_user_credentials (user_name )
139- request = requests .Request ()
140- request .post (
141- 'https://accounts.google.com/o/oauth2/revoke' ,
142- params = {'token' : user_credentials .token },
143- headers = {'Content-Type' : 'application/x-www-form-urlencoded' })
144-
145-
146- @mod .route ('/start' )
155+ try :
156+ store = Store ()
157+ user_credentials = store .get_user_credentials (user_name )
158+ if user_credentials is None :
159+ logging .info ("Ignoring logout request for user %s" , user_name )
160+ return
161+ logging .info ("Logging out user %s" , user_name )
162+ store .delete_user_credentials (user_name )
163+ request = requests .Request ()
164+ request .post (
165+ "https://accounts.google.com/o/oauth2/revoke" ,
166+ params = {"token" : user_credentials .token },
167+ headers = {"Content-Type" : "application/x-www-form-urlencoded" },
168+ )
169+ except Exception as e :
170+ logging .exception ("Error logging out user: %s" , e )
171+
172+
173+ @mod .route ("/start" )
147174def start_auth () -> flask .Response :
148175 """Begins the oauth flow to authorize access to profile data."""
149- token = flask .request .args ['token' ]
150- request = jwt .decode (token , JWT_SECRET , algorithm = 'HS256' )
151-
152- flask .session ['completion_url' ] = request ['completion_url' ]
153- oauth2_flow = flow .Flow .from_client_secrets_file (
154- CLIENT_SECRET_PATH ,
155- scopes = PEOPLE_API_SCOPES ,
156- redirect_uri = flask .url_for ('auth.on_oauth2_callback' , _external = True ))
157- oauth2_url , state = oauth2_flow .authorization_url (
158- access_type = 'offline' ,
159- include_granted_scopes = 'true' ,
160- prompt = 'consent' )
161- flask .session ['state' ] = state
162- return flask .redirect (oauth2_url )
163-
164- @mod .route ('/callback' )
176+ try :
177+ token = flask .request .args ["token" ]
178+ request = jwt .decode (token , JWT_SECRET , algorithms = ["HS256" ])
179+
180+ flask .session ["completion_url" ] = request ["completion_url" ]
181+ oauth2_flow = flow .Flow .from_client_secrets_file (
182+ CLIENT_SECRET_PATH ,
183+ scopes = PEOPLE_API_SCOPES ,
184+ redirect_uri = flask .url_for ("auth.on_oauth2_callback" , _external = True ),
185+ )
186+ oauth2_url , state = oauth2_flow .authorization_url (
187+ access_type = "offline" , include_granted_scopes = "true" , prompt = "consent"
188+ )
189+ flask .session ["state" ] = state
190+ return flask .redirect (oauth2_url )
191+ except Exception as e :
192+ logging .exception ("Error starting auth: %s" , e )
193+ return flask .abort (403 )
194+
195+
196+ @mod .route ("/callback" )
165197def on_oauth2_callback () -> flask .Response :
166198 """Handles the OAuth callback."""
167- saved_state = flask .session ['state' ]
168- state = flask .request .args ['state' ]
169-
170- if state != saved_state :
171- logging .warn ('Mismatched state in oauth response' )
199+ try :
200+ saved_state = flask .session ["state" ]
201+ state = flask .request .args ["state" ]
202+
203+ if state != saved_state :
204+ logging .warn ("Mismatched state in oauth response" )
205+ return flask .abort (403 )
206+
207+ redirect_uri = flask .url_for ("auth.on_oauth2_callback" , _external = True )
208+ oauth2_flow = flow .Flow .from_client_secrets_file (
209+ CLIENT_SECRET_PATH , scopes = PEOPLE_API_SCOPES , redirect_uri = redirect_uri
210+ )
211+ oauth2_flow .fetch_token (authorization_response = flask .request .url )
212+ creds = oauth2_flow .credentials
213+
214+ # Use the id_token to identify the chat user.
215+ request = requests .Request ()
216+ id_info = id_token .verify_oauth2_token (creds .id_token , request , creds .client_id )
217+
218+ if id_info ["iss" ] != "https://accounts.google.com" :
219+ flask .abort (403 )
220+
221+ user_id = id_info ["sub" ]
222+ user_name = "users/{user_id}" .format (user_id = user_id )
223+ store = Store ()
224+ store .put_user_credentials (user_name , creds )
225+ completion_url = flask .session ["completion_url" ]
226+ return flask .redirect (completion_url )
227+ except Exception as e :
228+ logging .exception ("Error completing auth: %s" , e )
172229 return flask .abort (403 )
173-
174- redirect_uri = flask .url_for ('auth.on_oauth2_callback' , _external = True )
175- oauth2_flow = flow .Flow .from_client_secrets_file (
176- CLIENT_SECRET_PATH ,
177- scopes = PEOPLE_API_SCOPES ,
178- redirect_uri = redirect_uri )
179- oauth2_flow .fetch_token (authorization_response = flask .request .url )
180- creds = oauth2_flow .credentials
181-
182- # Use the id_token to identify the chat user.
183- request = requests .Request ()
184- id_info = id_token .verify_oauth2_token (creds .id_token , request , creds .client_id )
185-
186- if id_info ['iss' ] != 'https://accounts.google.com' :
187- flask .abort (403 )
188-
189- user_id = id_info ['sub' ]
190- user_name = 'users/{user_id}' .format (user_id = user_id )
191- store = Store ()
192- store .put_user_credentials (user_name , creds )
193- completion_url = flask .session ['completion_url' ]
194- return flask .redirect (completion_url )
0 commit comments