diff --git a/changelog.d/9275.feature b/changelog.d/9275.feature
new file mode 100644
index 000000000..c21b197ca
--- /dev/null
+++ b/changelog.d/9275.feature
@@ -0,0 +1 @@
+Improve the user experience of setting up an account via single-sign on.
diff --git a/docs/sample_config.yaml b/docs/sample_config.yaml
index 05506a778..a6fbcc608 100644
--- a/docs/sample_config.yaml
+++ b/docs/sample_config.yaml
@@ -1801,7 +1801,8 @@ saml2_config:
#
# localpart_template: Jinja2 template for the localpart of the MXID.
# If this is not set, the user will be prompted to choose their
-# own username.
+# own username (see 'sso_auth_account_details.html' in the 'sso'
+# section of this file).
#
# display_name_template: Jinja2 template for the display name to set
# on first login. If unset, no displayname will be set.
@@ -1968,6 +1969,35 @@ sso:
#
# * idp: the 'idp_id' of the chosen IDP.
#
+ # * HTML page to prompt new users to enter a userid and confirm other
+ # details: 'sso_auth_account_details.html'. This is only shown if the
+ # SSO implementation (with any user_mapping_provider) does not return
+ # a localpart.
+ #
+ # When rendering, this template is given the following variables:
+ #
+ # * server_name: the homeserver's name.
+ #
+ # * idp: details of the SSO Identity Provider that the user logged in
+ # with: an object with the following attributes:
+ #
+ # * idp_id: unique identifier for the IdP
+ # * idp_name: user-facing name for the IdP
+ # * idp_icon: if specified in the IdP config, an MXC URI for an icon
+ # for the IdP
+ # * idp_brand: if specified in the IdP config, a textual identifier
+ # for the brand of the IdP
+ #
+ # * user_attributes: an object containing details about the user that
+ # we received from the IdP. May have the following attributes:
+ #
+ # * display_name: the user's display_name
+ # * emails: a list of email addresses
+ #
+ # The template should render a form which submits the following fields:
+ #
+ # * username: the localpart of the user's chosen user id
+ #
# * HTML page for a confirmation step before redirecting back to the client
# with the login token: 'sso_redirect_confirm.html'.
#
diff --git a/synapse/config/_base.py b/synapse/config/_base.py
index 94144efc8..35e5594b7 100644
--- a/synapse/config/_base.py
+++ b/synapse/config/_base.py
@@ -18,18 +18,18 @@
import argparse
import errno
import os
-import time
-import urllib.parse
from collections import OrderedDict
from hashlib import sha256
from textwrap import dedent
-from typing import Any, Callable, Iterable, List, MutableMapping, Optional
+from typing import Any, Iterable, List, MutableMapping, Optional
import attr
import jinja2
import pkg_resources
import yaml
+from synapse.util.templates import _create_mxc_to_http_filter, _format_ts_filter
+
class ConfigError(Exception):
"""Represents a problem parsing the configuration
@@ -248,6 +248,7 @@ class Config:
# Search the custom template directory as well
search_directories.insert(0, custom_template_directory)
+ # TODO: switch to synapse.util.templates.build_jinja_env
loader = jinja2.FileSystemLoader(search_directories)
env = jinja2.Environment(loader=loader, autoescape=autoescape)
@@ -267,38 +268,6 @@ class Config:
return templates
-def _format_ts_filter(value: int, format: str):
- return time.strftime(format, time.localtime(value / 1000))
-
-
-def _create_mxc_to_http_filter(public_baseurl: str) -> Callable:
- """Create and return a jinja2 filter that converts MXC urls to HTTP
-
- Args:
- public_baseurl: The public, accessible base URL of the homeserver
- """
-
- def mxc_to_http_filter(value, width, height, resize_method="crop"):
- if value[0:6] != "mxc://":
- return ""
-
- server_and_media_id = value[6:]
- fragment = None
- if "#" in server_and_media_id:
- server_and_media_id, fragment = server_and_media_id.split("#", 1)
- fragment = "#" + fragment
-
- params = {"width": width, "height": height, "method": resize_method}
- return "%s_matrix/media/v1/thumbnail/%s?%s%s" % (
- public_baseurl,
- server_and_media_id,
- urllib.parse.urlencode(params),
- fragment or "",
- )
-
- return mxc_to_http_filter
-
-
class RootConfig:
"""
Holder of an application's configuration.
diff --git a/synapse/config/oidc_config.py b/synapse/config/oidc_config.py
index f31511e03..784b416f9 100644
--- a/synapse/config/oidc_config.py
+++ b/synapse/config/oidc_config.py
@@ -152,7 +152,8 @@ class OIDCConfig(Config):
#
# localpart_template: Jinja2 template for the localpart of the MXID.
# If this is not set, the user will be prompted to choose their
- # own username.
+ # own username (see 'sso_auth_account_details.html' in the 'sso'
+ # section of this file).
#
# display_name_template: Jinja2 template for the display name to set
# on first login. If unset, no displayname will be set.
diff --git a/synapse/config/sso.py b/synapse/config/sso.py
index a470112ed..e308fc933 100644
--- a/synapse/config/sso.py
+++ b/synapse/config/sso.py
@@ -27,7 +27,7 @@ class SSOConfig(Config):
sso_config = config.get("sso") or {} # type: Dict[str, Any]
# The sso-specific template_dir
- template_dir = sso_config.get("template_dir")
+ self.sso_template_dir = sso_config.get("template_dir")
# Read templates from disk
(
@@ -48,7 +48,7 @@ class SSOConfig(Config):
"sso_auth_success.html",
"sso_auth_bad_user.html",
],
- template_dir,
+ self.sso_template_dir,
)
# These templates have no placeholders, so render them here
@@ -124,6 +124,35 @@ class SSOConfig(Config):
#
# * idp: the 'idp_id' of the chosen IDP.
#
+ # * HTML page to prompt new users to enter a userid and confirm other
+ # details: 'sso_auth_account_details.html'. This is only shown if the
+ # SSO implementation (with any user_mapping_provider) does not return
+ # a localpart.
+ #
+ # When rendering, this template is given the following variables:
+ #
+ # * server_name: the homeserver's name.
+ #
+ # * idp: details of the SSO Identity Provider that the user logged in
+ # with: an object with the following attributes:
+ #
+ # * idp_id: unique identifier for the IdP
+ # * idp_name: user-facing name for the IdP
+ # * idp_icon: if specified in the IdP config, an MXC URI for an icon
+ # for the IdP
+ # * idp_brand: if specified in the IdP config, a textual identifier
+ # for the brand of the IdP
+ #
+ # * user_attributes: an object containing details about the user that
+ # we received from the IdP. May have the following attributes:
+ #
+ # * display_name: the user's display_name
+ # * emails: a list of email addresses
+ #
+ # The template should render a form which submits the following fields:
+ #
+ # * username: the localpart of the user's chosen user id
+ #
# * HTML page for a confirmation step before redirecting back to the client
# with the login token: 'sso_redirect_confirm.html'.
#
diff --git a/synapse/handlers/sso.py b/synapse/handlers/sso.py
index ceaeb5a37..ff4750999 100644
--- a/synapse/handlers/sso.py
+++ b/synapse/handlers/sso.py
@@ -530,7 +530,7 @@ class SsoHandler:
logger.info("Recorded registration session id %s", session_id)
# Set the cookie and redirect to the username picker
- e = RedirectException(b"/_synapse/client/pick_username")
+ e = RedirectException(b"/_synapse/client/pick_username/account_details")
e.cookies.append(
b"%s=%s; path=/"
% (USERNAME_MAPPING_SESSION_COOKIE_NAME, session_id.encode("ascii"))
diff --git a/synapse/res/templates/sso_auth_account_details.html b/synapse/res/templates/sso_auth_account_details.html
new file mode 100644
index 000000000..f22b09aec
--- /dev/null
+++ b/synapse/res/templates/sso_auth_account_details.html
@@ -0,0 +1,115 @@
+
+
+
+ Synapse Login
+
+
+
+
+
+
+
Your account is nearly ready
+
Check your details before creating an account on {{ server_name }}