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

Commit a37c103

Browse files
author
David Robertson
committed
Validate /client/account/passsword/email/requestToken
1 parent 540b612 commit a37c103

File tree

1 file changed

+39
-33
lines changed

1 file changed

+39
-33
lines changed

synapse/rest/client/account.py

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from typing import TYPE_CHECKING, Optional, Tuple
2020
from urllib.parse import urlparse
2121

22-
from pydantic import BaseModel, StrictBool, StrictStr, constr
22+
from pydantic import BaseModel, StrictBool, StrictStr, constr, validator
2323

2424
from twisted.web.server import Request
2525

@@ -58,6 +58,28 @@
5858
logger = logging.getLogger(__name__)
5959

6060

61+
class EmailPasswordRequestBody(BaseModel):
62+
if TYPE_CHECKING:
63+
client_secret: str
64+
else:
65+
# See also assert_valid_client_secret()
66+
client_secret: constr(
67+
regex="[0-9a-zA-Z.=_-]", min_length=0, max_length=255 # noqa: F722
68+
)
69+
email: str
70+
id_access_token: Optional[str]
71+
id_server: Optional[str]
72+
next_link: Optional[str]
73+
send_attempt: int
74+
75+
# Canonicalise the email address. The addresses are all stored canonicalised
76+
# in the database. This allows the user to reset his password without having to
77+
# know the exact spelling (eg. upper and lower case) of address in the database.
78+
# Without this, an email stored in the database as "foo@bar.com" would cause
79+
# user requests for "FOO@bar.com" to raise a Not Found error.
80+
_email_validator = validator("email", allow_reuse=True)(validate_email)
81+
82+
6183
class EmailPasswordRequestTokenRestServlet(RestServlet):
6284
PATTERNS = client_patterns("/account/password/email/requestToken$")
6385

@@ -88,40 +110,24 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
88110
Codes.NOT_FOUND,
89111
)
90112

91-
body = parse_json_object_from_request(request)
92-
93-
assert_params_in_dict(body, ["client_secret", "email", "send_attempt"])
94-
95-
# Extract params from body
96-
client_secret = body["client_secret"]
97-
assert_valid_client_secret(client_secret)
98-
99-
# Canonicalise the email address. The addresses are all stored canonicalised
100-
# in the database. This allows the user to reset his password without having to
101-
# know the exact spelling (eg. upper and lower case) of address in the database.
102-
# Stored in the database "foo@bar.com"
103-
# User requests with "FOO@bar.com" would raise a Not Found error
104-
try:
105-
email = validate_email(body["email"])
106-
except ValueError as e:
107-
raise SynapseError(400, str(e))
108-
send_attempt = body["send_attempt"]
109-
next_link = body.get("next_link") # Optional param
113+
body = parse_and_validate_json_object_from_request(
114+
request, EmailPasswordRequestBody
115+
)
110116

111-
if next_link:
117+
if body.next_link:
112118
# Raise if the provided next_link value isn't valid
113-
assert_valid_next_link(self.hs, next_link)
119+
assert_valid_next_link(self.hs, body.next_link)
114120

115121
await self.identity_handler.ratelimit_request_token_requests(
116-
request, "email", email
122+
request, "email", body.email
117123
)
118124

119125
# The email will be sent to the stored address.
120126
# This avoids a potential account hijack by requesting a password reset to
121127
# an email address which is controlled by the attacker but which, after
122128
# canonicalisation, matches the one in our database.
123129
existing_user_id = await self.hs.get_datastores().main.get_user_id_by_threepid(
124-
"email", email
130+
"email", body.email
125131
)
126132

127133
if existing_user_id is None:
@@ -141,26 +147,26 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
141147
# Have the configured identity server handle the request
142148
ret = await self.identity_handler.requestEmailToken(
143149
self.hs.config.registration.account_threepid_delegate_email,
144-
email,
145-
client_secret,
146-
send_attempt,
147-
next_link,
150+
body.email,
151+
body.client_secret,
152+
body.send_attempt,
153+
body.next_link,
148154
)
149155
else:
150156
# Send password reset emails from Synapse
151157
sid = await self.identity_handler.send_threepid_validation(
152-
email,
153-
client_secret,
154-
send_attempt,
158+
body.email,
159+
body.client_secret,
160+
body.send_attempt,
155161
self.mailer.send_password_reset_mail,
156-
next_link,
162+
body.next_link,
157163
)
158164

159165
# Wrap the session id in a JSON object
160166
ret = {"sid": sid}
161167

162168
threepid_send_requests.labels(type="email", reason="password_reset").observe(
163-
send_attempt
169+
body.send_attempt
164170
)
165171

166172
return 200, ret

0 commit comments

Comments
 (0)