Skip to content

Commit

Permalink
Improve styling and wording of SSO redirect confirm template (matrix-…
Browse files Browse the repository at this point in the history
  • Loading branch information
richvdh authored Feb 1, 2021
1 parent 9c715a5 commit 8aed29d
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 30 deletions.
1 change: 1 addition & 0 deletions changelog.d/9272.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve the user experience of setting up an account via single-sign on.
14 changes: 13 additions & 1 deletion docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1971,7 +1971,8 @@ sso:
# * HTML page for a confirmation step before redirecting back to the client
# with the login token: 'sso_redirect_confirm.html'.
#
# When rendering, this template is given three variables:
# When rendering, this template is given the following variables:
#
# * redirect_url: the URL the user is about to be redirected to. Needs
# manual escaping (see
# https://jinja.palletsprojects.com/en/2.11.x/templates/#html-escaping).
Expand All @@ -1984,6 +1985,17 @@ sso:
#
# * server_name: the homeserver's name.
#
# * new_user: a boolean indicating whether this is the user's first time
# logging in.
#
# * user_id: the user's matrix ID.
#
# * user_profile.avatar_url: an MXC URI for the user's avatar, if any.
# None if the user has not set an avatar.
#
# * user_profile.display_name: the user's display name. None if the user
# has not set a display name.
#
# * HTML page which notifies the user that they are authenticating to confirm
# an operation on their account during the user interactive authentication
# process: 'sso_auth_confirm.html'.
Expand Down
14 changes: 13 additions & 1 deletion synapse/config/sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ def generate_config_section(self, **kwargs):
# * HTML page for a confirmation step before redirecting back to the client
# with the login token: 'sso_redirect_confirm.html'.
#
# When rendering, this template is given three variables:
# When rendering, this template is given the following variables:
#
# * redirect_url: the URL the user is about to be redirected to. Needs
# manual escaping (see
# https://jinja.palletsprojects.com/en/2.11.x/templates/#html-escaping).
Expand All @@ -140,6 +141,17 @@ def generate_config_section(self, **kwargs):
#
# * server_name: the homeserver's name.
#
# * new_user: a boolean indicating whether this is the user's first time
# logging in.
#
# * user_id: the user's matrix ID.
#
# * user_profile.avatar_url: an MXC URI for the user's avatar, if any.
# None if the user has not set an avatar.
#
# * user_profile.display_name: the user's display name. None if the user
# has not set a display name.
#
# * HTML page which notifies the user that they are authenticating to confirm
# an operation on their account during the user interactive authentication
# process: 'sso_auth_confirm.html'.
Expand Down
24 changes: 23 additions & 1 deletion synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
from synapse.logging.context import defer_to_thread
from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.module_api import ModuleApi
from synapse.storage.roommember import ProfileInfo
from synapse.types import JsonDict, Requester, UserID
from synapse.util import stringutils as stringutils
from synapse.util.async_helpers import maybe_awaitable
Expand Down Expand Up @@ -1396,6 +1397,7 @@ async def complete_sso_login(
request: Request,
client_redirect_url: str,
extra_attributes: Optional[JsonDict] = None,
new_user: bool = False,
):
"""Having figured out a mxid for this user, complete the HTTP request
Expand All @@ -1406,6 +1408,8 @@ async def complete_sso_login(
process.
extra_attributes: Extra attributes which will be passed to the client
during successful login. Must be JSON serializable.
new_user: True if we should use wording appropriate to a user who has just
registered.
"""
# If the account has been deactivated, do not proceed with the login
# flow.
Expand All @@ -1414,8 +1418,17 @@ async def complete_sso_login(
respond_with_html(request, 403, self._sso_account_deactivated_template)
return

profile = await self.store.get_profileinfo(
UserID.from_string(registered_user_id).localpart
)

self._complete_sso_login(
registered_user_id, request, client_redirect_url, extra_attributes
registered_user_id,
request,
client_redirect_url,
extra_attributes,
new_user=new_user,
user_profile_data=profile,
)

def _complete_sso_login(
Expand All @@ -1424,12 +1437,18 @@ def _complete_sso_login(
request: Request,
client_redirect_url: str,
extra_attributes: Optional[JsonDict] = None,
new_user: bool = False,
user_profile_data: Optional[ProfileInfo] = None,
):
"""
The synchronous portion of complete_sso_login.
This exists purely for backwards compatibility of synapse.module_api.ModuleApi.
"""

if user_profile_data is None:
user_profile_data = ProfileInfo(None, None)

# Store any extra attributes which will be passed in the login response.
# Note that this is per-user so it may overwrite a previous value, this
# is considered OK since the newest SSO attributes should be most valid.
Expand Down Expand Up @@ -1467,6 +1486,9 @@ def _complete_sso_login(
display_url=redirect_url_no_params,
redirect_url=redirect_url,
server_name=self._server_name,
new_user=new_user,
user_id=registered_user_id,
user_profile=user_profile_data,
)
respond_with_html(request, 200, html)

Expand Down
10 changes: 9 additions & 1 deletion synapse/handlers/sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ async def complete_sso_login_request(
to an additional page. (e.g. to prompt for more information)
"""
new_user = False

# grab a lock while we try to find a mapping for this user. This seems...
# optimistic, especially for implementations that end up redirecting to
# interstitial pages.
Expand Down Expand Up @@ -431,9 +433,14 @@ async def complete_sso_login_request(
get_request_user_agent(request),
request.getClientIP(),
)
new_user = True

await self._auth_handler.complete_sso_login(
user_id, request, client_redirect_url, extra_login_attributes
user_id,
request,
client_redirect_url,
extra_login_attributes,
new_user=new_user,
)

async def _call_attribute_mapper(
Expand Down Expand Up @@ -778,6 +785,7 @@ async def register_sso_user(self, request: Request, session_id: str) -> None:
request,
session.client_redirect_url,
session.extra_login_attributes,
new_user=True,
)

def _expire_old_sessions(self):
Expand Down
10 changes: 8 additions & 2 deletions synapse/module_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,11 @@ def complete_sso_login(
)

async def complete_sso_login_async(
self, registered_user_id: str, request: SynapseRequest, client_redirect_url: str
self,
registered_user_id: str,
request: SynapseRequest,
client_redirect_url: str,
new_user: bool = False,
):
"""Complete a SSO login by redirecting the user to a page to confirm whether they
want their access token sent to `client_redirect_url`, or redirect them to that
Expand All @@ -291,9 +295,11 @@ async def complete_sso_login_async(
request: The request to respond to.
client_redirect_url: The URL to which to offer to redirect the user (or to
redirect them directly if whitelisted).
new_user: set to true to use wording for the consent appropriate to a user
who has just registered.
"""
await self._auth_handler.complete_sso_login(
registered_user_id, request, client_redirect_url,
registered_user_id, request, client_redirect_url, new_user=new_user
)

@defer.inlineCallbacks
Expand Down
83 changes: 83 additions & 0 deletions synapse/res/templates/sso.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
body {
font-family: "Inter", "Helvetica", "Arial", sans-serif;
font-size: 14px;
color: #17191C;
}

header {
max-width: 480px;
width: 100%;
margin: 24px auto;
text-align: center;
}

header p {
color: #737D8C;
line-height: 24px;
}

h1 {
font-size: 24px;
}

h2 {
font-size: 14px;
}

h2 img {
vertical-align: middle;
margin-right: 8px;
width: 24px;
height: 24px;
}

label {
cursor: pointer;
}

main {
max-width: 360px;
width: 100%;
margin: 24px auto;
}

.primary-button {
border: none;
text-decoration: none;
padding: 12px;
color: white;
background-color: #418DED;
font-weight: bold;
display: block;
border-radius: 12px;
width: 100%;
margin: 16px 0;
cursor: pointer;
text-align: center;
}

.profile {
display: flex;
justify-content: center;
margin: 24px 0;
}

.profile .avatar {
width: 36px;
height: 36px;
border-radius: 100%;
display: block;
margin-right: 8px;
}

.profile .display-name {
font-weight: bold;
margin-bottom: 4px;
}
.profile .user-id {
color: #737D8C;
}

.profile .display-name, .profile .user-id {
line-height: 18px;
}
34 changes: 28 additions & 6 deletions synapse/res/templates/sso_redirect_confirm.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,34 @@
<head>
<meta charset="UTF-8">
<title>SSO redirect confirmation</title>
<meta name="viewport" content="width=device-width, user-scalable=no">
<style type="text/css">
{% include "sso.css" without context %}
</style>
</head>
<body>
<p>The application at <span style="font-weight:bold">{{ display_url | e }}</span> is requesting full access to your <span style="font-weight:bold">{{ server_name }}</span> Matrix account.</p>
<p>If you don't recognise this address, you should ignore this and close this tab.</p>
<p>
<a href="{{ redirect_url | e }}">I trust this address</a>
</p>
<header>
{% if new_user %}
<h1>Your account is now ready</h1>
<p>You've made your account on {{ server_name | e }}.</p>
{% else %}
<h1>Log in</h1>
{% endif %}
<p>Continue to confirm you trust <strong>{{ display_url | e }}</strong>.</p>
</header>
<main>
{% if user_profile.avatar_url %}
<div class="profile">
<img src="{{ user_profile.avatar_url | mxc_to_http(64, 64) }}" class="avatar" />
<div class="profile-details">
{% if user_profile.display_name %}
<div class="display-name">{{ user_profile.display_name | e }}</div>
{% endif %}
<div class="user-id">{{ user_id | e }}</div>
</div>
</div>
{% endif %}
<a href="{{ redirect_url | e }}" class="primary-button">Continue</a>
</main>
</body>
</html>
</html>
8 changes: 4 additions & 4 deletions tests/handlers/test_cas.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def test_map_cas_user_to_user(self):

# check that the auth handler got called as expected
auth_handler.complete_sso_login.assert_called_once_with(
"@test_user:test", request, "redirect_uri", None
"@test_user:test", request, "redirect_uri", None, new_user=True
)

def test_map_cas_user_to_existing_user(self):
Expand All @@ -85,7 +85,7 @@ def test_map_cas_user_to_existing_user(self):

# check that the auth handler got called as expected
auth_handler.complete_sso_login.assert_called_once_with(
"@test_user:test", request, "redirect_uri", None
"@test_user:test", request, "redirect_uri", None, new_user=False
)

# Subsequent calls should map to the same mxid.
Expand All @@ -94,7 +94,7 @@ def test_map_cas_user_to_existing_user(self):
self.handler._handle_cas_response(request, cas_response, "redirect_uri", "")
)
auth_handler.complete_sso_login.assert_called_once_with(
"@test_user:test", request, "redirect_uri", None
"@test_user:test", request, "redirect_uri", None, new_user=False
)

def test_map_cas_user_to_invalid_localpart(self):
Expand All @@ -112,7 +112,7 @@ def test_map_cas_user_to_invalid_localpart(self):

# check that the auth handler got called as expected
auth_handler.complete_sso_login.assert_called_once_with(
"@f=c3=b6=c3=b6:test", request, "redirect_uri", None
"@f=c3=b6=c3=b6:test", request, "redirect_uri", None, new_user=True
)


Expand Down
Loading

0 comments on commit 8aed29d

Please sign in to comment.