pyheltycloud 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. {pyheltycloud-0.2.0/src/pyheltycloud.egg-info → pyheltycloud-0.2.1}/PKG-INFO +1 -1
  2. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/pyproject.toml +1 -1
  3. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/src/pyheltycloud/auth.py +18 -2
  4. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1/src/pyheltycloud.egg-info}/PKG-INFO +1 -1
  5. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/tests/test_auth_flow.py +32 -0
  6. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/LICENSE +0 -0
  7. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/README.md +0 -0
  8. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/setup.cfg +0 -0
  9. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/src/pyheltycloud/__init__.py +0 -0
  10. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/src/pyheltycloud/client.py +0 -0
  11. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/src/pyheltycloud/const.py +0 -0
  12. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/src/pyheltycloud/exceptions.py +0 -0
  13. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/src/pyheltycloud/models.py +0 -0
  14. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/src/pyheltycloud/py.typed +0 -0
  15. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/src/pyheltycloud.egg-info/SOURCES.txt +0 -0
  16. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/src/pyheltycloud.egg-info/dependency_links.txt +0 -0
  17. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/src/pyheltycloud.egg-info/requires.txt +0 -0
  18. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/src/pyheltycloud.egg-info/top_level.txt +0 -0
  19. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/tests/test_auth.py +0 -0
  20. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/tests/test_client.py +0 -0
  21. {pyheltycloud-0.2.0 → pyheltycloud-0.2.1}/tests/test_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyheltycloud
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Async client for the Helty cloud (hcloud) of Helty Flow VMC units
5
5
  Author: Ermanno Baschiera
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyheltycloud"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "Async client for the Helty cloud (hcloud) of Helty Flow VMC units"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -26,6 +26,19 @@ import aiohttp
26
26
 
27
27
  from .exceptions import HeltyCloudAuthError, HeltyCloudConnectionError
28
28
 
29
+ #: Cognito errors that mean the credentials themselves were refused. Anything
30
+ #: else it answers with, like throttling or its own internal errors, is a
31
+ #: passing failure: reporting it as bad credentials would send the user to
32
+ #: re-enter a password that is still right.
33
+ CREDENTIAL_ERRORS = frozenset(
34
+ {
35
+ "NotAuthorizedException",
36
+ "PasswordResetRequiredException",
37
+ "UserNotConfirmedException",
38
+ "UserNotFoundException",
39
+ }
40
+ )
41
+
29
42
  # 3072-bit prime and generator, fixed by the Cognito protocol.
30
43
  N_HEX = (
31
44
  "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74"
@@ -195,8 +208,11 @@ class CognitoSrpAuth:
195
208
  raise HeltyCloudConnectionError(f"{target}: unexpected answer {text[:200]}")
196
209
 
197
210
  if status != 200:
198
- message = body.get("message") or body.get("__type") or str(body)
199
- raise HeltyCloudAuthError(f"{target}: {message}")
211
+ error_type = str(body.get("__type", "")).rpartition("#")[2]
212
+ message = body.get("message") or error_type or str(body)
213
+ if error_type in CREDENTIAL_ERRORS:
214
+ raise HeltyCloudAuthError(f"{target}: {message}")
215
+ raise HeltyCloudConnectionError(f"{target}: {message}")
200
216
  return body
201
217
 
202
218
  def _password_key(self, username: str, password: str, srp_b_hex: str, salt_hex: str) -> bytes:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyheltycloud
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Async client for the Helty cloud (hcloud) of Helty Flow VMC units
5
5
  Author: Ermanno Baschiera
6
6
  License-Expression: MIT
@@ -94,6 +94,38 @@ async def test_wrong_password_is_auth_error(auth: CognitoSrpAuth, cloud: FakeClo
94
94
  await auth.authenticate("user@example.com", "wrong")
95
95
 
96
96
 
97
+ @pytest.mark.parametrize(
98
+ ("status", "error_type"),
99
+ [
100
+ (400, "TooManyRequestsException"),
101
+ (400, "LimitExceededException"),
102
+ (500, "InternalErrorException"),
103
+ (500, "com.amazonaws.cognito#InternalErrorException"),
104
+ ],
105
+ )
106
+ async def test_passing_cognito_failure_is_not_an_auth_error(
107
+ auth: CognitoSrpAuth, cloud: FakeCloud, status: int, error_type: str
108
+ ) -> None:
109
+ """Throttling or an outage must not look like a wrong password."""
110
+ cloud.reply_sequence(
111
+ COGNITO_PATH, [(200, CHALLENGE), (status, {"__type": error_type, "message": "busy"})]
112
+ )
113
+ with pytest.raises(HeltyCloudConnectionError, match="busy"):
114
+ await auth.authenticate("user@example.com", "secret")
115
+
116
+
117
+ @pytest.mark.parametrize(
118
+ "error_type",
119
+ ["UserNotFoundException", "PasswordResetRequiredException", "UserNotConfirmedException"],
120
+ )
121
+ async def test_refused_account_is_auth_error(
122
+ auth: CognitoSrpAuth, cloud: FakeCloud, error_type: str
123
+ ) -> None:
124
+ cloud.reply(COGNITO_PATH, {"__type": error_type, "message": "refused"}, status=400)
125
+ with pytest.raises(HeltyCloudAuthError, match="refused"):
126
+ await auth.authenticate("user@example.com", "secret")
127
+
128
+
97
129
  async def test_unexpected_challenge(auth: CognitoSrpAuth, cloud: FakeCloud) -> None:
98
130
  """Should they ever enable MFA, better a clear error."""
99
131
  cloud.reply(COGNITO_PATH, {"ChallengeName": "SMS_MFA", "ChallengeParameters": {}})
File without changes
File without changes
File without changes