Skip to content

Commit 75ce98e

Browse files
authored
Support credit cards in 1Password credential parameters (#3746)
1 parent e3ecc4b commit 75ce98e

File tree

3 files changed

+87
-12
lines changed

3 files changed

+87
-12
lines changed

skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterAddPanel.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { WorkflowParameterInput } from "../../WorkflowParameterInput";
2525
import { ParametersState } from "../types";
2626
import { getDefaultValueForParameterType } from "../workflowEditorUtils";
2727
import { validateBitwardenLoginCredential } from "./util";
28+
import { HelpTooltip } from "@/components/HelpTooltip";
2829

2930
type Props = {
3031
type: WorkflowEditorParameterType;
@@ -273,19 +274,32 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
273274
{type === "credential" && credentialType === "onepassword" && (
274275
<>
275276
<div className="space-y-1">
276-
<Label className="text-xs text-slate-300">Vault ID</Label>
277+
<div className="flex gap-2">
278+
<Label className="text-xs text-slate-300">Vault ID</Label>
279+
<HelpTooltip content="You can find the Vault ID and Item ID in the URL when viewing the item in 1Password on the web." />
280+
</div>
277281
<Input
278282
value={vaultId}
279283
onChange={(e) => setVaultId(e.target.value)}
280284
/>
281285
</div>
282286
<div className="space-y-1">
283-
<Label className="text-xs text-slate-300">Item ID</Label>
287+
<div className="flex gap-2">
288+
<Label className="text-xs text-slate-300">Item ID</Label>
289+
<HelpTooltip content="Supports all 1Password item types: Logins, Passwords, Credit Cards, Secure Notes, and more." />
290+
</div>
284291
<Input
285292
value={itemId}
286293
onChange={(e) => setItemId(e.target.value)}
287294
/>
288295
</div>
296+
<div className="rounded-md bg-slate-800 p-2">
297+
<div className="space-y-1 text-xs text-slate-400">
298+
* Credit Cards: Due to a 1Password limitation, add the
299+
expiration date as a separate text field named "Expire Date"
300+
in the format MM/YYYY (e.g. 09/2027).
301+
</div>
302+
</div>
289303
</>
290304
)}
291305
{type === "credential" && credentialType === "azurevault" && (

skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterEditPanel.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
} from "../types";
3232
import { getDefaultValueForParameterType } from "../workflowEditorUtils";
3333
import { validateBitwardenLoginCredential } from "./util";
34+
import { HelpTooltip } from "@/components/HelpTooltip";
3435

3536
type Props = {
3637
type: WorkflowEditorParameterType;
@@ -357,19 +358,32 @@ function WorkflowParameterEditPanel({
357358
{type === "credential" && credentialType === "onepassword" && (
358359
<>
359360
<div className="space-y-1">
360-
<Label className="text-xs text-slate-300">Vault ID</Label>
361+
<div className="flex gap-2">
362+
<Label className="text-xs text-slate-300">Vault ID</Label>
363+
<HelpTooltip content="You can find the Vault ID and Item ID in the URL when viewing the item in 1Password on the web." />
364+
</div>
361365
<Input
362366
value={vaultId}
363367
onChange={(e) => setVaultId(e.target.value)}
364368
/>
365369
</div>
366370
<div className="space-y-1">
367-
<Label className="text-xs text-slate-300">Item ID</Label>
371+
<div className="flex gap-2">
372+
<Label className="text-xs text-slate-300">Item ID</Label>
373+
<HelpTooltip content="Supports all 1Password item types: Logins, Passwords, Credit Cards, Secure Notes, and more." />
374+
</div>
368375
<Input
369376
value={opItemId}
370377
onChange={(e) => setOpItemId(e.target.value)}
371378
/>
372379
</div>
380+
<div className="rounded-md bg-slate-800 p-2">
381+
<div className="space-y-1 text-xs text-slate-400">
382+
Credit Cards: Due to a 1Password limitation, add the
383+
expiration date as a separate text field named “Expire Date”
384+
in the format MM/YYYY (e.g. 09/2027).
385+
</div>
386+
</div>
373387
</>
374388
)}
375389
{type === "credential" && credentialType === "azurevault" && (

skyvern/forge/sdk/workflow/context_manager.py

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import structlog
55
from jinja2.sandbox import SandboxedEnvironment
6+
from onepassword import ItemFieldType
67
from onepassword.client import Client as OnePasswordClient
78

89
from skyvern.config import settings
@@ -452,24 +453,61 @@ async def register_onepassword_credential_parameter_value(
452453
"context": "These values are placeholders. When you type this in, the real value gets inserted (For security reasons)",
453454
}
454455

455-
# Process all fields
456+
# Process all fields generically so it covers passwords and credit cards
456457
for field in item.fields:
457-
if field.value is None:
458+
if not field.value or field.field_type == ItemFieldType.UNSUPPORTED:
458459
continue
459-
random_secret_id = self.generate_random_secret_id()
460+
461+
# ignore irrelevant fields to avoid confusing AI
462+
if field.id in ["validFrom", "interest", "issuenumber"]:
463+
continue
464+
460465
field_type = field.field_type.value.lower()
461466
if field_type == "totp":
467+
random_secret_id = self.generate_random_secret_id()
462468
totp_secret_id = f"{random_secret_id}_totp"
463469
self.secrets[totp_secret_id] = OnePasswordConstants.TOTP
464470
totp_secret_value = self.totp_secret_value_key(totp_secret_id)
465471
self.secrets[totp_secret_value] = parse_totp_secret(field.value)
466472
self.values[parameter.key]["totp"] = totp_secret_id
473+
elif field.title and field.title.lower() in ["expire date", "expiry date", "expiration date"]:
474+
parts = [part.strip() for part in field.value.strip().split("/")]
475+
476+
if len(parts) == 2:
477+
month, year_part = parts
478+
month = month.zfill(2) # ensure '5' becomes '05'
479+
480+
if len(year_part) == 4:
481+
year = year_part[2:] # 2025 -> 25
482+
else:
483+
year = year_part
484+
485+
self._add_secret_parameter_value(parameter, "card_exp_month", month)
486+
self._add_secret_parameter_value(parameter, "card_exp_year", year)
487+
if len(year) == 2:
488+
self._add_secret_parameter_value(parameter, "card_exp_mmyy", f"{month}/{year}")
489+
self._add_secret_parameter_value(parameter, "card_exp_mmyyyy", f"{month}/20{year}")
490+
else:
491+
# store the 1password-provided value additionally
492+
self._add_secret_parameter_value(parameter, "card_exp", field.value)
493+
else:
494+
# fallback on the 1password-provided value
495+
self._add_secret_parameter_value(parameter, "card_exp", field.value)
467496
else:
468-
# this will be the username or password or other field
469-
key = field.id.replace(" ", "_")
470-
secret_id = f"{random_secret_id}_{key}"
471-
self.secrets[secret_id] = field.value
472-
self.values[parameter.key][key] = secret_id
497+
# using more descriptive keys than 1password provides by default
498+
if field.id == "ccnum":
499+
self._add_secret_parameter_value(parameter, "card_number", field.value)
500+
elif field.id == "cardholder":
501+
self._add_secret_parameter_value(parameter, "card_holder_name", field.value)
502+
elif field.id == "cvv":
503+
self._add_secret_parameter_value(parameter, "card_cvv", field.value)
504+
else:
505+
# this will be the username, password or other fields
506+
self._add_secret_parameter_value(parameter, field.id.replace(" ", "_"), field.value)
507+
508+
# Secure Note support
509+
if item.notes:
510+
self._add_secret_parameter_value(parameter, "notes", item.notes)
473511

474512
async def register_bitwarden_login_credential_parameter_value(
475513
self,
@@ -981,6 +1019,15 @@ async def _get_azure_vault_client_for_organization(organization: Organization) -
9811019
azure_vault_client = AsyncAzureVaultClient.create_default()
9821020
return azure_vault_client
9831021

1022+
def _add_secret_parameter_value(self, parameter: Parameter, key: str, value: str) -> None:
1023+
if parameter.key not in self.values:
1024+
raise ValueError(f"{parameter.key} is missing")
1025+
1026+
random_secret_id = self.generate_random_secret_id()
1027+
secret_id = f"{random_secret_id}_{key}"
1028+
self.secrets[secret_id] = value
1029+
self.values[parameter.key][key] = secret_id
1030+
9841031

9851032
class WorkflowContextManager:
9861033
aws_client: AsyncAWSClient

0 commit comments

Comments
 (0)