vhi-python 0.1.1__tar.gz → 0.1.2__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.
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vhi-python
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: A Python client library for the Vhi API, handling authentication, MFA callbacks, claims retrieval, and document downloads.
5
5
  Author-email: Agent <agent@example.com>
6
- License-Expression: MIT
6
+ License: MIT
7
7
  Project-URL: Homepage, https://github.com/agent/vhi-python
8
8
  Project-URL: Repository, https://github.com/agent/vhi-python.git
9
9
  Classifier: Programming Language :: Python :: 3
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vhi-python"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  description = "A Python client library for the Vhi API, handling authentication, MFA callbacks, claims retrieval, and document downloads."
9
9
  readme = "README.md"
10
10
  authors = [
11
11
  { name = "Agent", email = "agent@example.com" }
12
12
  ]
13
- license = "MIT"
13
+ license = { text = "MIT" }
14
14
  classifiers = [
15
15
  "Programming Language :: Python :: 3",
16
16
  "Operating System :: OS Independent",
@@ -64,41 +64,48 @@ class VhiClient:
64
64
 
65
65
  payload = {
66
66
  "username": self.username,
67
- "password": self.password
67
+ "usercred": self.password
68
68
  }
69
69
 
70
70
  # 1. Primary Authentication
71
- # The path here is a presumed standard, will likely be updated if the actual path varies
72
- login_url = f"{self.apis_base_url}/auth/v1/login"
71
+ login_url = f"{self.apis_base_url}/api/myvhilogin/login"
73
72
 
74
73
  response = self.session.post(login_url, json=payload, headers=headers)
75
74
 
76
75
  if response.status_code == 200:
77
- self.is_authenticated = True
78
- return
79
-
80
- elif response.status_code == 202:
81
- # MFA Challenge Required
82
76
  try:
83
77
  data = response.json()
84
78
  except ValueError:
85
- raise VhiApiError("MFA response was not valid JSON", status_code=response.status_code)
79
+ raise VhiApiError("Login response was not valid JSON", status_code=response.status_code)
86
80
 
87
- if data.get("mfa_required"):
88
- state_token = data.get("state_token")
81
+ # The HAR trace uses a deeply nested response structure or a flattened one depending on Gateway
82
+ status_val = data.get("status") or data.get("data", {}).get("status")
83
+
84
+ if status_val == "MFA_REQUIRED":
85
+ state_token = data.get("stateToken") or data.get("data", {}).get("stateToken")
86
+
87
+ # Attempt to extract the verify url from the first factor
88
+ try:
89
+ factors = data.get("factors") or data.get("data", {}).get("_embedded", {}).get("factors", [])
90
+ verify_url = factors[0]["_links"]["verify"]["href"]
91
+ except (IndexError, KeyError):
92
+ # Fallback URL if extraction fails
93
+ verify_url = f"https://admin-digital.vhi.ie/api/v1/authn/factors/verify"
94
+
89
95
  if not state_token:
90
96
  raise VhiApiError("MFA required but no state_token provided in response")
91
97
 
92
- self._handle_mfa_challenge(state_token, headers)
98
+ self._handle_mfa_challenge(state_token, verify_url, headers)
93
99
  else:
94
- raise VhiApiError("Received 202 but not MFA challenge", status_code=response.status_code, response=response)
100
+ self.is_authenticated = True
101
+ return
95
102
 
96
103
  elif response.status_code == 401:
97
104
  raise VhiAuthenticationError("Invalid credentials provided.")
98
105
  else:
99
106
  raise VhiApiError(f"Login failed with status {response.status_code}", status_code=response.status_code, response=response)
100
107
 
101
- def _handle_mfa_challenge(self, state_token: str, base_headers: dict):
108
+ def _handle_mfa_challenge(self, state_token: str, verify_url: str, base_headers: dict):
102
109
  """
103
110
  Internal method to submit the MFA code.
104
111
  """
@@ -111,13 +118,12 @@ class VhiClient:
111
118
  if not otp_code:
112
119
  raise VhiAuthenticationError("MFA callback did not return a valid OTP code.")
113
120
 
114
- mfa_url = f"{self.apis_base_url}/auth/v1/mfa/verify"
115
121
  payload = {
116
- "otp": otp_code,
117
- "state_token": state_token
122
+ "passCode": otp_code,
123
+ "stateToken": state_token
118
124
  }
119
125
 
120
- response = self.session.post(mfa_url, json=payload, headers=base_headers)
126
+ response = self.session.post(verify_url, json=payload, headers=base_headers)
121
127
 
122
128
  if response.status_code == 200:
123
129
  self.is_authenticated = True
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vhi-python
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: A Python client library for the Vhi API, handling authentication, MFA callbacks, claims retrieval, and document downloads.
5
5
  Author-email: Agent <agent@example.com>
6
- License-Expression: MIT
6
+ License: MIT
7
7
  Project-URL: Homepage, https://github.com/agent/vhi-python
8
8
  Project-URL: Repository, https://github.com/agent/vhi-python.git
9
9
  Classifier: Programming Language :: Python :: 3
@@ -9,4 +9,5 @@ src/vhi_python.egg-info/SOURCES.txt
9
9
  src/vhi_python.egg-info/dependency_links.txt
10
10
  src/vhi_python.egg-info/requires.txt
11
11
  src/vhi_python.egg-info/top_level.txt
12
- tests/test_client.py
12
+ tests/test_client.py
13
+ tests/test_integration.py
@@ -11,8 +11,8 @@ def test_login_success_no_mfa():
11
11
 
12
12
  responses.add(
13
13
  responses.POST,
14
- "https://apis.vhi.ie/auth/v1/login",
15
- json={"token": "test-token"},
14
+ "https://apis.vhi.ie/api/myvhilogin/login",
15
+ json={"status": "SUCCESS", "sessionToken": "test-token"},
16
16
  status=200
17
17
  )
18
18
 
@@ -26,19 +26,23 @@ def test_login_requires_mfa_success():
26
26
 
27
27
  client = VhiClient("test@example.com", "password", mfa_callback=mfa_callback)
28
28
 
29
- # Mock primary login returning 202
29
+ # Mock primary login returning 200 with MFA_REQUIRED
30
30
  responses.add(
31
31
  responses.POST,
32
- "https://apis.vhi.ie/auth/v1/login",
33
- json={"mfa_required": True, "state_token": "state-123"},
34
- status=202
32
+ "https://apis.vhi.ie/api/myvhilogin/login",
33
+ json={
34
+ "status": "MFA_REQUIRED",
35
+ "stateToken": "state-123",
36
+ "factors": [{"_links": {"verify": {"href": "https://admin-digital.vhi.ie/api/v1/authn/factors/verify"}}}]
37
+ },
38
+ status=200
35
39
  )
36
40
 
37
- # Mock MFA verify returning 200
41
+ # Mock MFA verify returning 200 SUCCESS
38
42
  responses.add(
39
43
  responses.POST,
40
- "https://apis.vhi.ie/auth/v1/mfa/verify",
41
- json={"token": "final-token"},
44
+ "https://admin-digital.vhi.ie/api/v1/authn/factors/verify",
45
+ json={"status": "SUCCESS", "sessionToken": "final-token"},
42
46
  status=200
43
47
  )
44
48
 
@@ -51,7 +55,7 @@ def test_login_invalid_credentials():
51
55
 
52
56
  responses.add(
53
57
  responses.POST,
54
- "https://apis.vhi.ie/auth/v1/login",
58
+ "https://apis.vhi.ie/api/myvhilogin/login",
55
59
  status=401
56
60
  )
57
61
 
@@ -0,0 +1,31 @@
1
+ import os
2
+ import pytest
3
+ from vhi.client import VhiClient
4
+ from vhi.exceptions import VhiMfaRequiredError, VhiAuthenticationError
5
+
6
+ @pytest.mark.skipif(
7
+ not os.getenv("VHI_USERNAME") or not os.getenv("VHI_PASSWORD"),
8
+ reason="Integration tests require VHI_USERNAME and VHI_PASSWORD environment variables"
9
+ )
10
+ def test_live_login():
11
+ """
12
+ Test live login against Vhi API.
13
+ We expect to hit either a successful login or an MFA challenge.
14
+ """
15
+ username = os.environ["VHI_USERNAME"]
16
+ password = os.environ["VHI_PASSWORD"]
17
+
18
+ # We initialize without an mfa_callback to ensure it raises VhiMfaRequiredError
19
+ # if MFA is prompted, which validates the primary credentials were correct.
20
+ client = VhiClient(username, password)
21
+
22
+ try:
23
+ client.login()
24
+ # If it succeeds without MFA, we are authenticated.
25
+ assert client.is_authenticated is True
26
+ except VhiMfaRequiredError:
27
+ # If it throws MFA required, the API accepted the usercred and triggered Okta MFA.
28
+ # This confirms our primary login endpoint and payload reverse-engineering is correct.
29
+ pass
30
+ except VhiAuthenticationError as e:
31
+ pytest.fail(f"Integration login failed - incorrect credentials: {e}")
File without changes
File without changes
File without changes