mirror of
https://mau.dev/maunium/synapse.git
synced 2024-12-15 08:23:51 +01:00
Account validity: allow defining HTML templates to serve the us… (#5807)
Account validity: allow defining HTML templates to serve the user on account renewal attempt
This commit is contained in:
commit
8ed9e63432
8 changed files with 120 additions and 12 deletions
1
changelog.d/5807.feature
Normal file
1
changelog.d/5807.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Allow defining HTML templates to serve the user on account renewal attempt when using the account validity feature.
|
|
@ -802,6 +802,16 @@ uploads_path: "DATADIR/uploads"
|
||||||
# period: 6w
|
# period: 6w
|
||||||
# renew_at: 1w
|
# renew_at: 1w
|
||||||
# renew_email_subject: "Renew your %(app)s account"
|
# renew_email_subject: "Renew your %(app)s account"
|
||||||
|
# # Directory in which Synapse will try to find the HTML files to serve to the
|
||||||
|
# # user when trying to renew an account. Optional, defaults to
|
||||||
|
# # synapse/res/templates.
|
||||||
|
# template_dir: "res/templates"
|
||||||
|
# # HTML to be displayed to the user after they successfully renewed their
|
||||||
|
# # account. Optional.
|
||||||
|
# account_renewed_html_path: "account_renewed.html"
|
||||||
|
# # HTML to be displayed when the user tries to renew an account with an invalid
|
||||||
|
# # renewal token. Optional.
|
||||||
|
# invalid_token_html_path: "invalid_token.html"
|
||||||
|
|
||||||
# Time that a user's session remains valid for, after they log in.
|
# Time that a user's session remains valid for, after they log in.
|
||||||
#
|
#
|
||||||
|
|
|
@ -13,8 +13,11 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
from distutils.util import strtobool
|
from distutils.util import strtobool
|
||||||
|
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
from synapse.config._base import Config, ConfigError
|
from synapse.config._base import Config, ConfigError
|
||||||
from synapse.types import RoomAlias
|
from synapse.types import RoomAlias
|
||||||
from synapse.util.stringutils import random_string_with_symbols
|
from synapse.util.stringutils import random_string_with_symbols
|
||||||
|
@ -41,9 +44,37 @@ class AccountValidityConfig(Config):
|
||||||
|
|
||||||
self.startup_job_max_delta = self.period * 10.0 / 100.0
|
self.startup_job_max_delta = self.period * 10.0 / 100.0
|
||||||
|
|
||||||
if self.renew_by_email_enabled and "public_baseurl" not in synapse_config:
|
if self.renew_by_email_enabled:
|
||||||
|
if "public_baseurl" not in synapse_config:
|
||||||
raise ConfigError("Can't send renewal emails without 'public_baseurl'")
|
raise ConfigError("Can't send renewal emails without 'public_baseurl'")
|
||||||
|
|
||||||
|
template_dir = config.get("template_dir")
|
||||||
|
|
||||||
|
if not template_dir:
|
||||||
|
template_dir = pkg_resources.resource_filename("synapse", "res/templates")
|
||||||
|
|
||||||
|
if "account_renewed_html_path" in config:
|
||||||
|
file_path = os.path.join(template_dir, config["account_renewed_html_path"])
|
||||||
|
|
||||||
|
self.account_renewed_html_content = self.read_file(
|
||||||
|
file_path, "account_validity.account_renewed_html_path"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.account_renewed_html_content = (
|
||||||
|
"<html><body>Your account has been successfully renewed.</body><html>"
|
||||||
|
)
|
||||||
|
|
||||||
|
if "invalid_token_html_path" in config:
|
||||||
|
file_path = os.path.join(template_dir, config["invalid_token_html_path"])
|
||||||
|
|
||||||
|
self.invalid_token_html_content = self.read_file(
|
||||||
|
file_path, "account_validity.invalid_token_html_path"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.invalid_token_html_content = (
|
||||||
|
"<html><body>Invalid renewal token.</body><html>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RegistrationConfig(Config):
|
class RegistrationConfig(Config):
|
||||||
def read_config(self, config, **kwargs):
|
def read_config(self, config, **kwargs):
|
||||||
|
@ -145,6 +176,16 @@ class RegistrationConfig(Config):
|
||||||
# period: 6w
|
# period: 6w
|
||||||
# renew_at: 1w
|
# renew_at: 1w
|
||||||
# renew_email_subject: "Renew your %%(app)s account"
|
# renew_email_subject: "Renew your %%(app)s account"
|
||||||
|
# # Directory in which Synapse will try to find the HTML files to serve to the
|
||||||
|
# # user when trying to renew an account. Optional, defaults to
|
||||||
|
# # synapse/res/templates.
|
||||||
|
# template_dir: "res/templates"
|
||||||
|
# # HTML to be displayed to the user after they successfully renewed their
|
||||||
|
# # account. Optional.
|
||||||
|
# account_renewed_html_path: "account_renewed.html"
|
||||||
|
# # HTML to be displayed when the user tries to renew an account with an invalid
|
||||||
|
# # renewal token. Optional.
|
||||||
|
# invalid_token_html_path: "invalid_token.html"
|
||||||
|
|
||||||
# Time that a user's session remains valid for, after they log in.
|
# Time that a user's session remains valid for, after they log in.
|
||||||
#
|
#
|
||||||
|
|
|
@ -226,11 +226,19 @@ class AccountValidityHandler(object):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
renewal_token (str): Token sent with the renewal request.
|
renewal_token (str): Token sent with the renewal request.
|
||||||
|
Returns:
|
||||||
|
bool: Whether the provided token is valid.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
user_id = yield self.store.get_user_from_renewal_token(renewal_token)
|
user_id = yield self.store.get_user_from_renewal_token(renewal_token)
|
||||||
|
except StoreError:
|
||||||
|
defer.returnValue(False)
|
||||||
|
|
||||||
logger.debug("Renewing an account for user %s", user_id)
|
logger.debug("Renewing an account for user %s", user_id)
|
||||||
yield self.renew_account_for_user(user_id)
|
yield self.renew_account_for_user(user_id)
|
||||||
|
|
||||||
|
defer.returnValue(True)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def renew_account_for_user(self, user_id, expiration_ts=None, email_sent=False):
|
def renew_account_for_user(self, user_id, expiration_ts=None, email_sent=False):
|
||||||
"""Renews the account attached to a given user by pushing back the
|
"""Renews the account attached to a given user by pushing back the
|
||||||
|
|
1
synapse/res/templates/account_renewed.html
Normal file
1
synapse/res/templates/account_renewed.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<html><body>Your account has been successfully renewed.</body><html>
|
1
synapse/res/templates/invalid_token.html
Normal file
1
synapse/res/templates/invalid_token.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<html><body>Invalid renewal token.</body><html>
|
|
@ -42,6 +42,8 @@ class AccountValidityRenewServlet(RestServlet):
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
self.account_activity_handler = hs.get_account_validity_handler()
|
self.account_activity_handler = hs.get_account_validity_handler()
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
|
self.success_html = hs.config.account_validity.account_renewed_html_content
|
||||||
|
self.failure_html = hs.config.account_validity.invalid_token_html_content
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request):
|
def on_GET(self, request):
|
||||||
|
@ -49,16 +51,23 @@ class AccountValidityRenewServlet(RestServlet):
|
||||||
raise SynapseError(400, "Missing renewal token")
|
raise SynapseError(400, "Missing renewal token")
|
||||||
renewal_token = request.args[b"token"][0]
|
renewal_token = request.args[b"token"][0]
|
||||||
|
|
||||||
yield self.account_activity_handler.renew_account(renewal_token.decode("utf8"))
|
token_valid = yield self.account_activity_handler.renew_account(
|
||||||
|
renewal_token.decode("utf8")
|
||||||
request.setResponseCode(200)
|
|
||||||
request.setHeader(b"Content-Type", b"text/html; charset=utf-8")
|
|
||||||
request.setHeader(
|
|
||||||
b"Content-Length", b"%d" % (len(AccountValidityRenewServlet.SUCCESS_HTML),)
|
|
||||||
)
|
)
|
||||||
request.write(AccountValidityRenewServlet.SUCCESS_HTML)
|
|
||||||
|
if token_valid:
|
||||||
|
status_code = 200
|
||||||
|
response = self.success_html
|
||||||
|
else:
|
||||||
|
status_code = 404
|
||||||
|
response = self.failure_html
|
||||||
|
|
||||||
|
request.setResponseCode(status_code)
|
||||||
|
request.setHeader(b"Content-Type", b"text/html; charset=utf-8")
|
||||||
|
request.setHeader(b"Content-Length", b"%d" % (len(response),))
|
||||||
|
request.write(response.encode("utf8"))
|
||||||
finish_request(request)
|
finish_request(request)
|
||||||
return None
|
defer.returnValue(None)
|
||||||
|
|
||||||
|
|
||||||
class AccountValiditySendMailServlet(RestServlet):
|
class AccountValiditySendMailServlet(RestServlet):
|
||||||
|
@ -87,7 +96,7 @@ class AccountValiditySendMailServlet(RestServlet):
|
||||||
user_id = requester.user.to_string()
|
user_id = requester.user.to_string()
|
||||||
yield self.account_activity_handler.send_renewal_email_to_user(user_id)
|
yield self.account_activity_handler.send_renewal_email_to_user(user_id)
|
||||||
|
|
||||||
return (200, {})
|
defer.returnValue((200, {}))
|
||||||
|
|
||||||
|
|
||||||
def register_servlets(hs, http_server):
|
def register_servlets(hs, http_server):
|
||||||
|
|
|
@ -323,6 +323,8 @@ class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
|
||||||
"renew_at": 172800000, # Time in ms for 2 days
|
"renew_at": 172800000, # Time in ms for 2 days
|
||||||
"renew_by_email_enabled": True,
|
"renew_by_email_enabled": True,
|
||||||
"renew_email_subject": "Renew your account",
|
"renew_email_subject": "Renew your account",
|
||||||
|
"account_renewed_html_path": "account_renewed.html",
|
||||||
|
"invalid_token_html_path": "invalid_token.html",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Email config.
|
# Email config.
|
||||||
|
@ -373,6 +375,19 @@ class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
|
||||||
self.render(request)
|
self.render(request)
|
||||||
self.assertEquals(channel.result["code"], b"200", channel.result)
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
||||||
|
|
||||||
|
# Check that we're getting HTML back.
|
||||||
|
content_type = None
|
||||||
|
for header in channel.result.get("headers", []):
|
||||||
|
if header[0] == b"Content-Type":
|
||||||
|
content_type = header[1]
|
||||||
|
self.assertEqual(content_type, b"text/html; charset=utf-8", channel.result)
|
||||||
|
|
||||||
|
# Check that the HTML we're getting is the one we expect on a successful renewal.
|
||||||
|
expected_html = self.hs.config.account_validity.account_renewed_html_content
|
||||||
|
self.assertEqual(
|
||||||
|
channel.result["body"], expected_html.encode("utf8"), channel.result
|
||||||
|
)
|
||||||
|
|
||||||
# Move 3 days forward. If the renewal failed, every authed request with
|
# Move 3 days forward. If the renewal failed, every authed request with
|
||||||
# our access token should be denied from now, otherwise they should
|
# our access token should be denied from now, otherwise they should
|
||||||
# succeed.
|
# succeed.
|
||||||
|
@ -381,6 +396,28 @@ class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
|
||||||
self.render(request)
|
self.render(request)
|
||||||
self.assertEquals(channel.result["code"], b"200", channel.result)
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
||||||
|
|
||||||
|
def test_renewal_invalid_token(self):
|
||||||
|
# Hit the renewal endpoint with an invalid token and check that it behaves as
|
||||||
|
# expected, i.e. that it responds with 404 Not Found and the correct HTML.
|
||||||
|
url = "/_matrix/client/unstable/account_validity/renew?token=123"
|
||||||
|
request, channel = self.make_request(b"GET", url)
|
||||||
|
self.render(request)
|
||||||
|
self.assertEquals(channel.result["code"], b"404", channel.result)
|
||||||
|
|
||||||
|
# Check that we're getting HTML back.
|
||||||
|
content_type = None
|
||||||
|
for header in channel.result.get("headers", []):
|
||||||
|
if header[0] == b"Content-Type":
|
||||||
|
content_type = header[1]
|
||||||
|
self.assertEqual(content_type, b"text/html; charset=utf-8", channel.result)
|
||||||
|
|
||||||
|
# Check that the HTML we're getting is the one we expect when using an
|
||||||
|
# invalid/unknown token.
|
||||||
|
expected_html = self.hs.config.account_validity.invalid_token_html_content
|
||||||
|
self.assertEqual(
|
||||||
|
channel.result["body"], expected_html.encode("utf8"), channel.result
|
||||||
|
)
|
||||||
|
|
||||||
def test_manual_email_send(self):
|
def test_manual_email_send(self):
|
||||||
self.email_attempts = []
|
self.email_attempts = []
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue