def __init__(self, hs: "HomeServer", pusher_config: PusherConfig, mailer: Mailer): super().__init__(hs, pusher_config) self.mailer = mailer self.store = self.hs.get_datastore() self.email = pusher_config.pushkey self.timed_call: Optional[IDelayedCall] = None self.throttle_params: Dict[str, ThrottleParams] = {} self._inited = False self._is_processing = False # Make sure that the email is valid. try: validate_email(self.email) except ValueError: raise PusherConfigException("Invalid email")
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: if self.hs.config.email.threepid_behaviour_email == ThreepidBehaviour.OFF: if (self.hs.config.email. local_threepid_handling_disabled_due_to_email_config): logger.warning( "Email registration has been disabled due to lack of email config" ) raise SynapseError( 400, "Email-based registration has been disabled on this server") body = parse_json_object_from_request(request) assert_params_in_dict(body, ["client_secret", "email", "send_attempt"]) # Extract params from body client_secret = body["client_secret"] assert_valid_client_secret(client_secret) # For emails, canonicalise the address. # We store all email addresses canonicalised in the DB. # (See on_POST in EmailThreepidRequestTokenRestServlet # in synapse/rest/client/account.py) try: email = validate_email(body["email"]) except ValueError as e: raise SynapseError(400, str(e)) send_attempt = body["send_attempt"] next_link = body.get("next_link") # Optional param if not check_3pid_allowed(self.hs, "email", email): raise SynapseError( 403, "Your email domain is not authorized to register on this server", Codes.THREEPID_DENIED, ) await self.identity_handler.ratelimit_request_token_requests( request, "email", email) existing_user_id = await self.hs.get_datastore( ).get_user_id_by_threepid("email", email) if existing_user_id is not None: if self.hs.config.server.request_token_inhibit_3pid_errors: # Make the client think the operation succeeded. See the rationale in the # comments for request_token_inhibit_3pid_errors. # Also wait for some random amount of time between 100ms and 1s to make it # look like we did something. await self.hs.get_clock().sleep(random.randint(1, 10) / 10) return 200, {"sid": random_string(16)} raise SynapseError(400, "Email is already in use", Codes.THREEPID_IN_USE) if self.config.email.threepid_behaviour_email == ThreepidBehaviour.REMOTE: assert self.hs.config.registration.account_threepid_delegate_email # Have the configured identity server handle the request ret = await self.identity_handler.requestEmailToken( self.hs.config.registration.account_threepid_delegate_email, email, client_secret, send_attempt, next_link, ) else: # Send registration emails from Synapse sid = await self.identity_handler.send_threepid_validation( email, client_secret, send_attempt, self.mailer.send_registration_mail, next_link, ) # Wrap the session id in a JSON object ret = {"sid": sid} threepid_send_requests.labels(type="email", reason="register").observe(send_attempt) return 200, ret
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: if self.config.email.threepid_behaviour_email == ThreepidBehaviour.OFF: if self.config.email.local_threepid_handling_disabled_due_to_email_config: logger.warning( "Adding emails have been disabled due to lack of an email config" ) raise SynapseError( 400, "Adding an email to your account is disabled on this server") body = parse_json_object_from_request(request) assert_params_in_dict(body, ["client_secret", "email", "send_attempt"]) client_secret = body["client_secret"] assert_valid_client_secret(client_secret) # Canonicalise the email address. The addresses are all stored canonicalised # in the database. # This ensures that the validation email is sent to the canonicalised address # as it will later be entered into the database. # Otherwise the email will be sent to "*****@*****.**" and stored as # "*****@*****.**" in database. try: email = validate_email(body["email"]) except ValueError as e: raise SynapseError(400, str(e)) send_attempt = body["send_attempt"] next_link = body.get("next_link") # Optional param if not await check_3pid_allowed(self.hs, "email", email): raise SynapseError( 403, "Your email is not authorized on this server", Codes.THREEPID_DENIED, ) await self.identity_handler.ratelimit_request_token_requests( request, "email", email) if next_link: # Raise if the provided next_link value isn't valid assert_valid_next_link(self.hs, next_link) existing_user_id = await self.store.get_user_id_by_threepid( "email", email) if existing_user_id is not None: if self.config.server.request_token_inhibit_3pid_errors: # Make the client think the operation succeeded. See the rationale in the # comments for request_token_inhibit_3pid_errors. # Also wait for some random amount of time between 100ms and 1s to make it # look like we did something. await self.hs.get_clock().sleep(random.randint(1, 10) / 10) return 200, {"sid": random_string(16)} raise SynapseError(400, "Email is already in use", Codes.THREEPID_IN_USE) if self.config.email.threepid_behaviour_email == ThreepidBehaviour.REMOTE: assert self.hs.config.registration.account_threepid_delegate_email # Have the configured identity server handle the request ret = await self.identity_handler.requestEmailToken( self.hs.config.registration.account_threepid_delegate_email, email, client_secret, send_attempt, next_link, ) else: # Send threepid validation emails from Synapse sid = await self.identity_handler.send_threepid_validation( email, client_secret, send_attempt, self.mailer.send_add_threepid_mail, next_link, ) # Wrap the session id in a JSON object ret = {"sid": sid} threepid_send_requests.labels( type="email", reason="add_threepid").observe(send_attempt) return 200, ret
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: if self.config.email.threepid_behaviour_email == ThreepidBehaviour.OFF: if self.config.email.local_threepid_handling_disabled_due_to_email_config: logger.warning( "User password resets have been disabled due to lack of email config" ) raise SynapseError( 400, "Email-based password resets have been disabled on this server" ) body = parse_json_object_from_request(request) assert_params_in_dict(body, ["client_secret", "email", "send_attempt"]) # Extract params from body client_secret = body["client_secret"] assert_valid_client_secret(client_secret) # Canonicalise the email address. The addresses are all stored canonicalised # in the database. This allows the user to reset his password without having to # know the exact spelling (eg. upper and lower case) of address in the database. # Stored in the database "*****@*****.**" # User requests with "*****@*****.**" would raise a Not Found error try: email = validate_email(body["email"]) except ValueError as e: raise SynapseError(400, str(e)) send_attempt = body["send_attempt"] next_link = body.get("next_link") # Optional param if next_link: # Raise if the provided next_link value isn't valid assert_valid_next_link(self.hs, next_link) await self.identity_handler.ratelimit_request_token_requests( request, "email", email) # The email will be sent to the stored address. # This avoids a potential account hijack by requesting a password reset to # an email address which is controlled by the attacker but which, after # canonicalisation, matches the one in our database. existing_user_id = await self.hs.get_datastore( ).get_user_id_by_threepid("email", email) if existing_user_id is None: if self.config.server.request_token_inhibit_3pid_errors: # Make the client think the operation succeeded. See the rationale in the # comments for request_token_inhibit_3pid_errors. # Also wait for some random amount of time between 100ms and 1s to make it # look like we did something. await self.hs.get_clock().sleep(random.randint(1, 10) / 10) return 200, {"sid": random_string(16)} raise SynapseError(400, "Email not found", Codes.THREEPID_NOT_FOUND) if self.config.email.threepid_behaviour_email == ThreepidBehaviour.REMOTE: assert self.hs.config.registration.account_threepid_delegate_email # Have the configured identity server handle the request ret = await self.identity_handler.requestEmailToken( self.hs.config.registration.account_threepid_delegate_email, email, client_secret, send_attempt, next_link, ) else: # Send password reset emails from Synapse sid = await self.identity_handler.send_threepid_validation( email, client_secret, send_attempt, self.mailer.send_password_reset_mail, next_link, ) # Wrap the session id in a JSON object ret = {"sid": sid} threepid_send_requests.labels( type="email", reason="password_reset").observe(send_attempt) return 200, ret
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: body = parse_json_object_from_request(request) # we do basic sanity checks here because the auth layer will store these # in sessions. Pull out the new password provided to us. new_password = body.pop("new_password", None) if new_password is not None: if not isinstance(new_password, str) or len(new_password) > 512: raise SynapseError(400, "Invalid password") self.password_policy_handler.validate_password(new_password) # there are two possibilities here. Either the user does not have an # access token, and needs to do a password reset; or they have one and # need to validate their identity. # # In the first case, we offer a couple of means of identifying # themselves (email and msisdn, though it's unclear if msisdn actually # works). # # In the second case, we require a password to confirm their identity. requester = None if self.auth.has_access_token(request): requester = await self.auth.get_user_by_req(request) # blindly trust ASes without UI-authing them try: ( params, session_id, ) = await self.auth_handler.validate_user_via_ui_auth( requester, request, body, "modify your account password", ) except InteractiveAuthIncompleteError as e: # The user needs to provide more steps to complete auth, but # they're not required to provide the password again. # # If a password is available now, hash the provided password and # store it for later. if new_password: new_password_hash = await self.auth_handler.hash( new_password) await self.auth_handler.set_session_data( e.session_id, UIAuthSessionDataConstants.PASSWORD_HASH, new_password_hash, ) raise user_id = requester.user.to_string() else: try: result, params, session_id = await self.auth_handler.check_ui_auth( [[LoginType.EMAIL_IDENTITY]], request, body, "modify your account password", ) except InteractiveAuthIncompleteError as e: # The user needs to provide more steps to complete auth, but # they're not required to provide the password again. # # If a password is available now, hash the provided password and # store it for later. if new_password: new_password_hash = await self.auth_handler.hash( new_password) await self.auth_handler.set_session_data( e.session_id, UIAuthSessionDataConstants.PASSWORD_HASH, new_password_hash, ) raise if LoginType.EMAIL_IDENTITY in result: threepid = result[LoginType.EMAIL_IDENTITY] if "medium" not in threepid or "address" not in threepid: raise SynapseError(500, "Malformed threepid") if threepid["medium"] == "email": # For emails, canonicalise the address. # We store all email addresses canonicalised in the DB. # (See add_threepid in synapse/handlers/auth.py) try: threepid["address"] = validate_email( threepid["address"]) except ValueError as e: raise SynapseError(400, str(e)) # if using email, we must know about the email they're authing with! threepid_user_id = await self.datastore.get_user_id_by_threepid( threepid["medium"], threepid["address"]) if not threepid_user_id: raise SynapseError(404, "Email address not found", Codes.NOT_FOUND) user_id = threepid_user_id else: logger.error("Auth succeeded but no known type! %r", result.keys()) raise SynapseError(500, "", Codes.UNKNOWN) # If we have a password in this request, prefer it. Otherwise, use the # password hash from an earlier request. if new_password: password_hash: Optional[str] = await self.auth_handler.hash( new_password) elif session_id is not None: password_hash = await self.auth_handler.get_session_data( session_id, UIAuthSessionDataConstants.PASSWORD_HASH, None) else: # UI validation was skipped, but the request did not include a new # password. password_hash = None if not password_hash: raise SynapseError(400, "Missing params: password", Codes.MISSING_PARAM) logout_devices = params.get("logout_devices", True) await self._set_password_handler.set_password(user_id, password_hash, logout_devices, requester) return 200, {}