@@ -111,52 +111,33 @@ class _AuthCodeHandler(BaseHTTPRequestHandler):
111111 def do_GET (self ):
112112 # For flexibility, we choose to not check self.path matching redirect_uri
113113 #assert self.path.startswith('/THE_PATH_REGISTERED_BY_THE_APP')
114- parsed_url = urlparse (self .path )
115- qs = parse_qs (parsed_url .query )
116114
117- if qs .get ('code' ): # Auth code via GET is a security risk - reject it
118- logger .error ("Received auth code via query string (GET request). "
119- "This is a security risk. Only form_post (POST) is supported." )
115+ # Check if this is a blank redirect (eSTS error flow where user clicked OK)
116+ qs = parse_qs (urlparse (self .path ).query )
117+ if not qs or (not qs .get ('code' ) and not qs .get ('error' )):
118+ # Blank redirect from eSTS error - show generic error and mark done
120119 self ._send_full_response (
121- "Authentication method not supported. "
122- "The application requires response_mode=form_post for security. "
123- "Please ensure your application registration uses form_post response mode." ,
124- is_ok = False )
125- elif qs .get ("error" ): # Errors can come via GET - process them
126- auth_response = _qs2kv (qs )
127- self ._process_auth_response (auth_response )
128- elif not qs and parsed_url .path == '/' :
129- # GET request to root with no query params - this is likely from clicking OK on eSTS error
130- # Treat it as an error since success would come via POST
131- logger .warning ("Received GET request to root path with no query parameters - treating as authentication error" )
132- auth_response = {
133- "error" : "authentication_failed" ,
134- "error_description" : "Please close this window and try again."
135- }
136- # Include the original state so state validation doesn't fail
137- if self .server .auth_state :
138- auth_response ["state" ] = self .server .auth_state
139- self ._process_auth_response (auth_response )
120+ "Authentication could not be completed. "
121+ "You can close this window and return to the application." )
122+ self .server .done = True
140123 else :
124+ # GET request with parameters (shouldn't happen with form_post, but handle gracefully)
141125 self ._send_full_response (self .server .welcome_page )
142126 # NOTE: Don't do self.server.shutdown() here. It'll halt the server.
143127
144128 def do_POST (self ):
145129 # Handle form_post response mode where auth code is sent via POST body
146130 content_length = int (self .headers .get ('Content-Length' , 0 ))
147131 post_data = self .rfile .read (content_length ).decode ('utf-8' )
148- try :
149- from urllib .parse import parse_qs as parse_qs_post
150- except ImportError :
151- from urlparse import parse_qs as parse_qs_post
152132
153- qs = parse_qs_post (post_data )
133+ qs = parse_qs (post_data )
154134 if qs .get ('code' ) or qs .get ('error' ): # So, it is an auth response
155135 auth_response = _qs2kv (qs )
156136 logger .debug ("Got auth response via POST: %s" , auth_response )
157137 self ._process_auth_response (auth_response )
158138 else :
159139 self ._send_full_response ("Invalid POST request" , is_ok = False )
140+ # NOTE: Don't do self.server.shutdown() here. It'll halt the server.
160141
161142 def _process_auth_response (self , auth_response ):
162143 """Process the auth response from either GET or POST request."""
@@ -177,7 +158,15 @@ def _process_auth_response(self, auth_response):
177158 # to avoid showing literal placeholder text like "$error_description"
178159 safe_data .setdefault ("error" , "" )
179160 safe_data .setdefault ("error_description" , "" )
180- safe_data .setdefault ("error_uri" , "" )
161+ # Format error message nicely: include ": description." only if description exists
162+ if "code" not in auth_response : # This is an error response
163+ error_desc = auth_response .get ("error_description" , "" ).strip ()
164+ if error_desc :
165+ safe_data ["error_message" ] = f"{ safe_data ['error' ]} : { error_desc } ."
166+ else :
167+ safe_data ["error_message" ] = safe_data ["error" ]
168+ else :
169+ safe_data ["error_message" ] = ""
181170 self ._send_full_response (template .safe_substitute (** safe_data ))
182171 self .server .auth_response = auth_response # Set it now, after the response is likely sent
183172
@@ -336,6 +325,15 @@ def _get_auth_response(self, result, auth_uri=None, timeout=None, state=None,
336325 welcome_uri = "http://localhost:{p}" .format (p = self .get_port ())
337326 abort_uri = "{loc}?error=abort" .format (loc = welcome_uri )
338327 logger .debug ("Abort by visit %s" , abort_uri )
328+
329+ # Enforce response_mode=form_post for security
330+ if auth_uri :
331+ parsed = urlparse (auth_uri )
332+ params = parse_qs (parsed .query )
333+ params ['response_mode' ] = ['form_post' ] # Enforce form_post
334+ new_query = urlencode (params , doseq = True )
335+ auth_uri = parsed ._replace (query = new_query ).geturl ()
336+
339337 self ._server .welcome_page = Template (welcome_template or "" ).safe_substitute (
340338 auth_uri = auth_uri , abort_uri = abort_uri )
341339 if auth_uri : # Now attempt to open a local browser to visit it
@@ -369,7 +367,7 @@ def _get_auth_response(self, result, auth_uri=None, timeout=None, state=None,
369367 "Authentication complete. You can return to the application. Please close this browser tab.\n \n "
370368 "For your security: Do not share the contents of this page, the address bar, or take screenshots." )
371369 self ._server .error_template = Template (error_template or
372- "Authentication failed. $error: $error_description. \n \n "
370+ "Authentication failed. $error_message \n \n "
373371 "For your security: Do not share the contents of this page, the address bar, or take screenshots." )
374372
375373 self ._server .timeout = timeout # Otherwise its handle_timeout() won't work
0 commit comments