Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 8aed29d

Browse files
authored
Improve styling and wording of SSO redirect confirm template (#9272)
1 parent 9c715a5 commit 8aed29d

File tree

11 files changed

+200
-30
lines changed

11 files changed

+200
-30
lines changed

changelog.d/9272.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve the user experience of setting up an account via single-sign on.

docs/sample_config.yaml

+13-1
Original file line numberDiff line numberDiff line change
@@ -1971,7 +1971,8 @@ sso:
19711971
# * HTML page for a confirmation step before redirecting back to the client
19721972
# with the login token: 'sso_redirect_confirm.html'.
19731973
#
1974-
# When rendering, this template is given three variables:
1974+
# When rendering, this template is given the following variables:
1975+
#
19751976
# * redirect_url: the URL the user is about to be redirected to. Needs
19761977
# manual escaping (see
19771978
# https://jinja.palletsprojects.com/en/2.11.x/templates/#html-escaping).
@@ -1984,6 +1985,17 @@ sso:
19841985
#
19851986
# * server_name: the homeserver's name.
19861987
#
1988+
# * new_user: a boolean indicating whether this is the user's first time
1989+
# logging in.
1990+
#
1991+
# * user_id: the user's matrix ID.
1992+
#
1993+
# * user_profile.avatar_url: an MXC URI for the user's avatar, if any.
1994+
# None if the user has not set an avatar.
1995+
#
1996+
# * user_profile.display_name: the user's display name. None if the user
1997+
# has not set a display name.
1998+
#
19871999
# * HTML page which notifies the user that they are authenticating to confirm
19882000
# an operation on their account during the user interactive authentication
19892001
# process: 'sso_auth_confirm.html'.

synapse/config/sso.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ def generate_config_section(self, **kwargs):
127127
# * HTML page for a confirmation step before redirecting back to the client
128128
# with the login token: 'sso_redirect_confirm.html'.
129129
#
130-
# When rendering, this template is given three variables:
130+
# When rendering, this template is given the following variables:
131+
#
131132
# * redirect_url: the URL the user is about to be redirected to. Needs
132133
# manual escaping (see
133134
# https://jinja.palletsprojects.com/en/2.11.x/templates/#html-escaping).
@@ -140,6 +141,17 @@ def generate_config_section(self, **kwargs):
140141
#
141142
# * server_name: the homeserver's name.
142143
#
144+
# * new_user: a boolean indicating whether this is the user's first time
145+
# logging in.
146+
#
147+
# * user_id: the user's matrix ID.
148+
#
149+
# * user_profile.avatar_url: an MXC URI for the user's avatar, if any.
150+
# None if the user has not set an avatar.
151+
#
152+
# * user_profile.display_name: the user's display name. None if the user
153+
# has not set a display name.
154+
#
143155
# * HTML page which notifies the user that they are authenticating to confirm
144156
# an operation on their account during the user interactive authentication
145157
# process: 'sso_auth_confirm.html'.

synapse/handlers/auth.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
from synapse.logging.context import defer_to_thread
6262
from synapse.metrics.background_process_metrics import run_as_background_process
6363
from synapse.module_api import ModuleApi
64+
from synapse.storage.roommember import ProfileInfo
6465
from synapse.types import JsonDict, Requester, UserID
6566
from synapse.util import stringutils as stringutils
6667
from synapse.util.async_helpers import maybe_awaitable
@@ -1396,6 +1397,7 @@ async def complete_sso_login(
13961397
request: Request,
13971398
client_redirect_url: str,
13981399
extra_attributes: Optional[JsonDict] = None,
1400+
new_user: bool = False,
13991401
):
14001402
"""Having figured out a mxid for this user, complete the HTTP request
14011403
@@ -1406,6 +1408,8 @@ async def complete_sso_login(
14061408
process.
14071409
extra_attributes: Extra attributes which will be passed to the client
14081410
during successful login. Must be JSON serializable.
1411+
new_user: True if we should use wording appropriate to a user who has just
1412+
registered.
14091413
"""
14101414
# If the account has been deactivated, do not proceed with the login
14111415
# flow.
@@ -1414,8 +1418,17 @@ async def complete_sso_login(
14141418
respond_with_html(request, 403, self._sso_account_deactivated_template)
14151419
return
14161420

1421+
profile = await self.store.get_profileinfo(
1422+
UserID.from_string(registered_user_id).localpart
1423+
)
1424+
14171425
self._complete_sso_login(
1418-
registered_user_id, request, client_redirect_url, extra_attributes
1426+
registered_user_id,
1427+
request,
1428+
client_redirect_url,
1429+
extra_attributes,
1430+
new_user=new_user,
1431+
user_profile_data=profile,
14191432
)
14201433

14211434
def _complete_sso_login(
@@ -1424,12 +1437,18 @@ def _complete_sso_login(
14241437
request: Request,
14251438
client_redirect_url: str,
14261439
extra_attributes: Optional[JsonDict] = None,
1440+
new_user: bool = False,
1441+
user_profile_data: Optional[ProfileInfo] = None,
14271442
):
14281443
"""
14291444
The synchronous portion of complete_sso_login.
14301445
14311446
This exists purely for backwards compatibility of synapse.module_api.ModuleApi.
14321447
"""
1448+
1449+
if user_profile_data is None:
1450+
user_profile_data = ProfileInfo(None, None)
1451+
14331452
# Store any extra attributes which will be passed in the login response.
14341453
# Note that this is per-user so it may overwrite a previous value, this
14351454
# is considered OK since the newest SSO attributes should be most valid.
@@ -1467,6 +1486,9 @@ def _complete_sso_login(
14671486
display_url=redirect_url_no_params,
14681487
redirect_url=redirect_url,
14691488
server_name=self._server_name,
1489+
new_user=new_user,
1490+
user_id=registered_user_id,
1491+
user_profile=user_profile_data,
14701492
)
14711493
respond_with_html(request, 200, html)
14721494

synapse/handlers/sso.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,8 @@ async def complete_sso_login_request(
391391
to an additional page. (e.g. to prompt for more information)
392392
393393
"""
394+
new_user = False
395+
394396
# grab a lock while we try to find a mapping for this user. This seems...
395397
# optimistic, especially for implementations that end up redirecting to
396398
# interstitial pages.
@@ -431,9 +433,14 @@ async def complete_sso_login_request(
431433
get_request_user_agent(request),
432434
request.getClientIP(),
433435
)
436+
new_user = True
434437

435438
await self._auth_handler.complete_sso_login(
436-
user_id, request, client_redirect_url, extra_login_attributes
439+
user_id,
440+
request,
441+
client_redirect_url,
442+
extra_login_attributes,
443+
new_user=new_user,
437444
)
438445

439446
async def _call_attribute_mapper(
@@ -778,6 +785,7 @@ async def register_sso_user(self, request: Request, session_id: str) -> None:
778785
request,
779786
session.client_redirect_url,
780787
session.extra_login_attributes,
788+
new_user=True,
781789
)
782790

783791
def _expire_old_sessions(self):

synapse/module_api/__init__.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,11 @@ def complete_sso_login(
279279
)
280280

281281
async def complete_sso_login_async(
282-
self, registered_user_id: str, request: SynapseRequest, client_redirect_url: str
282+
self,
283+
registered_user_id: str,
284+
request: SynapseRequest,
285+
client_redirect_url: str,
286+
new_user: bool = False,
283287
):
284288
"""Complete a SSO login by redirecting the user to a page to confirm whether they
285289
want their access token sent to `client_redirect_url`, or redirect them to that
@@ -291,9 +295,11 @@ async def complete_sso_login_async(
291295
request: The request to respond to.
292296
client_redirect_url: The URL to which to offer to redirect the user (or to
293297
redirect them directly if whitelisted).
298+
new_user: set to true to use wording for the consent appropriate to a user
299+
who has just registered.
294300
"""
295301
await self._auth_handler.complete_sso_login(
296-
registered_user_id, request, client_redirect_url,
302+
registered_user_id, request, client_redirect_url, new_user=new_user
297303
)
298304

299305
@defer.inlineCallbacks

synapse/res/templates/sso.css

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
body {
2+
font-family: "Inter", "Helvetica", "Arial", sans-serif;
3+
font-size: 14px;
4+
color: #17191C;
5+
}
6+
7+
header {
8+
max-width: 480px;
9+
width: 100%;
10+
margin: 24px auto;
11+
text-align: center;
12+
}
13+
14+
header p {
15+
color: #737D8C;
16+
line-height: 24px;
17+
}
18+
19+
h1 {
20+
font-size: 24px;
21+
}
22+
23+
h2 {
24+
font-size: 14px;
25+
}
26+
27+
h2 img {
28+
vertical-align: middle;
29+
margin-right: 8px;
30+
width: 24px;
31+
height: 24px;
32+
}
33+
34+
label {
35+
cursor: pointer;
36+
}
37+
38+
main {
39+
max-width: 360px;
40+
width: 100%;
41+
margin: 24px auto;
42+
}
43+
44+
.primary-button {
45+
border: none;
46+
text-decoration: none;
47+
padding: 12px;
48+
color: white;
49+
background-color: #418DED;
50+
font-weight: bold;
51+
display: block;
52+
border-radius: 12px;
53+
width: 100%;
54+
margin: 16px 0;
55+
cursor: pointer;
56+
text-align: center;
57+
}
58+
59+
.profile {
60+
display: flex;
61+
justify-content: center;
62+
margin: 24px 0;
63+
}
64+
65+
.profile .avatar {
66+
width: 36px;
67+
height: 36px;
68+
border-radius: 100%;
69+
display: block;
70+
margin-right: 8px;
71+
}
72+
73+
.profile .display-name {
74+
font-weight: bold;
75+
margin-bottom: 4px;
76+
}
77+
.profile .user-id {
78+
color: #737D8C;
79+
}
80+
81+
.profile .display-name, .profile .user-id {
82+
line-height: 18px;
83+
}

synapse/res/templates/sso_redirect_confirm.html

+28-6
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,34 @@
33
<head>
44
<meta charset="UTF-8">
55
<title>SSO redirect confirmation</title>
6+
<meta name="viewport" content="width=device-width, user-scalable=no">
7+
<style type="text/css">
8+
{% include "sso.css" without context %}
9+
</style>
610
</head>
711
<body>
8-
<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>
9-
<p>If you don't recognise this address, you should ignore this and close this tab.</p>
10-
<p>
11-
<a href="{{ redirect_url | e }}">I trust this address</a>
12-
</p>
12+
<header>
13+
{% if new_user %}
14+
<h1>Your account is now ready</h1>
15+
<p>You've made your account on {{ server_name | e }}.</p>
16+
{% else %}
17+
<h1>Log in</h1>
18+
{% endif %}
19+
<p>Continue to confirm you trust <strong>{{ display_url | e }}</strong>.</p>
20+
</header>
21+
<main>
22+
{% if user_profile.avatar_url %}
23+
<div class="profile">
24+
<img src="{{ user_profile.avatar_url | mxc_to_http(64, 64) }}" class="avatar" />
25+
<div class="profile-details">
26+
{% if user_profile.display_name %}
27+
<div class="display-name">{{ user_profile.display_name | e }}</div>
28+
{% endif %}
29+
<div class="user-id">{{ user_id | e }}</div>
30+
</div>
31+
</div>
32+
{% endif %}
33+
<a href="{{ redirect_url | e }}" class="primary-button">Continue</a>
34+
</main>
1335
</body>
14-
</html>
36+
</html>

tests/handlers/test_cas.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def test_map_cas_user_to_user(self):
6262

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

6868
def test_map_cas_user_to_existing_user(self):
@@ -85,7 +85,7 @@ def test_map_cas_user_to_existing_user(self):
8585

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

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

100100
def test_map_cas_user_to_invalid_localpart(self):
@@ -112,7 +112,7 @@ def test_map_cas_user_to_invalid_localpart(self):
112112

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

118118

0 commit comments

Comments
 (0)