Skip to content

Commit c7c2b5b

Browse files
committed
chore: allow specifying a max for custom refresh token durations (#17)
This amends 05b8fc1. You can now specify a maximum duration for the refresh token lifetime to be used with the custom `com.famedly.refresh_token_lifetime_ms` parameter. This clamps any values passed via the API to be lower or equal of this maximum specified via `famedly_maximum_refresh_token_lifetime` in the config.
1 parent 0f53a67 commit c7c2b5b

File tree

3 files changed

+88
-1
lines changed

3 files changed

+88
-1
lines changed

synapse/config/registration.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,14 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
229229
" exceeds `session_lifetime`!"
230230
)
231231

232+
max_refresh_token_lifetime = config.get(
233+
"famedly_maximum_refresh_token_lifetime", "160d"
234+
)
235+
236+
if max_refresh_token_lifetime is not None:
237+
max_refresh_token_lifetime = self.parse_duration(max_refresh_token_lifetime)
238+
self.famedly_maximum_refresh_token_lifetime: int = max_refresh_token_lifetime
239+
232240
# The fallback template used for authenticating using a registration token
233241
self.registration_token_template = self.read_template("registration_token.html")
234242

synapse/rest/client/login.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,9 @@ def __init__(self, hs: "HomeServer"):
589589
hs.config.registration.refreshable_access_token_lifetime
590590
)
591591
self.refresh_token_lifetime = hs.config.registration.refresh_token_lifetime
592+
self.famedly_maximum_refresh_token_lifetime = (
593+
hs.config.registration.famedly_maximum_refresh_token_lifetime
594+
)
592595

593596
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
594597
refresh_submission = parse_json_object_from_request(request)
@@ -614,7 +617,10 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
614617
"Invalid param: com.famedly.refresh_token_lifetime_ms",
615618
Codes.INVALID_PARAM,
616619
)
617-
refresh_valid_until_ms = now + custom_refresh_token_lifetime
620+
refresh_valid_until_ms = now + min(
621+
custom_refresh_token_lifetime,
622+
self.famedly_maximum_refresh_token_lifetime,
623+
)
618624
elif self.refresh_token_lifetime is not None:
619625
refresh_valid_until_ms = now + self.refresh_token_lifetime
620626

tests/rest/client/test_auth.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,79 @@ def use_custom_refresh_token(refresh_token: str) -> FakeChannel:
991991
refresh_response.code, HTTPStatus.FORBIDDEN, refresh_response.result
992992
)
993993

994+
@override_config(
995+
{
996+
"refreshable_access_token_lifetime": "1m",
997+
"refresh_token_lifetime": "2m",
998+
"famedly_maximum_refresh_token_lifetime": "10m",
999+
}
1000+
)
1001+
def test_custom_refresh_token_expiry_maximum(self) -> None:
1002+
"""
1003+
The client might override the refresh token lifetime using the custom com.famedly.refresh_token_lifetime_ms parameter. Ensure we clamp to the maximum.
1004+
"""
1005+
1006+
def use_custom_refresh_token(refresh_token: str) -> FakeChannel:
1007+
"""
1008+
Helper that makes a request to use a refresh token with a custom lifetime (30 minutes).
1009+
"""
1010+
return self.make_request(
1011+
"POST",
1012+
"/_matrix/client/v3/refresh",
1013+
{
1014+
"refresh_token": refresh_token,
1015+
"com.famedly.refresh_token_lifetime_ms": 30 * 60 * 1000,
1016+
},
1017+
)
1018+
1019+
body = {
1020+
"type": "m.login.password",
1021+
"user": "test",
1022+
"password": self.user_pass,
1023+
"refresh_token": True,
1024+
}
1025+
login_response = self.make_request(
1026+
"POST",
1027+
"/_matrix/client/r0/login",
1028+
body,
1029+
)
1030+
self.assertEqual(login_response.code, HTTPStatus.OK, login_response.result)
1031+
refresh_token1 = login_response.json_body["refresh_token"]
1032+
1033+
# refresh immediately to set custom expiry time
1034+
refresh_response = use_custom_refresh_token(refresh_token1)
1035+
self.assertEqual(refresh_response.code, HTTPStatus.OK, refresh_response.result)
1036+
self.assertIn(
1037+
"refresh_token",
1038+
refresh_response.json_body,
1039+
"No new refresh token returned after refresh.",
1040+
)
1041+
refresh_token2 = refresh_response.json_body["refresh_token"]
1042+
1043+
# Advance 179 seconds in the future (just shy of 3 minutes)
1044+
self.reactor.advance(179.0)
1045+
1046+
# Refresh our session. The refresh token should still JUST be valid right now.
1047+
# By doing so, we get a new access token and a new refresh token.
1048+
refresh_response = use_custom_refresh_token(refresh_token2)
1049+
self.assertEqual(refresh_response.code, HTTPStatus.OK, refresh_response.result)
1050+
self.assertIn(
1051+
"refresh_token",
1052+
refresh_response.json_body,
1053+
"No new refresh token returned after refresh.",
1054+
)
1055+
refresh_token3 = refresh_response.json_body["refresh_token"]
1056+
1057+
# Advance 610 seconds in the future (just a bit more than 10 minutes)
1058+
self.reactor.advance(610.0)
1059+
1060+
# Try to refresh our session, but instead notice that the refresh token is
1061+
# not valid (it just expired).
1062+
refresh_response = use_custom_refresh_token(refresh_token3)
1063+
self.assertEqual(
1064+
refresh_response.code, HTTPStatus.FORBIDDEN, refresh_response.result
1065+
)
1066+
9941067
@override_config(
9951068
{
9961069
"refreshable_access_token_lifetime": "2m",

0 commit comments

Comments
 (0)