Files
Dorod-Sky/skyvern/forge/sdk/schemas/credentials.py

365 lines
14 KiB
Python
Raw Normal View History

from datetime import datetime
from enum import StrEnum
from typing import Self
from fastapi import status
from pydantic import BaseModel, ConfigDict, Field, model_validator
from skyvern.exceptions import SkyvernHTTPException
from skyvern.utils.url_validators import validate_url
class CredentialVaultType(StrEnum):
BITWARDEN = "bitwarden"
AZURE_VAULT = "azure_vault"
CUSTOM = "custom"
class CredentialType(StrEnum):
2025-05-01 07:03:56 -07:00
"""Type of credential stored in the system."""
PASSWORD = "password"
CREDIT_CARD = "credit_card"
SECRET = "secret"
class TotpType(StrEnum):
"""Type of 2FA/TOTP method used."""
AUTHENTICATOR = "authenticator"
EMAIL = "email"
TEXT = "text"
NONE = "none"
class PasswordCredentialResponse(BaseModel):
"""Response model for password credentials — non-sensitive fields only.
SECURITY: Must NEVER include password, TOTP secret, or TOTP identifier.
"""
2025-05-01 07:03:56 -07:00
username: str = Field(..., description="The username associated with the credential", examples=["user@example.com"])
totp_type: TotpType = Field(
TotpType.NONE,
description="Type of 2FA method used for this credential",
examples=[TotpType.AUTHENTICATOR],
)
totp_identifier: str | None = Field(
default=None,
description="Identifier (email or phone number) used to fetch TOTP codes",
examples=["user@example.com", "+14155550123"],
)
class CreditCardCredentialResponse(BaseModel):
"""Response model for credit card credentials — non-sensitive fields only.
SECURITY: Must NEVER include full card number, CVV, expiration date, or card holder name.
"""
2025-05-01 07:03:56 -07:00
last_four: str = Field(..., description="Last four digits of the credit card number", examples=["1234"])
brand: str = Field(..., description="Brand of the credit card", examples=["visa"])
class SecretCredentialResponse(BaseModel):
"""Response model for secret credentials — non-sensitive fields only.
SECURITY: Must NEVER include the secret_value.
"""
secret_label: str | None = Field(default=None, description="Optional label for the stored secret")
class PasswordCredential(BaseModel):
2025-05-01 07:03:56 -07:00
"""Base model for password credentials."""
password: str = Field(..., description="The password value", examples=["securepassword123"])
username: str = Field(..., description="The username associated with the credential", examples=["user@example.com"])
totp: str | None = Field(
None,
description="Optional TOTP (Time-based One-Time Password) string used to generate 2FA codes",
examples=["JBSWY3DPEHPK3PXP"],
)
totp_type: TotpType = Field(
TotpType.NONE,
description="Type of 2FA method used for this credential",
examples=[TotpType.AUTHENTICATOR],
)
totp_identifier: str | None = Field(
default=None,
description="Identifier (email or phone number) used to fetch TOTP codes",
examples=["user@example.com", "+14155550123"],
)
class NonEmptyPasswordCredential(PasswordCredential):
2025-05-01 07:03:56 -07:00
"""Password credential model that requires non-empty values."""
password: str = Field(
..., min_length=1, description="The password value (must not be empty)", examples=["securepassword123"]
)
username: str = Field(
...,
min_length=1,
description="The username associated with the credential (must not be empty)",
examples=["user@example.com"],
)
class CreditCardCredential(BaseModel):
2025-05-01 07:03:56 -07:00
"""Base model for credit card credentials."""
card_number: str = Field(..., description="The full credit card number", examples=["4111111111111111"])
card_cvv: str = Field(..., description="The card's CVV (Card Verification Value)", examples=["123"])
card_exp_month: str = Field(..., description="The card's expiration month", examples=["12"])
card_exp_year: str = Field(..., description="The card's expiration year", examples=["2025"])
card_brand: str = Field(..., description="The card's brand", examples=["visa"])
card_holder_name: str = Field(..., description="The name of the card holder", examples=["John Doe"])
class NonEmptyCreditCardCredential(CreditCardCredential):
2025-05-01 07:03:56 -07:00
"""Credit card credential model that requires non-empty values."""
card_number: str = Field(
..., min_length=1, description="The full credit card number (must not be empty)", examples=["4111111111111111"]
)
card_cvv: str = Field(..., min_length=1, description="The card's CVV (must not be empty)", examples=["123"])
card_exp_month: str = Field(
..., min_length=1, description="The card's expiration month (must not be empty)", examples=["12"]
)
card_exp_year: str = Field(
..., min_length=1, description="The card's expiration year (must not be empty)", examples=["2025"]
)
card_brand: str = Field(..., min_length=1, description="The card's brand (must not be empty)", examples=["visa"])
card_holder_name: str = Field(
..., min_length=1, description="The name of the card holder (must not be empty)", examples=["John Doe"]
)
class SecretCredential(BaseModel):
"""Generic secret credential."""
secret_value: str = Field(..., min_length=1, description="The secret value", examples=["sk-abc123"])
secret_label: str | None = Field(default=None, description="Optional label describing the secret")
class CredentialItem(BaseModel):
2025-05-01 07:03:56 -07:00
"""Model representing a credential item in the system."""
item_id: str = Field(..., description="Unique identifier for the credential item", examples=["cred_1234567890"])
name: str = Field(..., description="Name of the credential", examples=["Skyvern Login"])
credential_type: CredentialType = Field(..., description="Type of the credential. Eg password, credit card, etc.")
credential: PasswordCredential | CreditCardCredential | SecretCredential = Field(
..., description="The actual credential data"
)
class CreateCredentialRequest(BaseModel):
2025-05-01 07:03:56 -07:00
"""Request model for creating a new credential."""
name: str = Field(..., description="Name of the credential", examples=["Amazon Login"])
2025-05-01 07:03:56 -07:00
credential_type: CredentialType = Field(..., description="Type of credential to create")
credential: NonEmptyPasswordCredential | NonEmptyCreditCardCredential | SecretCredential = Field(
2025-05-01 07:03:56 -07:00
...,
description="The credential data to store",
examples=[{"username": "user@example.com", "password": "securepassword123"}],
)
class CredentialResponse(BaseModel):
2025-05-01 07:03:56 -07:00
"""Response model for credential operations."""
2025-05-01 07:03:56 -07:00
credential_id: str = Field(..., description="Unique identifier for the credential", examples=["cred_1234567890"])
credential: PasswordCredentialResponse | CreditCardCredentialResponse | SecretCredentialResponse = Field(
2025-05-01 07:03:56 -07:00
..., description="The credential data"
)
credential_type: CredentialType = Field(..., description="Type of the credential")
name: str = Field(..., description="Name of the credential", examples=["Amazon Login"])
browser_profile_id: str | None = Field(default=None, description="Browser profile ID linked to this credential")
tested_url: str | None = Field(default=None, description="Login page URL used during the credential test")
2025-05-01 07:03:56 -07:00
class Credential(BaseModel):
"""Database model for credentials."""
2025-05-01 07:03:56 -07:00
model_config = ConfigDict(from_attributes=True)
2025-05-01 07:03:56 -07:00
credential_id: str = Field(..., description="Unique identifier for the credential", examples=["cred_1234567890"])
organization_id: str = Field(
..., description="ID of the organization that owns the credential", examples=["o_1234567890"]
)
name: str = Field(..., description="Name of the credential", examples=["Skyvern Login"])
vault_type: CredentialVaultType | None = Field(..., description="Where the secret is stored: Bitwarden vs Azure")
2025-05-01 07:03:56 -07:00
item_id: str = Field(..., description="ID of the associated credential item", examples=["item_1234567890"])
credential_type: CredentialType = Field(..., description="Type of the credential. Eg password, credit card, etc.")
username: str | None = Field(..., description="For password credentials: the username")
totp_type: TotpType = Field(
TotpType.NONE,
description="Type of 2FA method used for this credential",
examples=[TotpType.AUTHENTICATOR],
)
totp_identifier: str | None = Field(
default=None,
description="Identifier (email or phone number) used to fetch TOTP codes",
examples=["user@example.com", "+14155550123"],
)
card_last4: str | None = Field(..., description="For credit_card credentials: the last four digits of the card")
card_brand: str | None = Field(..., description="For credit_card credentials: the card brand")
secret_label: str | None = Field(default=None, description="For secret credentials: optional label")
browser_profile_id: str | None = Field(default=None, description="Browser profile ID linked to this credential")
tested_url: str | None = Field(default=None, description="Login page URL used during the credential test")
2025-05-01 07:03:56 -07:00
created_at: datetime = Field(..., description="Timestamp when the credential was created")
modified_at: datetime = Field(..., description="Timestamp when the credential was last modified")
deleted_at: datetime | None = Field(None, description="Timestamp when the credential was deleted, if applicable")
class UpdateCredentialRequest(BaseModel):
"""Request model for updating credential metadata."""
name: str = Field(
...,
min_length=1,
description="New name for the credential",
examples=["My Updated Credential"],
)
tested_url: str | None = Field(
default=None,
description="Optional login page URL associated with this credential",
examples=["https://example.com/login"],
)
class TestCredentialRequest(BaseModel):
"""Request model for testing a credential by logging into a website."""
url: str = Field(
...,
description="The login page URL to test the credential against",
examples=["https://example.com/login"],
)
save_browser_profile: bool = Field(
default=True,
description="Whether to save the browser profile after a successful login test",
)
@model_validator(mode="after")
def validate_url(self) -> Self:
result = validate_url(self.url)
if result is None:
raise SkyvernHTTPException(message=f"Invalid URL: {self.url}", status_code=status.HTTP_400_BAD_REQUEST)
self.url = result
return self
class TestLoginRequest(BaseModel):
"""Request model for testing a login with inline credentials (no saved credential required)."""
url: str = Field(
...,
description="The login page URL to test against",
examples=["https://example.com/login"],
)
username: str = Field(
...,
min_length=1,
description="The username to test",
examples=["user@example.com"],
)
password: str = Field(
...,
min_length=1,
description="The password to test",
examples=["securepassword123"],
)
totp: str | None = Field(
default=None,
description="Optional TOTP secret for 2FA",
)
totp_type: TotpType = Field(
default=TotpType.NONE,
description="Type of 2FA method",
)
totp_identifier: str | None = Field(
default=None,
description="Identifier (email or phone) for TOTP",
)
@model_validator(mode="after")
def validate_url(self) -> Self:
result = validate_url(self.url)
if result is None:
raise SkyvernHTTPException(message=f"Invalid URL: {self.url}", status_code=status.HTTP_400_BAD_REQUEST)
self.url = result
return self
class TestCredentialResponse(BaseModel):
"""Response model for a credential test initiation."""
credential_id: str = Field(..., description="The credential being tested")
workflow_run_id: str = Field(
...,
description="The workflow run ID to poll for test status",
examples=["wr_1234567890"],
)
status: str = Field(
...,
description="Current status of the test",
examples=["running"],
)
class TestLoginResponse(BaseModel):
"""Response model for an inline login test (no saved credential)."""
credential_id: str = Field(
...,
description="The temporary credential ID created for this test",
)
workflow_run_id: str = Field(
...,
description="The workflow run ID to poll for test status",
examples=["wr_1234567890"],
)
status: str = Field(
...,
description="Current status of the test",
examples=["running"],
)
class TestCredentialStatusResponse(BaseModel):
"""Response model for credential test status polling."""
credential_id: str = Field(..., description="The credential being tested")
workflow_run_id: str = Field(..., description="The workflow run ID")
status: str = Field(
...,
description="Current status: created, running, completed, failed, timed_out",
examples=["completed"],
)
failure_reason: str | None = Field(default=None, description="Reason for failure, if any")
browser_profile_id: str | None = Field(
default=None,
description="Browser profile ID created from successful test.",
)
tested_url: str | None = Field(
default=None,
description="Login page URL used during the credential test.",
)
browser_profile_failure_reason: str | None = Field(
default=None,
description="Reason the browser profile failed to save, if applicable.",
)
class CancelTestResponse(BaseModel):
"""Response model for canceling a credential test."""
status: str = Field(
...,
description="Result of the cancellation: 'canceled' or 'cancel_failed'",
examples=["canceled"],
)