register.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. # Copyright 2015 - 2016 OpenMarket Ltd
  2. # Copyright 2017 Vector Creations Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import logging
  16. import random
  17. from typing import TYPE_CHECKING, List, Optional, Tuple
  18. from twisted.web.server import Request
  19. import synapse
  20. import synapse.api.auth
  21. import synapse.types
  22. from synapse.api.constants import APP_SERVICE_REGISTRATION_TYPE, LoginType
  23. from synapse.api.errors import (
  24. Codes,
  25. InteractiveAuthIncompleteError,
  26. SynapseError,
  27. ThreepidValidationError,
  28. UnrecognizedRequestError,
  29. )
  30. from synapse.api.ratelimiting import Ratelimiter
  31. from synapse.config import ConfigError
  32. from synapse.config.emailconfig import ThreepidBehaviour
  33. from synapse.config.homeserver import HomeServerConfig
  34. from synapse.config.ratelimiting import FederationRateLimitConfig
  35. from synapse.config.server import is_threepid_reserved
  36. from synapse.handlers.auth import AuthHandler
  37. from synapse.handlers.ui_auth import UIAuthSessionDataConstants
  38. from synapse.http.server import HttpServer, finish_request, respond_with_html
  39. from synapse.http.servlet import (
  40. RestServlet,
  41. assert_params_in_dict,
  42. parse_boolean,
  43. parse_json_object_from_request,
  44. parse_string,
  45. )
  46. from synapse.http.site import SynapseRequest
  47. from synapse.metrics import threepid_send_requests
  48. from synapse.push.mailer import Mailer
  49. from synapse.types import JsonDict
  50. from synapse.util.msisdn import phone_number_to_msisdn
  51. from synapse.util.ratelimitutils import FederationRateLimiter
  52. from synapse.util.stringutils import assert_valid_client_secret, random_string
  53. from synapse.util.threepids import (
  54. canonicalise_email,
  55. check_3pid_allowed,
  56. validate_email,
  57. )
  58. from ._base import client_patterns, interactive_auth_handler
  59. if TYPE_CHECKING:
  60. from synapse.server import HomeServer
  61. logger = logging.getLogger(__name__)
  62. class EmailRegisterRequestTokenRestServlet(RestServlet):
  63. PATTERNS = client_patterns("/register/email/requestToken$")
  64. def __init__(self, hs: "HomeServer"):
  65. super().__init__()
  66. self.hs = hs
  67. self.identity_handler = hs.get_identity_handler()
  68. self.config = hs.config
  69. if self.hs.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
  70. self.mailer = Mailer(
  71. hs=self.hs,
  72. app_name=self.config.email_app_name,
  73. template_html=self.config.email_registration_template_html,
  74. template_text=self.config.email_registration_template_text,
  75. )
  76. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  77. if self.hs.config.threepid_behaviour_email == ThreepidBehaviour.OFF:
  78. if self.hs.config.local_threepid_handling_disabled_due_to_email_config:
  79. logger.warning(
  80. "Email registration has been disabled due to lack of email config"
  81. )
  82. raise SynapseError(
  83. 400, "Email-based registration has been disabled on this server"
  84. )
  85. body = parse_json_object_from_request(request)
  86. assert_params_in_dict(body, ["client_secret", "email", "send_attempt"])
  87. # Extract params from body
  88. client_secret = body["client_secret"]
  89. assert_valid_client_secret(client_secret)
  90. # For emails, canonicalise the address.
  91. # We store all email addresses canonicalised in the DB.
  92. # (See on_POST in EmailThreepidRequestTokenRestServlet
  93. # in synapse/rest/client/account.py)
  94. try:
  95. email = validate_email(body["email"])
  96. except ValueError as e:
  97. raise SynapseError(400, str(e))
  98. send_attempt = body["send_attempt"]
  99. next_link = body.get("next_link") # Optional param
  100. if not check_3pid_allowed(self.hs, "email", email):
  101. raise SynapseError(
  102. 403,
  103. "Your email domain is not authorized to register on this server",
  104. Codes.THREEPID_DENIED,
  105. )
  106. await self.identity_handler.ratelimit_request_token_requests(
  107. request, "email", email
  108. )
  109. existing_user_id = await self.hs.get_datastore().get_user_id_by_threepid(
  110. "email", email
  111. )
  112. if existing_user_id is not None:
  113. if self.hs.config.request_token_inhibit_3pid_errors:
  114. # Make the client think the operation succeeded. See the rationale in the
  115. # comments for request_token_inhibit_3pid_errors.
  116. # Also wait for some random amount of time between 100ms and 1s to make it
  117. # look like we did something.
  118. await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
  119. return 200, {"sid": random_string(16)}
  120. raise SynapseError(400, "Email is already in use", Codes.THREEPID_IN_USE)
  121. if self.config.threepid_behaviour_email == ThreepidBehaviour.REMOTE:
  122. assert self.hs.config.account_threepid_delegate_email
  123. # Have the configured identity server handle the request
  124. ret = await self.identity_handler.requestEmailToken(
  125. self.hs.config.account_threepid_delegate_email,
  126. email,
  127. client_secret,
  128. send_attempt,
  129. next_link,
  130. )
  131. else:
  132. # Send registration emails from Synapse
  133. sid = await self.identity_handler.send_threepid_validation(
  134. email,
  135. client_secret,
  136. send_attempt,
  137. self.mailer.send_registration_mail,
  138. next_link,
  139. )
  140. # Wrap the session id in a JSON object
  141. ret = {"sid": sid}
  142. threepid_send_requests.labels(type="email", reason="register").observe(
  143. send_attempt
  144. )
  145. return 200, ret
  146. class MsisdnRegisterRequestTokenRestServlet(RestServlet):
  147. PATTERNS = client_patterns("/register/msisdn/requestToken$")
  148. def __init__(self, hs: "HomeServer"):
  149. super().__init__()
  150. self.hs = hs
  151. self.identity_handler = hs.get_identity_handler()
  152. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  153. body = parse_json_object_from_request(request)
  154. assert_params_in_dict(
  155. body, ["client_secret", "country", "phone_number", "send_attempt"]
  156. )
  157. client_secret = body["client_secret"]
  158. assert_valid_client_secret(client_secret)
  159. country = body["country"]
  160. phone_number = body["phone_number"]
  161. send_attempt = body["send_attempt"]
  162. next_link = body.get("next_link") # Optional param
  163. msisdn = phone_number_to_msisdn(country, phone_number)
  164. if not check_3pid_allowed(self.hs, "msisdn", msisdn):
  165. raise SynapseError(
  166. 403,
  167. "Phone numbers are not authorized to register on this server",
  168. Codes.THREEPID_DENIED,
  169. )
  170. await self.identity_handler.ratelimit_request_token_requests(
  171. request, "msisdn", msisdn
  172. )
  173. existing_user_id = await self.hs.get_datastore().get_user_id_by_threepid(
  174. "msisdn", msisdn
  175. )
  176. if existing_user_id is not None:
  177. if self.hs.config.request_token_inhibit_3pid_errors:
  178. # Make the client think the operation succeeded. See the rationale in the
  179. # comments for request_token_inhibit_3pid_errors.
  180. # Also wait for some random amount of time between 100ms and 1s to make it
  181. # look like we did something.
  182. await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
  183. return 200, {"sid": random_string(16)}
  184. raise SynapseError(
  185. 400, "Phone number is already in use", Codes.THREEPID_IN_USE
  186. )
  187. if not self.hs.config.account_threepid_delegate_msisdn:
  188. logger.warning(
  189. "No upstream msisdn account_threepid_delegate configured on the server to "
  190. "handle this request"
  191. )
  192. raise SynapseError(
  193. 400, "Registration by phone number is not supported on this homeserver"
  194. )
  195. ret = await self.identity_handler.requestMsisdnToken(
  196. self.hs.config.account_threepid_delegate_msisdn,
  197. country,
  198. phone_number,
  199. client_secret,
  200. send_attempt,
  201. next_link,
  202. )
  203. threepid_send_requests.labels(type="msisdn", reason="register").observe(
  204. send_attempt
  205. )
  206. return 200, ret
  207. class RegistrationSubmitTokenServlet(RestServlet):
  208. """Handles registration 3PID validation token submission"""
  209. PATTERNS = client_patterns(
  210. "/registration/(?P<medium>[^/]*)/submit_token$", releases=(), unstable=True
  211. )
  212. def __init__(self, hs: "HomeServer"):
  213. super().__init__()
  214. self.hs = hs
  215. self.auth = hs.get_auth()
  216. self.config = hs.config
  217. self.clock = hs.get_clock()
  218. self.store = hs.get_datastore()
  219. if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
  220. self._failure_email_template = (
  221. self.config.email_registration_template_failure_html
  222. )
  223. async def on_GET(self, request: Request, medium: str) -> None:
  224. if medium != "email":
  225. raise SynapseError(
  226. 400, "This medium is currently not supported for registration"
  227. )
  228. if self.config.threepid_behaviour_email == ThreepidBehaviour.OFF:
  229. if self.config.local_threepid_handling_disabled_due_to_email_config:
  230. logger.warning(
  231. "User registration via email has been disabled due to lack of email config"
  232. )
  233. raise SynapseError(
  234. 400, "Email-based registration is disabled on this server"
  235. )
  236. sid = parse_string(request, "sid", required=True)
  237. client_secret = parse_string(request, "client_secret", required=True)
  238. assert_valid_client_secret(client_secret)
  239. token = parse_string(request, "token", required=True)
  240. # Attempt to validate a 3PID session
  241. try:
  242. # Mark the session as valid
  243. next_link = await self.store.validate_threepid_session(
  244. sid, client_secret, token, self.clock.time_msec()
  245. )
  246. # Perform a 302 redirect if next_link is set
  247. if next_link:
  248. if next_link.startswith("file:///"):
  249. logger.warning(
  250. "Not redirecting to next_link as it is a local file: address"
  251. )
  252. else:
  253. request.setResponseCode(302)
  254. request.setHeader("Location", next_link)
  255. finish_request(request)
  256. return None
  257. # Otherwise show the success template
  258. html = self.config.email_registration_template_success_html_content
  259. status_code = 200
  260. except ThreepidValidationError as e:
  261. status_code = e.code
  262. # Show a failure page with a reason
  263. template_vars = {"failure_reason": e.msg}
  264. html = self._failure_email_template.render(**template_vars)
  265. respond_with_html(request, status_code, html)
  266. class UsernameAvailabilityRestServlet(RestServlet):
  267. PATTERNS = client_patterns("/register/available")
  268. def __init__(self, hs: "HomeServer"):
  269. super().__init__()
  270. self.hs = hs
  271. self.registration_handler = hs.get_registration_handler()
  272. self.ratelimiter = FederationRateLimiter(
  273. hs.get_clock(),
  274. FederationRateLimitConfig(
  275. # Time window of 2s
  276. window_size=2000,
  277. # Artificially delay requests if rate > sleep_limit/window_size
  278. sleep_limit=1,
  279. # Amount of artificial delay to apply
  280. sleep_delay=1000,
  281. # Error with 429 if more than reject_limit requests are queued
  282. reject_limit=1,
  283. # Allow 1 request at a time
  284. concurrent=1,
  285. ),
  286. )
  287. async def on_GET(self, request: Request) -> Tuple[int, JsonDict]:
  288. if not self.hs.config.enable_registration:
  289. raise SynapseError(
  290. 403, "Registration has been disabled", errcode=Codes.FORBIDDEN
  291. )
  292. ip = request.getClientIP()
  293. with self.ratelimiter.ratelimit(ip) as wait_deferred:
  294. await wait_deferred
  295. username = parse_string(request, "username", required=True)
  296. await self.registration_handler.check_username(username)
  297. return 200, {"available": True}
  298. class RegistrationTokenValidityRestServlet(RestServlet):
  299. """Check the validity of a registration token.
  300. Example:
  301. GET /_matrix/client/unstable/org.matrix.msc3231/register/org.matrix.msc3231.login.registration_token/validity?token=abcd
  302. 200 OK
  303. {
  304. "valid": true
  305. }
  306. """
  307. PATTERNS = client_patterns(
  308. f"/org.matrix.msc3231/register/{LoginType.REGISTRATION_TOKEN}/validity",
  309. releases=(),
  310. unstable=True,
  311. )
  312. def __init__(self, hs: "HomeServer"):
  313. super().__init__()
  314. self.hs = hs
  315. self.store = hs.get_datastore()
  316. self.ratelimiter = Ratelimiter(
  317. store=self.store,
  318. clock=hs.get_clock(),
  319. rate_hz=hs.config.ratelimiting.rc_registration_token_validity.per_second,
  320. burst_count=hs.config.ratelimiting.rc_registration_token_validity.burst_count,
  321. )
  322. async def on_GET(self, request: Request) -> Tuple[int, JsonDict]:
  323. await self.ratelimiter.ratelimit(None, (request.getClientIP(),))
  324. if not self.hs.config.enable_registration:
  325. raise SynapseError(
  326. 403, "Registration has been disabled", errcode=Codes.FORBIDDEN
  327. )
  328. token = parse_string(request, "token", required=True)
  329. valid = await self.store.registration_token_is_valid(token)
  330. return 200, {"valid": valid}
  331. class RegisterRestServlet(RestServlet):
  332. PATTERNS = client_patterns("/register$")
  333. def __init__(self, hs: "HomeServer"):
  334. super().__init__()
  335. self.hs = hs
  336. self.auth = hs.get_auth()
  337. self.store = hs.get_datastore()
  338. self.auth_handler = hs.get_auth_handler()
  339. self.registration_handler = hs.get_registration_handler()
  340. self.identity_handler = hs.get_identity_handler()
  341. self.room_member_handler = hs.get_room_member_handler()
  342. self.macaroon_gen = hs.get_macaroon_generator()
  343. self.ratelimiter = hs.get_registration_ratelimiter()
  344. self.password_policy_handler = hs.get_password_policy_handler()
  345. self.clock = hs.get_clock()
  346. self._registration_enabled = self.hs.config.enable_registration
  347. self._msc2918_enabled = hs.config.access_token_lifetime is not None
  348. self._registration_flows = _calculate_registration_flows(
  349. hs.config, self.auth_handler
  350. )
  351. @interactive_auth_handler
  352. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  353. body = parse_json_object_from_request(request)
  354. client_addr = request.getClientIP()
  355. await self.ratelimiter.ratelimit(None, client_addr, update=False)
  356. kind = parse_string(request, "kind", default="user")
  357. if kind == "guest":
  358. ret = await self._do_guest_registration(body, address=client_addr)
  359. return ret
  360. elif kind != "user":
  361. raise UnrecognizedRequestError(
  362. f"Do not understand membership kind: {kind}",
  363. )
  364. if self._msc2918_enabled:
  365. # Check if this registration should also issue a refresh token, as
  366. # per MSC2918
  367. should_issue_refresh_token = parse_boolean(
  368. request, name="org.matrix.msc2918.refresh_token", default=False
  369. )
  370. else:
  371. should_issue_refresh_token = False
  372. # Pull out the provided username and do basic sanity checks early since
  373. # the auth layer will store these in sessions.
  374. desired_username = None
  375. if "username" in body:
  376. if not isinstance(body["username"], str) or len(body["username"]) > 512:
  377. raise SynapseError(400, "Invalid username")
  378. desired_username = body["username"]
  379. # fork off as soon as possible for ASes which have completely
  380. # different registration flows to normal users
  381. # == Application Service Registration ==
  382. if body.get("type") == APP_SERVICE_REGISTRATION_TYPE:
  383. if not self.auth.has_access_token(request):
  384. raise SynapseError(
  385. 400,
  386. "Appservice token must be provided when using a type of m.login.application_service",
  387. )
  388. # Verify the AS
  389. self.auth.get_appservice_by_req(request)
  390. # Set the desired user according to the AS API (which uses the
  391. # 'user' key not 'username'). Since this is a new addition, we'll
  392. # fallback to 'username' if they gave one.
  393. desired_username = body.get("user", desired_username)
  394. # XXX we should check that desired_username is valid. Currently
  395. # we give appservices carte blanche for any insanity in mxids,
  396. # because the IRC bridges rely on being able to register stupid
  397. # IDs.
  398. access_token = self.auth.get_access_token_from_request(request)
  399. if not isinstance(desired_username, str):
  400. raise SynapseError(400, "Desired Username is missing or not a string")
  401. result = await self._do_appservice_registration(
  402. desired_username,
  403. access_token,
  404. body,
  405. should_issue_refresh_token=should_issue_refresh_token,
  406. )
  407. return 200, result
  408. elif self.auth.has_access_token(request):
  409. raise SynapseError(
  410. 400,
  411. "An access token should not be provided on requests to /register (except if type is m.login.application_service)",
  412. )
  413. # == Normal User Registration == (everyone else)
  414. if not self._registration_enabled:
  415. raise SynapseError(403, "Registration has been disabled", Codes.FORBIDDEN)
  416. # For regular registration, convert the provided username to lowercase
  417. # before attempting to register it. This should mean that people who try
  418. # to register with upper-case in their usernames don't get a nasty surprise.
  419. #
  420. # Note that we treat usernames case-insensitively in login, so they are
  421. # free to carry on imagining that their username is CrAzYh4cKeR if that
  422. # keeps them happy.
  423. if desired_username is not None:
  424. desired_username = desired_username.lower()
  425. # Check if this account is upgrading from a guest account.
  426. guest_access_token = body.get("guest_access_token", None)
  427. # Pull out the provided password and do basic sanity checks early.
  428. #
  429. # Note that we remove the password from the body since the auth layer
  430. # will store the body in the session and we don't want a plaintext
  431. # password store there.
  432. password = body.pop("password", None)
  433. if password is not None:
  434. if not isinstance(password, str) or len(password) > 512:
  435. raise SynapseError(400, "Invalid password")
  436. self.password_policy_handler.validate_password(password)
  437. if "initial_device_display_name" in body and password is None:
  438. # ignore 'initial_device_display_name' if sent without
  439. # a password to work around a client bug where it sent
  440. # the 'initial_device_display_name' param alone, wiping out
  441. # the original registration params
  442. logger.warning("Ignoring initial_device_display_name without password")
  443. del body["initial_device_display_name"]
  444. session_id = self.auth_handler.get_session_id(body)
  445. registered_user_id = None
  446. password_hash = None
  447. if session_id:
  448. # if we get a registered user id out of here, it means we previously
  449. # registered a user for this session, so we could just return the
  450. # user here. We carry on and go through the auth checks though,
  451. # for paranoia.
  452. registered_user_id = await self.auth_handler.get_session_data(
  453. session_id, UIAuthSessionDataConstants.REGISTERED_USER_ID, None
  454. )
  455. # Extract the previously-hashed password from the session.
  456. password_hash = await self.auth_handler.get_session_data(
  457. session_id, UIAuthSessionDataConstants.PASSWORD_HASH, None
  458. )
  459. # Ensure that the username is valid.
  460. if desired_username is not None:
  461. await self.registration_handler.check_username(
  462. desired_username,
  463. guest_access_token=guest_access_token,
  464. assigned_user_id=registered_user_id,
  465. )
  466. # Check if the user-interactive authentication flows are complete, if
  467. # not this will raise a user-interactive auth error.
  468. try:
  469. auth_result, params, session_id = await self.auth_handler.check_ui_auth(
  470. self._registration_flows,
  471. request,
  472. body,
  473. "register a new account",
  474. )
  475. except InteractiveAuthIncompleteError as e:
  476. # The user needs to provide more steps to complete auth.
  477. #
  478. # Hash the password and store it with the session since the client
  479. # is not required to provide the password again.
  480. #
  481. # If a password hash was previously stored we will not attempt to
  482. # re-hash and store it for efficiency. This assumes the password
  483. # does not change throughout the authentication flow, but this
  484. # should be fine since the data is meant to be consistent.
  485. if not password_hash and password:
  486. password_hash = await self.auth_handler.hash(password)
  487. await self.auth_handler.set_session_data(
  488. e.session_id,
  489. UIAuthSessionDataConstants.PASSWORD_HASH,
  490. password_hash,
  491. )
  492. raise
  493. # Check that we're not trying to register a denied 3pid.
  494. #
  495. # the user-facing checks will probably already have happened in
  496. # /register/email/requestToken when we requested a 3pid, but that's not
  497. # guaranteed.
  498. if auth_result:
  499. for login_type in [LoginType.EMAIL_IDENTITY, LoginType.MSISDN]:
  500. if login_type in auth_result:
  501. medium = auth_result[login_type]["medium"]
  502. address = auth_result[login_type]["address"]
  503. if not check_3pid_allowed(self.hs, medium, address):
  504. raise SynapseError(
  505. 403,
  506. "Third party identifiers (email/phone numbers)"
  507. + " are not authorized on this server",
  508. Codes.THREEPID_DENIED,
  509. )
  510. if registered_user_id is not None:
  511. logger.info(
  512. "Already registered user ID %r for this session", registered_user_id
  513. )
  514. # don't re-register the threepids
  515. registered = False
  516. else:
  517. # If we have a password in this request, prefer it. Otherwise, there
  518. # might be a password hash from an earlier request.
  519. if password:
  520. password_hash = await self.auth_handler.hash(password)
  521. if not password_hash:
  522. raise SynapseError(400, "Missing params: password", Codes.MISSING_PARAM)
  523. desired_username = params.get("username", None)
  524. guest_access_token = params.get("guest_access_token", None)
  525. if desired_username is not None:
  526. desired_username = desired_username.lower()
  527. threepid = None
  528. if auth_result:
  529. threepid = auth_result.get(LoginType.EMAIL_IDENTITY)
  530. # Also check that we're not trying to register a 3pid that's already
  531. # been registered.
  532. #
  533. # This has probably happened in /register/email/requestToken as well,
  534. # but if a user hits this endpoint twice then clicks on each link from
  535. # the two activation emails, they would register the same 3pid twice.
  536. for login_type in [LoginType.EMAIL_IDENTITY, LoginType.MSISDN]:
  537. if login_type in auth_result:
  538. medium = auth_result[login_type]["medium"]
  539. address = auth_result[login_type]["address"]
  540. # For emails, canonicalise the address.
  541. # We store all email addresses canonicalised in the DB.
  542. # (See on_POST in EmailThreepidRequestTokenRestServlet
  543. # in synapse/rest/client/account.py)
  544. if medium == "email":
  545. try:
  546. address = canonicalise_email(address)
  547. except ValueError as e:
  548. raise SynapseError(400, str(e))
  549. existing_user_id = await self.store.get_user_id_by_threepid(
  550. medium, address
  551. )
  552. if existing_user_id is not None:
  553. raise SynapseError(
  554. 400,
  555. "%s is already in use" % medium,
  556. Codes.THREEPID_IN_USE,
  557. )
  558. entries = await self.store.get_user_agents_ips_to_ui_auth_session(
  559. session_id
  560. )
  561. registered_user_id = await self.registration_handler.register_user(
  562. localpart=desired_username,
  563. password_hash=password_hash,
  564. guest_access_token=guest_access_token,
  565. threepid=threepid,
  566. address=client_addr,
  567. user_agent_ips=entries,
  568. )
  569. # Necessary due to auth checks prior to the threepid being
  570. # written to the db
  571. if threepid:
  572. if is_threepid_reserved(
  573. self.hs.config.mau_limits_reserved_threepids, threepid
  574. ):
  575. await self.store.upsert_monthly_active_user(registered_user_id)
  576. # Remember that the user account has been registered (and the user
  577. # ID it was registered with, since it might not have been specified).
  578. await self.auth_handler.set_session_data(
  579. session_id,
  580. UIAuthSessionDataConstants.REGISTERED_USER_ID,
  581. registered_user_id,
  582. )
  583. registered = True
  584. return_dict = await self._create_registration_details(
  585. registered_user_id,
  586. params,
  587. should_issue_refresh_token=should_issue_refresh_token,
  588. )
  589. if registered:
  590. # Check if a token was used to authenticate registration
  591. registration_token = await self.auth_handler.get_session_data(
  592. session_id,
  593. UIAuthSessionDataConstants.REGISTRATION_TOKEN,
  594. )
  595. if registration_token:
  596. # Increment the `completed` counter for the token
  597. await self.store.use_registration_token(registration_token)
  598. # Indicate that the token has been successfully used so that
  599. # pending is not decremented again when expiring old UIA sessions.
  600. await self.store.mark_ui_auth_stage_complete(
  601. session_id,
  602. LoginType.REGISTRATION_TOKEN,
  603. True,
  604. )
  605. await self.registration_handler.post_registration_actions(
  606. user_id=registered_user_id,
  607. auth_result=auth_result,
  608. access_token=return_dict.get("access_token"),
  609. )
  610. return 200, return_dict
  611. async def _do_appservice_registration(
  612. self,
  613. username: str,
  614. as_token: str,
  615. body: JsonDict,
  616. should_issue_refresh_token: bool = False,
  617. ) -> JsonDict:
  618. user_id = await self.registration_handler.appservice_register(
  619. username, as_token
  620. )
  621. return await self._create_registration_details(
  622. user_id,
  623. body,
  624. is_appservice_ghost=True,
  625. should_issue_refresh_token=should_issue_refresh_token,
  626. )
  627. async def _create_registration_details(
  628. self,
  629. user_id: str,
  630. params: JsonDict,
  631. is_appservice_ghost: bool = False,
  632. should_issue_refresh_token: bool = False,
  633. ) -> JsonDict:
  634. """Complete registration of newly-registered user
  635. Allocates device_id if one was not given; also creates access_token.
  636. Args:
  637. user_id: full canonical @user:id
  638. params: registration parameters, from which we pull device_id,
  639. initial_device_name and inhibit_login
  640. is_appservice_ghost
  641. should_issue_refresh_token: True if this registration should issue
  642. a refresh token alongside the access token.
  643. Returns:
  644. dictionary for response from /register
  645. """
  646. result: JsonDict = {
  647. "user_id": user_id,
  648. "home_server": self.hs.hostname,
  649. }
  650. if not params.get("inhibit_login", False):
  651. device_id = params.get("device_id")
  652. initial_display_name = params.get("initial_device_display_name")
  653. (
  654. device_id,
  655. access_token,
  656. valid_until_ms,
  657. refresh_token,
  658. ) = await self.registration_handler.register_device(
  659. user_id,
  660. device_id,
  661. initial_display_name,
  662. is_guest=False,
  663. is_appservice_ghost=is_appservice_ghost,
  664. should_issue_refresh_token=should_issue_refresh_token,
  665. )
  666. result.update({"access_token": access_token, "device_id": device_id})
  667. if valid_until_ms is not None:
  668. expires_in_ms = valid_until_ms - self.clock.time_msec()
  669. result["expires_in_ms"] = expires_in_ms
  670. if refresh_token is not None:
  671. result["refresh_token"] = refresh_token
  672. return result
  673. async def _do_guest_registration(
  674. self, params: JsonDict, address: Optional[str] = None
  675. ) -> Tuple[int, JsonDict]:
  676. if not self.hs.config.allow_guest_access:
  677. raise SynapseError(403, "Guest access is disabled")
  678. user_id = await self.registration_handler.register_user(
  679. make_guest=True, address=address
  680. )
  681. # we don't allow guests to specify their own device_id, because
  682. # we have nowhere to store it.
  683. device_id = synapse.api.auth.GUEST_DEVICE_ID
  684. initial_display_name = params.get("initial_device_display_name")
  685. (
  686. device_id,
  687. access_token,
  688. valid_until_ms,
  689. refresh_token,
  690. ) = await self.registration_handler.register_device(
  691. user_id, device_id, initial_display_name, is_guest=True
  692. )
  693. result: JsonDict = {
  694. "user_id": user_id,
  695. "device_id": device_id,
  696. "access_token": access_token,
  697. "home_server": self.hs.hostname,
  698. }
  699. if valid_until_ms is not None:
  700. expires_in_ms = valid_until_ms - self.clock.time_msec()
  701. result["expires_in_ms"] = expires_in_ms
  702. if refresh_token is not None:
  703. result["refresh_token"] = refresh_token
  704. return 200, result
  705. def _calculate_registration_flows(
  706. config: HomeServerConfig, auth_handler: AuthHandler
  707. ) -> List[List[str]]:
  708. """Get a suitable flows list for registration
  709. Args:
  710. config: server configuration
  711. auth_handler: authorization handler
  712. Returns: a list of supported flows
  713. """
  714. # FIXME: need a better error than "no auth flow found" for scenarios
  715. # where we required 3PID for registration but the user didn't give one
  716. require_email = "email" in config.registrations_require_3pid
  717. require_msisdn = "msisdn" in config.registrations_require_3pid
  718. show_msisdn = True
  719. show_email = True
  720. if config.disable_msisdn_registration:
  721. show_msisdn = False
  722. require_msisdn = False
  723. enabled_auth_types = auth_handler.get_enabled_auth_types()
  724. if LoginType.EMAIL_IDENTITY not in enabled_auth_types:
  725. show_email = False
  726. if require_email:
  727. raise ConfigError(
  728. "Configuration requires email address at registration, but email "
  729. "validation is not configured"
  730. )
  731. if LoginType.MSISDN not in enabled_auth_types:
  732. show_msisdn = False
  733. if require_msisdn:
  734. raise ConfigError(
  735. "Configuration requires msisdn at registration, but msisdn "
  736. "validation is not configured"
  737. )
  738. flows = []
  739. # only support 3PIDless registration if no 3PIDs are required
  740. if not require_email and not require_msisdn:
  741. # Add a dummy step here, otherwise if a client completes
  742. # recaptcha first we'll assume they were going for this flow
  743. # and complete the request, when they could have been trying to
  744. # complete one of the flows with email/msisdn auth.
  745. flows.append([LoginType.DUMMY])
  746. # only support the email-only flow if we don't require MSISDN 3PIDs
  747. if show_email and not require_msisdn:
  748. flows.append([LoginType.EMAIL_IDENTITY])
  749. # only support the MSISDN-only flow if we don't require email 3PIDs
  750. if show_msisdn and not require_email:
  751. flows.append([LoginType.MSISDN])
  752. if show_email and show_msisdn:
  753. # always let users provide both MSISDN & email
  754. flows.append([LoginType.MSISDN, LoginType.EMAIL_IDENTITY])
  755. # Prepend m.login.terms to all flows if we're requiring consent
  756. if config.user_consent_at_registration:
  757. for flow in flows:
  758. flow.insert(0, LoginType.TERMS)
  759. # Prepend recaptcha to all flows if we're requiring captcha
  760. if config.enable_registration_captcha:
  761. for flow in flows:
  762. flow.insert(0, LoginType.RECAPTCHA)
  763. # Prepend registration token to all flows if we're requiring a token
  764. if config.registration_requires_token:
  765. for flow in flows:
  766. flow.insert(0, LoginType.REGISTRATION_TOKEN)
  767. return flows
  768. def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
  769. EmailRegisterRequestTokenRestServlet(hs).register(http_server)
  770. MsisdnRegisterRequestTokenRestServlet(hs).register(http_server)
  771. UsernameAvailabilityRestServlet(hs).register(http_server)
  772. RegistrationSubmitTokenServlet(hs).register(http_server)
  773. RegistrationTokenValidityRestServlet(hs).register(http_server)
  774. RegisterRestServlet(hs).register(http_server)