soprano-sdk 0.2.5__py3-none-any.whl → 0.2.7__py3-none-any.whl
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.
- soprano_sdk/authenticators/mfa.py +42 -12
- soprano_sdk/core/constants.py +4 -0
- soprano_sdk/core/engine.py +15 -2
- soprano_sdk/nodes/call_function.py +6 -0
- {soprano_sdk-0.2.5.dist-info → soprano_sdk-0.2.7.dist-info}/METADATA +1 -1
- {soprano_sdk-0.2.5.dist-info → soprano_sdk-0.2.7.dist-info}/RECORD +8 -8
- {soprano_sdk-0.2.5.dist-info → soprano_sdk-0.2.7.dist-info}/WHEEL +0 -0
- {soprano_sdk-0.2.5.dist-info → soprano_sdk-0.2.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -10,6 +10,7 @@ class MFAChallenge(TypedDict):
|
|
|
10
10
|
class MFAState(TypedDict):
|
|
11
11
|
challengeType: Literal['OTP', 'dob']
|
|
12
12
|
post_payload: dict[str, str]
|
|
13
|
+
post_headers: NotRequired[dict[str, str]]
|
|
13
14
|
otpValue: NotRequired[str]
|
|
14
15
|
status: Literal['IN_PROGRESS', 'COMPLETED', 'ERRORED', 'FAILED'] | None
|
|
15
16
|
message: str
|
|
@@ -35,6 +36,10 @@ def enforce_mfa_if_required(state: dict, mfa_config: Optional[MFAConfig] = None)
|
|
|
35
36
|
_mfa : MFAState = state['_mfa']
|
|
36
37
|
if _mfa['status'] == 'COMPLETED':
|
|
37
38
|
return True
|
|
39
|
+
|
|
40
|
+
# Use custom headers if provided, otherwise empty dict
|
|
41
|
+
headers = _mfa.get('post_headers', {})
|
|
42
|
+
|
|
38
43
|
generate_token_response = requests.post(
|
|
39
44
|
build_path(
|
|
40
45
|
base_url=mfa_config.generate_token_base_url,
|
|
@@ -42,7 +47,7 @@ def enforce_mfa_if_required(state: dict, mfa_config: Optional[MFAConfig] = None)
|
|
|
42
47
|
),
|
|
43
48
|
json=_mfa['post_payload'],
|
|
44
49
|
timeout=mfa_config.api_timeout,
|
|
45
|
-
headers=
|
|
50
|
+
headers=headers
|
|
46
51
|
)
|
|
47
52
|
_, error = get_response(generate_token_response)
|
|
48
53
|
|
|
@@ -65,6 +70,9 @@ def mfa_validate_user_input(mfa_config: Optional[MFAConfig] = None, **state: dic
|
|
|
65
70
|
if not state[input_field_name]:
|
|
66
71
|
return False
|
|
67
72
|
|
|
73
|
+
# Use custom headers if provided, otherwise empty dict
|
|
74
|
+
headers = _mfa.get('post_headers', {})
|
|
75
|
+
|
|
68
76
|
post_payload = _mfa['post_payload']
|
|
69
77
|
challenge_field_name = f"{_mfa['challengeType'].lower()}Challenge"
|
|
70
78
|
post_payload.update({challenge_field_name: {"value": state[input_field_name]}})
|
|
@@ -75,7 +83,7 @@ def mfa_validate_user_input(mfa_config: Optional[MFAConfig] = None, **state: dic
|
|
|
75
83
|
),
|
|
76
84
|
json=post_payload,
|
|
77
85
|
timeout=mfa_config.api_timeout,
|
|
78
|
-
headers=
|
|
86
|
+
headers=headers
|
|
79
87
|
)
|
|
80
88
|
_mfa['retry_count'] += 1
|
|
81
89
|
response, error = get_response(validate_token_response)
|
|
@@ -95,7 +103,7 @@ def mfa_validate_user_input(mfa_config: Optional[MFAConfig] = None, **state: dic
|
|
|
95
103
|
),
|
|
96
104
|
json=post_payload,
|
|
97
105
|
timeout=mfa_config.api_timeout,
|
|
98
|
-
headers=
|
|
106
|
+
headers=headers
|
|
99
107
|
)
|
|
100
108
|
if authorize.status_code == 204:
|
|
101
109
|
_mfa['status'] = 'COMPLETED'
|
|
@@ -146,25 +154,47 @@ class MFANodeConfig:
|
|
|
146
154
|
model=model_name,
|
|
147
155
|
initial_message="{{_mfa.message}}",
|
|
148
156
|
instructions="""
|
|
149
|
-
You are an authentication value extractor. Your job is to identify and extract MFA codes from user input.
|
|
157
|
+
You are an authentication value extractor. Your job is to identify and extract MFA codes from user input, or detect if the user wants to cancel the authentication flow.
|
|
150
158
|
|
|
151
159
|
**Task:**
|
|
152
|
-
- Read the user's message
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
- Read the user's message carefully
|
|
161
|
+
- First, check if the user wants to cancel, stop, or exit the authentication process
|
|
162
|
+
- If they want to cancel, output: MFA_CANCELLED:
|
|
163
|
+
- Otherwise, extract ONLY the OTP/MFA code value and output in the format shown below
|
|
164
|
+
|
|
165
|
+
**Cancellation Detection:**
|
|
166
|
+
If the user expresses any intent to cancel, stop, exit, abort, or quit the authentication process, respond with: MFA_CANCELLED
|
|
167
|
+
|
|
168
|
+
Examples of cancellation phrases:
|
|
169
|
+
* "cancel" → MFA_CANCELLED:
|
|
170
|
+
* "I want to stop" → MFA_CANCELLED:
|
|
171
|
+
* "exit" → MFA_CANCELLED:
|
|
172
|
+
* "nevermind" → MFA_CANCELLED:
|
|
173
|
+
* "I don't want to continue" → MFA_CANCELLED:
|
|
174
|
+
* "stop this" → MFA_CANCELLED:
|
|
175
|
+
* "forget it" → MFA_CANCELLED:
|
|
176
|
+
* "abort" → MFA_CANCELLED:
|
|
177
|
+
* "quit" → MFA_CANCELLED:
|
|
178
|
+
|
|
179
|
+
**OTP Capture Examples:**
|
|
180
|
+
* "1234" → MFA_CAPTURED:1234
|
|
181
|
+
* "2345e" → MFA_CAPTURED:2345e
|
|
182
|
+
* "the code is 567890" → MFA_CAPTURED:567890
|
|
183
|
+
* "my otp is 123456" → MFA_CAPTURED:123456
|
|
159
184
|
|
|
160
185
|
**Output Format:**
|
|
161
|
-
MFA_CAPTURED:<
|
|
186
|
+
- For OTP/MFA codes: MFA_CAPTURED:<otp_value>
|
|
187
|
+
- For cancellation: MFA_CANCELLED:
|
|
162
188
|
|
|
163
189
|
"""),
|
|
164
190
|
transitions=[
|
|
165
191
|
dict(
|
|
166
192
|
pattern="MFA_CAPTURED:",
|
|
167
193
|
next=next_node
|
|
194
|
+
),
|
|
195
|
+
dict(
|
|
196
|
+
pattern="MFA_CANCELLED:",
|
|
197
|
+
next="mfa_cancelled"
|
|
168
198
|
)
|
|
169
199
|
]
|
|
170
200
|
)
|
soprano_sdk/core/constants.py
CHANGED
|
@@ -114,6 +114,10 @@ class MFAConfig(BaseSettings):
|
|
|
114
114
|
default=30,
|
|
115
115
|
description="API request timeout in seconds"
|
|
116
116
|
)
|
|
117
|
+
mfa_cancelled_message: str = Field(
|
|
118
|
+
default="Authentication has been cancelled.",
|
|
119
|
+
description="Message to display when user cancels MFA authentication"
|
|
120
|
+
)
|
|
117
121
|
|
|
118
122
|
model_config = SettingsConfigDict(
|
|
119
123
|
case_sensitive=False,
|
soprano_sdk/core/engine.py
CHANGED
|
@@ -39,8 +39,7 @@ class WorkflowEngine:
|
|
|
39
39
|
self.step_map = {step['id']: step for step in self.steps}
|
|
40
40
|
self.mfa_config = (mfa_config or MFAConfig()) if self.mfa_validator_steps else None
|
|
41
41
|
self.data_fields = self.load_data()
|
|
42
|
-
|
|
43
|
-
self.outcomes = self.config['outcomes']
|
|
42
|
+
self.outcomes = self.load_outcomes()
|
|
44
43
|
self.metadata = self.config.get('metadata', {})
|
|
45
44
|
|
|
46
45
|
self.StateType = create_state_model(self.data_fields)
|
|
@@ -260,6 +259,20 @@ class WorkflowEngine:
|
|
|
260
259
|
)
|
|
261
260
|
return data
|
|
262
261
|
|
|
262
|
+
def load_outcomes(self):
|
|
263
|
+
outcomes: list = self.config['outcomes']
|
|
264
|
+
|
|
265
|
+
if self.mfa_config:
|
|
266
|
+
mfa_cancelled_outcome = {
|
|
267
|
+
'id': 'mfa_cancelled',
|
|
268
|
+
'type': 'failure',
|
|
269
|
+
'message': self.mfa_config.mfa_cancelled_message
|
|
270
|
+
}
|
|
271
|
+
outcomes.append(mfa_cancelled_outcome)
|
|
272
|
+
logger.info(f"Auto-generated 'mfa_cancelled' outcome with message: {self.mfa_config.mfa_cancelled_message}")
|
|
273
|
+
|
|
274
|
+
return outcomes
|
|
275
|
+
|
|
263
276
|
|
|
264
277
|
def load_workflow(yaml_path: str, checkpointer=None, config=None, mfa_config: Optional[MFAConfig] = None) -> Tuple[CompiledStateGraph, WorkflowEngine]:
|
|
265
278
|
"""
|
|
@@ -39,11 +39,17 @@ class CallFunctionStrategy(ActionStrategy):
|
|
|
39
39
|
if 'mfa' in self.step_config:
|
|
40
40
|
state['_mfa'] = state.get('_mfa', {})
|
|
41
41
|
state['_mfa']['post_payload'] = dict(transactionId=str(uuid.uuid4()))
|
|
42
|
+
state['_mfa']['post_headers'] = {}
|
|
42
43
|
state['_mfa_config'] = self.engine_context.mfa_config
|
|
43
44
|
template_loader = self.engine_context.get_config_value("template_loader", Environment())
|
|
44
45
|
for k, v in self.step_config['mfa']['payload'].items():
|
|
45
46
|
state['_mfa']['post_payload'][k] = compile_values(template_loader, state, v)
|
|
46
47
|
|
|
48
|
+
# Process headers if provided
|
|
49
|
+
if 'headers' in self.step_config['mfa']:
|
|
50
|
+
for k, v in self.step_config['mfa']['headers'].items():
|
|
51
|
+
state['_mfa']['post_headers'][k] = compile_values(template_loader, state, v)
|
|
52
|
+
|
|
47
53
|
def execute(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
|
48
54
|
from ..utils.tracing import trace_node_execution
|
|
49
55
|
|
|
@@ -6,16 +6,16 @@ soprano_sdk/agents/adaptor.py,sha256=Cm02YKFclrESu-Qq4CTknCgU7KaA7Z_2FspnQDkEVfU
|
|
|
6
6
|
soprano_sdk/agents/factory.py,sha256=Aucfz4rZVKCXMAQtbGAqp1JR8aYwa66mokRmKkKGhYA,6699
|
|
7
7
|
soprano_sdk/agents/structured_output.py,sha256=7DSVzfMPsZAqBwI3v6XL15qG5Gh4jJ-qddcVPaa3gdc,3326
|
|
8
8
|
soprano_sdk/authenticators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
soprano_sdk/authenticators/mfa.py,sha256=
|
|
9
|
+
soprano_sdk/authenticators/mfa.py,sha256=Vew9Nb8pIRTw9hKbEZTH3YScY-fZ_TLq4ZuCzc-wbr8,7387
|
|
10
10
|
soprano_sdk/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
soprano_sdk/core/constants.py,sha256=
|
|
12
|
-
soprano_sdk/core/engine.py,sha256=
|
|
11
|
+
soprano_sdk/core/constants.py,sha256=UPXlRbF7gsOUNOV0Lm0jvgFfgZX7JrsV6n9I5csMfns,3508
|
|
12
|
+
soprano_sdk/core/engine.py,sha256=vM2-nAvDc6Oam-q_BegabBT4uKVMV8DVV10SyJJPgVw,11762
|
|
13
13
|
soprano_sdk/core/rollback_strategies.py,sha256=NjDTtBCZlqyDql5PSwI9SMDLK7_BNlTxbW_cq_5gV0g,7783
|
|
14
14
|
soprano_sdk/core/state.py,sha256=k8ojLfWgjES3p9XWMeGU5s4UK-Xa5T8mS4VtZzTrcDw,2961
|
|
15
15
|
soprano_sdk/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
soprano_sdk/nodes/async_function.py,sha256=v6WujLKm8NXX2iAkJ7Gz_QIVCtWFrpC6nnPyyfuCxXs,9354
|
|
17
17
|
soprano_sdk/nodes/base.py,sha256=idFyOGGPnjsASYnrOF_NIh7eFcSuJqw61EoVN_WCTaU,2360
|
|
18
|
-
soprano_sdk/nodes/call_function.py,sha256=
|
|
18
|
+
soprano_sdk/nodes/call_function.py,sha256=afYBmj5Aditbkvb_7gD3CsXBEEUohcsC1_cdHfcOunE,5847
|
|
19
19
|
soprano_sdk/nodes/collect_input.py,sha256=lEltZOU5ALvc57q8I_4SjzzEapVejy9mS0E73Jf7-sk,23759
|
|
20
20
|
soprano_sdk/nodes/factory.py,sha256=IbBzT4FKBnYw5PuSo7uDONV3HSFtoyqjBQQtXtUY2IY,1756
|
|
21
21
|
soprano_sdk/routing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -29,7 +29,7 @@ soprano_sdk/utils/tracing.py,sha256=gSHeBDLe-MbAZ9rkzpCoGFveeMdR9KLaA6tteB0IWjk,
|
|
|
29
29
|
soprano_sdk/validation/__init__.py,sha256=ImChmO86jYHU90xzTttto2-LmOUOmvY_ibOQaLRz5BA,262
|
|
30
30
|
soprano_sdk/validation/schema.py,sha256=SlC4sq-ueEg0p_8Uox_cgPj9S-0AEEiOOlA1Vsu0DsE,15443
|
|
31
31
|
soprano_sdk/validation/validator.py,sha256=GaCvHvjwVe88Z8yatQsueiPnqtq1oo5uN75gogzpQT0,8940
|
|
32
|
-
soprano_sdk-0.2.
|
|
33
|
-
soprano_sdk-0.2.
|
|
34
|
-
soprano_sdk-0.2.
|
|
35
|
-
soprano_sdk-0.2.
|
|
32
|
+
soprano_sdk-0.2.7.dist-info/METADATA,sha256=L9wKEDqgskIRsFIIhtYJq8gfUZ2TJuwXUqrtb-4qNEI,11297
|
|
33
|
+
soprano_sdk-0.2.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
34
|
+
soprano_sdk-0.2.7.dist-info/licenses/LICENSE,sha256=A1aBauSjPNtVehOXJe3WuvdU2xvM9H8XmigFMm6665s,1073
|
|
35
|
+
soprano_sdk-0.2.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|