Skip to content

Commit 2e3ff3b

Browse files
committed
verify logic, models and tests
1 parent 20051fd commit 2e3ff3b

File tree

17 files changed

+439
-108
lines changed

17 files changed

+439
-108
lines changed

http_client/src/vonage_http_client/http_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,6 @@ def _parse_response(self, response: Response) -> Union[dict, None]:
224224
f'Response received from {response.url} with status code: {response.status_code}; headers: {response.headers}'
225225
)
226226
self._last_response = response
227-
content_type = response.headers['Content-Type'].split(';', 1)[0]
228227
if 200 <= response.status_code < 300:
229228
if response.status_code == 204:
230229
return None
@@ -233,6 +232,7 @@ def _parse_response(self, response: Response) -> Union[dict, None]:
233232
except JSONDecodeError:
234233
return None
235234
if response.status_code >= 400:
235+
content_type = response.headers['Content-Type'].split(';', 1)[0]
236236
logger.warning(
237237
f'Http Response Error! Status code: {response.status_code}; content: {repr(response.text)}; from url: {response.url}'
238238
)

verify/src/vonage_verify/language_codes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class LanguageCode(str, Enum):
4343
zh_tw = 'zh-tw'
4444

4545

46-
class Psd2LanguageCode(Enum):
46+
class Psd2LanguageCode(str, Enum):
4747
en_gb = 'en-gb'
4848
bg_bg = 'bg-bg'
4949
cs_cz = 'cs-cz'
Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
1+
from .enums import ChannelType, Locale
12
from .errors import VerifyError
2-
from .enums import VerifyChannel, VerifyLocale
33
from .requests import (
4+
EmailChannel,
5+
SilentAuthChannel,
6+
SmsChannel,
47
VerifyRequest,
5-
SilentAuthWorkflow,
6-
SmsWorkflow,
7-
WhatsappWorkflow,
8-
VoiceWorkflow,
9-
EmailWorkflow,
8+
VoiceChannel,
9+
WhatsappChannel,
1010
)
11-
from .responses import (
12-
StartVerificationResponse,
13-
)
14-
from .verify_v2 import Verify
11+
from .responses import StartVerificationResponse
12+
from .verify_v2 import VerifyV2
1513

1614
__all__ = [
17-
'Verify',
15+
'VerifyV2',
1816
'VerifyError',
19-
'VerifyChannel',
20-
'VerifyLocale',
17+
'ChannelType',
18+
'Locale',
2119
'VerifyRequest',
22-
'SilentAuthWorkflow',
23-
'SmsWorkflow',
24-
'WhatsappWorkflow',
25-
'VoiceWorkflow',
26-
'EmailWorkflow',
20+
'SilentAuthChannel',
21+
'SmsChannel',
22+
'WhatsappChannel',
23+
'VoiceChannel',
24+
'EmailChannel',
2725
'StartVerificationResponse',
2826
]

verify_v2/src/vonage_verify_v2/enums.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
from enum import Enum
22

33

4-
class VerifyChannel(Enum):
4+
class ChannelType(str, Enum):
55
SILENT_AUTH = 'silent_auth'
66
SMS = 'sms'
77
WHATSAPP = 'whatsapp'
88
VOICE = 'voice'
99
EMAIL = 'email'
1010

1111

12-
class VerifyLocale(Enum):
12+
class Locale(str, Enum):
1313
EN_US = 'en-us'
1414
EN_GB = 'en-gb'
1515
ES_ES = 'es-es'
Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,29 @@
1-
from typing import List, Optional, Union
21
from re import search
2+
from typing import List, Optional, Union
33

4-
from pydantic import (
5-
BaseModel,
6-
Field,
7-
field_validator,
8-
model_validator,
9-
)
4+
from pydantic import BaseModel, Field, field_validator, model_validator
105
from vonage_utils.types.phone_number import PhoneNumber
116

12-
from .enums import VerifyChannel, VerifyLocale
7+
from .enums import ChannelType, Locale
138
from .errors import VerifyError
149

1510

16-
class Workflow(BaseModel):
17-
channel: VerifyChannel
11+
class Channel(BaseModel):
1812
to: PhoneNumber
1913

2014

21-
class SilentAuthWorkflow(Workflow):
15+
class SilentAuthChannel(Channel):
2216
redirect_url: Optional[str] = None
2317
sandbox: Optional[bool] = None
18+
channel: ChannelType = ChannelType.SILENT_AUTH
2419

2520

26-
class SmsWorkflow(Workflow):
21+
class SmsChannel(Channel):
2722
from_: Optional[Union[PhoneNumber, str]] = Field(None, serialization_alias='from')
2823
entity_id: Optional[str] = Field(None, pattern=r'^[0-9]{1,20}$')
2924
content_id: Optional[str] = Field(None, pattern=r'^[0-9]{1,20}$')
3025
app_hash: Optional[str] = Field(None, min_length=11, max_length=11)
26+
channel: ChannelType = ChannelType.SMS
3127

3228
@field_validator('from_')
3329
@classmethod
@@ -37,45 +33,71 @@ def check_valid_from_field(cls, v):
3733
and type(v) is not PhoneNumber
3834
and not search(r'^[a-zA-Z0-9]{1,15}$', v)
3935
):
40-
raise VerifyError(f'You must specify a valid "from" value if included.')
36+
raise VerifyError(
37+
'You must specify a valid "from_" value if included. '
38+
'It must be a valid phone number without the leading +, or a string of 1-15 alphanumeric characters. '
39+
f'You set "from_": "{v}".'
40+
)
41+
return v
4142

4243

43-
class WhatsappWorkflow(Workflow):
44+
class WhatsappChannel(Channel):
4445
from_: Union[PhoneNumber, str] = Field(..., serialization_alias='from')
46+
channel: ChannelType = ChannelType.WHATSAPP
4547

4648
@field_validator('from_')
4749
@classmethod
4850
def check_valid_sender(cls, v):
4951
if type(v) is not PhoneNumber and not search(r'^[a-zA-Z0-9]{1,15}$', v):
50-
raise VerifyError(f'You must specify a valid "from" value.')
52+
raise VerifyError(
53+
f'You must specify a valid "from_" value. '
54+
'It must be a valid phone number without the leading +, or a string of 1-15 alphanumeric characters. '
55+
f'You set "from_": "{v}".'
56+
)
57+
return v
5158

5259

53-
class VoiceWorkflow(Workflow):
54-
@model_validator(mode='after')
55-
def remove_from_field_from_voice(self):
56-
self.from_ = None
57-
return self
60+
class VoiceChannel(Channel):
61+
channel: ChannelType = ChannelType.VOICE
5862

5963

60-
class EmailWorkflow(Workflow):
64+
class EmailChannel(Channel):
6165
to: str
6266
from_: Optional[str] = Field(None, serialization_alias='from')
67+
channel: ChannelType = ChannelType.EMAIL
6368

6469

6570
class VerifyRequest(BaseModel):
6671
brand: str = Field(..., min_length=1, max_length=16)
67-
workflow: List[Workflow]
68-
locale: Optional[VerifyLocale] = None
72+
workflow: List[
73+
Union[
74+
SilentAuthChannel,
75+
SmsChannel,
76+
WhatsappChannel,
77+
VoiceChannel,
78+
EmailChannel,
79+
]
80+
]
81+
locale: Optional[Locale] = None
6982
channel_timeout: Optional[int] = Field(None, ge=60, le=900)
7083
client_ref: Optional[str] = Field(None, min_length=1, max_length=16)
7184
code_length: Optional[int] = Field(None, ge=4, le=10)
7285
code: Optional[str] = Field(None, pattern=r'^[a-zA-Z0-9]{4,10}$')
7386

7487
@model_validator(mode='after')
7588
def remove_fields_if_only_silent_auth(self):
76-
if len(self.workflow) == 1 and isinstance(self.workflow[0], SilentAuthWorkflow):
89+
if len(self.workflow) == 1 and isinstance(self.workflow[0], SilentAuthChannel):
7790
self.locale = None
78-
self.client_ref = None
7991
self.code_length = None
8092
self.code = None
8193
return self
94+
95+
@model_validator(mode='after')
96+
def check_silent_auth_first_if_present(self):
97+
if len(self.workflow) > 1:
98+
for i in range(1, len(self.workflow)):
99+
if isinstance(self.workflow[i], SilentAuthChannel):
100+
raise VerifyError(
101+
'If using Silent Authentication, it must be the first channel in the "workflow" list.'
102+
)
103+
return self

verify_v2/src/vonage_verify_v2/responses.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@
66
class StartVerificationResponse(BaseModel):
77
request_id: str
88
check_url: Optional[str] = None
9+
10+
11+
class CheckCodeResponse(BaseModel):
12+
request_id: str
13+
status: str
Lines changed: 8 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,11 @@
1-
from typing import List, Optional, Union
2-
31
from pydantic import validate_call
42
from vonage_http_client.http_client import HttpClient
53

6-
from .errors import VerifyError
7-
from .requests import (
8-
VerifyRequest,
9-
SilentAuthWorkflow,
10-
SmsWorkflow,
11-
WhatsappWorkflow,
12-
VoiceWorkflow,
13-
EmailWorkflow,
14-
)
15-
from .responses import (
16-
CheckCodeResponse,
17-
StartVerificationResponse,
18-
VerifyControlStatus,
19-
VerifyStatus,
20-
)
4+
from .requests import VerifyRequest
5+
from .responses import CheckCodeResponse, StartVerificationResponse
216

227

23-
class Verify:
8+
class VerifyV2:
249
"""Calls Vonage's Verify V2 API."""
2510

2611
def __init__(self, http_client: HttpClient) -> None:
@@ -47,14 +32,6 @@ def start_verification(
4732

4833
return StartVerificationResponse(**response)
4934

50-
####################################################################################################
51-
52-
####################################################################################################
53-
54-
####################################################################################################
55-
56-
####################################################################################################
57-
5835
@validate_call
5936
def check_code(self, request_id: str, code: str) -> CheckCodeResponse:
6037
"""Check a verification code.
@@ -67,55 +44,26 @@ def check_code(self, request_id: str, code: str) -> CheckCodeResponse:
6744
CheckCodeResponse: The response object containing the verification result.
6845
"""
6946
response = self._http_client.post(
70-
self._http_client.api_host,
71-
'/verify/check/json',
72-
{'request_id': request_id, 'code': code},
73-
self._auth_type,
74-
self._sent_data_type,
47+
self._http_client.api_host, f'/v2/verify/{request_id}', {'code': code}
7548
)
76-
self._check_for_error(response)
7749
return CheckCodeResponse(**response)
7850

7951
@validate_call
80-
def cancel_verification(self, request_id: str) -> VerifyControlStatus:
52+
def cancel_verification(self, request_id: str) -> None:
8153
"""Cancel a verification request.
8254
8355
Args:
8456
request_id (str): The request ID.
85-
86-
Returns:
87-
VerifyControlStatus: The response object containing details of the submitted
88-
verification control.
8957
"""
90-
response = self._http_client.post(
91-
self._http_client.api_host,
92-
'/verify/control/json',
93-
{'request_id': request_id, 'cmd': 'cancel'},
94-
self._auth_type,
95-
self._sent_data_type,
96-
)
97-
self._check_for_error(response)
98-
99-
return VerifyControlStatus(**response)
58+
self._http_client.delete(self._http_client.api_host, f'/v2/verify/{request_id}')
10059

10160
@validate_call
102-
def trigger_next_workflow(self, request_id: str) -> VerifyControlStatus:
61+
def trigger_next_workflow(self, request_id: str) -> None:
10362
"""Trigger the next workflow event in the verification process.
10463
10564
Args:
10665
request_id (str): The request ID.
107-
108-
Returns:
109-
VerifyControlStatus: The response object containing details of the submitted
110-
verification control.
11166
"""
11267
response = self._http_client.post(
113-
self._http_client.api_host,
114-
'/verify/control/json',
115-
{'request_id': request_id, 'cmd': 'trigger_next_event'},
116-
self._auth_type,
117-
self._sent_data_type,
68+
self._http_client.api_host, f'/v2/verify/{request_id}'
11869
)
119-
self._check_for_error(response)
120-
121-
return VerifyControlStatus(**response)

verify_v2/tests/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python_tests(dependencies=['verify_v2', 'testutils'])

verify_v2/tests/data/check_code.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"request_id": "36e7060d-2b23-4257-bad0-773ab47f85ef",
3+
"status": "completed"
4+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "https://developer.nexmo.com/api-errors#bad-request",
3+
"title": "Invalid Code",
4+
"detail": "The code you provided does not match the expected value.",
5+
"instance": "475343c0-9239-4715-aed1-72b4a18379d1"
6+
}

0 commit comments

Comments
 (0)