Display an error page during failure of fallback UIA. (#10561)

This commit is contained in:
Callum Brown 2021-08-18 13:13:35 +01:00 committed by GitHub
parent 964f29cb6f
commit 6e613a10d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 67 additions and 30 deletions

1
changelog.d/10561.bugfix Normal file
View file

@ -0,0 +1 @@
Display an error on User-Interactive Authentication fallback pages when authentication fails. Contributed by Callum Brown.

View file

@ -125,6 +125,14 @@ environment variable.
See [using a forward proxy with Synapse documentation](setup/forward_proxy.md) for See [using a forward proxy with Synapse documentation](setup/forward_proxy.md) for
details. details.
## User-interactive authentication fallback templates can now display errors
This may affect you if you make use of custom HTML templates for the
[reCAPTCHA](../synapse/res/templates/recaptcha.html) or
[terms](../synapse/res/templates/terms.html) fallback pages.
The template is now provided an `error` variable if the authentication
process failed. See the default templates linked above for an example.
# Upgrading to v1.39.0 # Upgrading to v1.39.0

View file

@ -627,23 +627,28 @@ class AuthHandler(BaseHandler):
async def add_oob_auth( async def add_oob_auth(
self, stagetype: str, authdict: Dict[str, Any], clientip: str self, stagetype: str, authdict: Dict[str, Any], clientip: str
) -> bool: ) -> None:
""" """
Adds the result of out-of-band authentication into an existing auth Adds the result of out-of-band authentication into an existing auth
session. Currently used for adding the result of fallback auth. session. Currently used for adding the result of fallback auth.
Raises:
LoginError if the stagetype is unknown or the session is missing.
LoginError is raised by check_auth if authentication fails.
""" """
if stagetype not in self.checkers: if stagetype not in self.checkers:
raise LoginError(400, "", Codes.MISSING_PARAM) raise LoginError(
400, f"Unknown UIA stage type: {stagetype}", Codes.INVALID_PARAM
)
if "session" not in authdict: if "session" not in authdict:
raise LoginError(400, "", Codes.MISSING_PARAM) raise LoginError(400, "Missing session ID", Codes.MISSING_PARAM)
# If authentication fails a LoginError is raised. Otherwise, store
# the successful result.
result = await self.checkers[stagetype].check_auth(authdict, clientip) result = await self.checkers[stagetype].check_auth(authdict, clientip)
if result:
await self.store.mark_ui_auth_stage_complete( await self.store.mark_ui_auth_stage_complete(
authdict["session"], stagetype, result authdict["session"], stagetype, result
) )
return True
return False
def get_session_id(self, clientdict: Dict[str, Any]) -> Optional[str]: def get_session_id(self, clientdict: Dict[str, Any]) -> Optional[str]:
""" """

View file

@ -49,7 +49,7 @@ class UserInteractiveAuthChecker:
clientip: The IP address of the client. clientip: The IP address of the client.
Raises: Raises:
SynapseError if authentication failed LoginError if authentication failed.
Returns: Returns:
The result of authentication (to pass back to the client?) The result of authentication (to pass back to the client?)
@ -131,7 +131,9 @@ class RecaptchaAuthChecker(UserInteractiveAuthChecker):
) )
if resp_body["success"]: if resp_body["success"]:
return True return True
raise LoginError(401, "", errcode=Codes.UNAUTHORIZED) raise LoginError(
401, "Captcha authentication failed", errcode=Codes.UNAUTHORIZED
)
class _BaseThreepidAuthChecker: class _BaseThreepidAuthChecker:
@ -191,7 +193,9 @@ class _BaseThreepidAuthChecker:
raise AssertionError("Unrecognized threepid medium: %s" % (medium,)) raise AssertionError("Unrecognized threepid medium: %s" % (medium,))
if not threepid: if not threepid:
raise LoginError(401, "", errcode=Codes.UNAUTHORIZED) raise LoginError(
401, "Unable to get validated threepid", errcode=Codes.UNAUTHORIZED
)
if threepid["medium"] != medium: if threepid["medium"] != medium:
raise LoginError( raise LoginError(

View file

@ -16,6 +16,9 @@ function captchaDone() {
<body> <body>
<form id="registrationForm" method="post" action="{{ myurl }}"> <form id="registrationForm" method="post" action="{{ myurl }}">
<div> <div>
{% if error is defined %}
<p class="error"><strong>Error: {{ error }}</strong></p>
{% endif %}
<p> <p>
Hello! We need to prevent computer programs and other automated Hello! We need to prevent computer programs and other automated
things from creating accounts on this server. things from creating accounts on this server.

View file

@ -8,6 +8,9 @@
<body> <body>
<form id="registrationForm" method="post" action="{{ myurl }}"> <form id="registrationForm" method="post" action="{{ myurl }}">
<div> <div>
{% if error is defined %}
<p class="error"><strong>Error: {{ error }}</strong></p>
{% endif %}
<p> <p>
Please click the button below if you agree to the Please click the button below if you agree to the
<a href="{{ terms_url }}">privacy policy of this homeserver.</a> <a href="{{ terms_url }}">privacy policy of this homeserver.</a>

View file

@ -16,7 +16,7 @@ import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from synapse.api.constants import LoginType from synapse.api.constants import LoginType
from synapse.api.errors import SynapseError from synapse.api.errors import LoginError, SynapseError
from synapse.api.urls import CLIENT_API_PREFIX from synapse.api.urls import CLIENT_API_PREFIX
from synapse.http.server import respond_with_html from synapse.http.server import respond_with_html
from synapse.http.servlet import RestServlet, parse_string from synapse.http.servlet import RestServlet, parse_string
@ -95,29 +95,32 @@ class AuthRestServlet(RestServlet):
authdict = {"response": response, "session": session} authdict = {"response": response, "session": session}
success = await self.auth_handler.add_oob_auth( try:
await self.auth_handler.add_oob_auth(
LoginType.RECAPTCHA, authdict, request.getClientIP() LoginType.RECAPTCHA, authdict, request.getClientIP()
) )
except LoginError as e:
if success: # Authentication failed, let user try again
html = self.success_template.render()
else:
html = self.recaptcha_template.render( html = self.recaptcha_template.render(
session=session, session=session,
myurl="%s/r0/auth/%s/fallback/web" myurl="%s/r0/auth/%s/fallback/web"
% (CLIENT_API_PREFIX, LoginType.RECAPTCHA), % (CLIENT_API_PREFIX, LoginType.RECAPTCHA),
sitekey=self.hs.config.recaptcha_public_key, sitekey=self.hs.config.recaptcha_public_key,
error=e.msg,
) )
else:
# No LoginError was raised, so authentication was successful
html = self.success_template.render()
elif stagetype == LoginType.TERMS: elif stagetype == LoginType.TERMS:
authdict = {"session": session} authdict = {"session": session}
success = await self.auth_handler.add_oob_auth( try:
await self.auth_handler.add_oob_auth(
LoginType.TERMS, authdict, request.getClientIP() LoginType.TERMS, authdict, request.getClientIP()
) )
except LoginError as e:
if success: # Authentication failed, let user try again
html = self.success_template.render()
else:
html = self.terms_template.render( html = self.terms_template.render(
session=session, session=session,
terms_url="%s_matrix/consent?v=%s" terms_url="%s_matrix/consent?v=%s"
@ -127,10 +130,16 @@ class AuthRestServlet(RestServlet):
), ),
myurl="%s/r0/auth/%s/fallback/web" myurl="%s/r0/auth/%s/fallback/web"
% (CLIENT_API_PREFIX, LoginType.TERMS), % (CLIENT_API_PREFIX, LoginType.TERMS),
error=e.msg,
) )
else:
# No LoginError was raised, so authentication was successful
html = self.success_template.render()
elif stagetype == LoginType.SSO: elif stagetype == LoginType.SSO:
# The SSO fallback workflow should not post here, # The SSO fallback workflow should not post here,
raise SynapseError(404, "Fallback SSO auth does not support POST requests.") raise SynapseError(404, "Fallback SSO auth does not support POST requests.")
else: else:
raise SynapseError(404, "Unknown auth stage type") raise SynapseError(404, "Unknown auth stage type")

View file

@ -58,3 +58,7 @@ textarea, input {
background-color: #f8f8f8; background-color: #f8f8f8;
border: 1px #ccc solid; border: 1px #ccc solid;
} }
.error {
color: red;
}