diff --git a/skyvern-frontend/src/api/types.ts b/skyvern-frontend/src/api/types.ts
index f91a139a..b7f08654 100644
--- a/skyvern-frontend/src/api/types.ts
+++ b/skyvern-frontend/src/api/types.ts
@@ -500,29 +500,51 @@ export type CreditCardCredentialApiResponse = {
brand: string;
};
+export type SecretCredentialResponse = {
+ secret_label?: string | null;
+};
+
export type CredentialApiResponse = {
credential_id: string;
- credential: PasswordCredentialApiResponse | CreditCardCredentialApiResponse;
- credential_type: "password" | "credit_card";
+ credential:
+ | PasswordCredentialApiResponse
+ | CreditCardCredentialApiResponse
+ | SecretCredentialResponse;
+ credential_type: "password" | "credit_card" | "secret";
name: string;
};
export function isPasswordCredential(
- credential: PasswordCredentialApiResponse | CreditCardCredentialApiResponse,
+ credential:
+ | PasswordCredentialApiResponse
+ | CreditCardCredentialApiResponse
+ | SecretCredentialResponse,
): credential is PasswordCredentialApiResponse {
return "username" in credential;
}
export function isCreditCardCredential(
- credential: PasswordCredentialApiResponse | CreditCardCredentialApiResponse,
+ credential:
+ | PasswordCredentialApiResponse
+ | CreditCardCredentialApiResponse
+ | SecretCredentialResponse,
): credential is CreditCardCredentialApiResponse {
return "last_four" in credential;
}
+export function isSecretCredential(
+ credential:
+ | PasswordCredentialApiResponse
+ | CreditCardCredentialApiResponse
+ | SecretCredentialResponse,
+): credential is SecretCredentialResponse {
+ return !("username" in credential) && !("last_four" in credential);
+}
+
export type CreateCredentialRequest = {
name: string;
- credential_type: "password" | "credit_card";
- credential: PasswordCredential | CreditCardCredential;
+ credential_type: "password" | "credit_card" | "secret";
+ credential: PasswordCredential | CreditCardCredential | SecretCredential;
};
export type PasswordCredential = {
@@ -542,6 +564,11 @@ export type CreditCardCredential = {
card_holder_name: string;
};
+export type SecretCredential = {
+ secret_value: string;
+ secret_label?: string | null;
+};
+
export const OtpType = {
Totp: "totp",
MagicLink: "magic_link",
diff --git a/skyvern-frontend/src/routes/credentials/CredentialItem.tsx b/skyvern-frontend/src/routes/credentials/CredentialItem.tsx
index d1314cb3..2857cbea 100644
--- a/skyvern-frontend/src/routes/credentials/CredentialItem.tsx
+++ b/skyvern-frontend/src/routes/credentials/CredentialItem.tsx
@@ -1,12 +1,17 @@
-import { isPasswordCredential } from "@/api/types";
+import {
+ CredentialApiResponse,
+ isCreditCardCredential,
+ isPasswordCredential,
+ isSecretCredential,
+} from "@/api/types";
import { DeleteCredentialButton } from "./DeleteCredentialButton";
-import { CredentialApiResponse } from "@/api/types";
type Props = {
credential: CredentialApiResponse;
};
function CredentialItem({ credential }: Props) {
+ const credentialData = credential.credential;
const getTotpTypeDisplay = (totpType: string) => {
switch (totpType) {
case "authenticator":
@@ -21,6 +26,69 @@ function CredentialItem({ credential }: Props) {
}
};
+ let credentialDetails = null;
+
+ if (isPasswordCredential(credentialData)) {
+ credentialDetails = (
+
+
+
+
Username/Email
+
Password
+ {credentialData.totp_type !== "none" && (
+
2FA Type
+ )}
+
+
+
{credentialData.username}
+
{"********"}
+ {credentialData.totp_type !== "none" && (
+
+ {getTotpTypeDisplay(credentialData.totp_type)}
+
+ )}
+
+
+
+ );
+ } else if (isCreditCardCredential(credentialData)) {
+ credentialDetails = (
+
+
+
+
+
+ {"************" + credentialData.last_four}
+
+
{credentialData.brand}
+
+
+
+ );
+ } else if (isSecretCredential(credentialData)) {
+ credentialDetails = (
+
+
+
Secret Value
+ {credentialData.secret_label ? (
+
Type
+ ) : null}
+
+
+
{"************"}
+ {credentialData.secret_label ? (
+
{credentialData.secret_label}
+ ) : null}
+
+
+ );
+ }
+
return (
@@ -29,45 +97,7 @@ function CredentialItem({ credential }: Props) {
{credential.credential_id}
- {isPasswordCredential(credential.credential) ? (
-
-
-
-
Username/Email
-
Password
- {credential.credential.totp_type !== "none" && (
-
2FA Type
- )}
-
-
-
{credential.credential.username}
-
{"********"}
- {credential.credential.totp_type !== "none" && (
-
- {getTotpTypeDisplay(credential.credential.totp_type)}
-
- )}
-
-
-
- ) : (
-
-
-
-
-
- {"************" + credential.credential.last_four}
-
-
{credential.credential.brand}
-
-
-
- )}
+ {credentialDetails}
diff --git a/skyvern-frontend/src/routes/credentials/CredentialsList.tsx b/skyvern-frontend/src/routes/credentials/CredentialsList.tsx
index ad9be3f6..2dd5fb68 100644
--- a/skyvern-frontend/src/routes/credentials/CredentialsList.tsx
+++ b/skyvern-frontend/src/routes/credentials/CredentialsList.tsx
@@ -2,7 +2,7 @@ import { Skeleton } from "@/components/ui/skeleton";
import { CredentialItem } from "./CredentialItem";
import { useCredentialsQuery } from "@/routes/workflows/hooks/useCredentialsQuery";
-type CredentialFilter = "password" | "credit_card";
+type CredentialFilter = "password" | "credit_card" | "secret";
type Props = {
filter?: CredentialFilter;
@@ -11,6 +11,7 @@ type Props = {
const EMPTY_MESSAGE: Record
= {
password: "No password credentials stored yet.",
credit_card: "No credit cards stored yet.",
+ secret: "No secrets stored yet.",
};
function CredentialsList({ filter }: Props = {}) {
diff --git a/skyvern-frontend/src/routes/credentials/CredentialsModal.tsx b/skyvern-frontend/src/routes/credentials/CredentialsModal.tsx
index e75c14ed..7dea5ae5 100644
--- a/skyvern-frontend/src/routes/credentials/CredentialsModal.tsx
+++ b/skyvern-frontend/src/routes/credentials/CredentialsModal.tsx
@@ -10,6 +10,7 @@ import {
CredentialModalTypes,
} from "./useCredentialModalState";
import { PasswordCredentialContent } from "./PasswordCredentialContent";
+import { SecretCredentialContent } from "./SecretCredentialContent";
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { CreditCardCredentialContent } from "./CreditCardCredentialContent";
@@ -40,6 +41,12 @@ const CREDIT_CARD_CREDENTIAL_INITIAL_VALUES = {
cardHolderName: "",
};
+const SECRET_CREDENTIAL_INITIAL_VALUES = {
+ name: "",
+ secretLabel: "",
+ secretValue: "",
+};
+
// Function to generate a unique credential name
function generateDefaultCredentialName(existingNames: string[]): string {
const baseName = "credentials";
@@ -73,6 +80,9 @@ function CredentialsModal({ onCredentialCreated }: Props) {
const [creditCardCredentialValues, setCreditCardCredentialValues] = useState(
CREDIT_CARD_CREDENTIAL_INITIAL_VALUES,
);
+ const [secretCredentialValues, setSecretCredentialValues] = useState(
+ SECRET_CREDENTIAL_INITIAL_VALUES,
+ );
// Set default name when modal opens
useEffect(() => {
@@ -88,12 +98,17 @@ function CredentialsModal({ onCredentialCreated }: Props) {
...prev,
name: defaultName,
}));
+ setSecretCredentialValues((prev) => ({
+ ...prev,
+ name: defaultName,
+ }));
}
}, [isOpen, credentials]);
function reset() {
setPasswordCredentialValues(PASSWORD_CREDENTIAL_INITIAL_VALUES);
setCreditCardCredentialValues(CREDIT_CARD_CREDENTIAL_INITIAL_VALUES);
+ setSecretCredentialValues(SECRET_CREDENTIAL_INITIAL_VALUES);
}
const createCredentialMutation = useMutation({
@@ -128,7 +143,9 @@ function CredentialsModal({ onCredentialCreated }: Props) {
const name =
type === CredentialModalTypes.PASSWORD
? passwordCredentialValues.name.trim()
- : creditCardCredentialValues.name.trim();
+ : type === CredentialModalTypes.CREDIT_CARD
+ ? creditCardCredentialValues.name.trim()
+ : secretCredentialValues.name.trim();
if (name === "") {
toast({
title: "Error",
@@ -219,9 +236,55 @@ function CredentialsModal({ onCredentialCreated }: Props) {
card_holder_name: cardHolderName,
},
});
+ } else if (type === CredentialModalTypes.SECRET) {
+ const secretValue = secretCredentialValues.secretValue.trim();
+ const secretLabel = secretCredentialValues.secretLabel.trim();
+
+ if (secretValue === "") {
+ toast({
+ title: "Error",
+ description: "Secret value is required",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ createCredentialMutation.mutate({
+ name,
+ credential_type: "secret",
+ credential: {
+ secret_value: secretValue,
+ secret_label: secretLabel === "" ? null : secretLabel,
+ },
+ });
}
};
+ const credentialContent = (() => {
+ if (type === CredentialModalTypes.PASSWORD) {
+ return (
+
+ );
+ }
+ if (type === CredentialModalTypes.CREDIT_CARD) {
+ return (
+
+ );
+ }
+ return (
+
+ );
+ })();
+
return (
Add Credential
- {type === CredentialModalTypes.PASSWORD ? (
-
- ) : (
-
- )}
+ {credentialContent}
Credit Card
+ {
+ setIsOpen(true);
+ setType(CredentialModalTypes.SECRET);
+ }}
+ className="cursor-pointer"
+ >
+
+ Secret
+
@@ -90,6 +105,7 @@ function CredentialsPage() {
Passwords
Credit Cards
+ Secrets
2FA
@@ -101,6 +117,10 @@ function CredentialsPage() {
+
+
+
+
diff --git a/skyvern-frontend/src/routes/credentials/SecretCredentialContent.tsx b/skyvern-frontend/src/routes/credentials/SecretCredentialContent.tsx
new file mode 100644
index 00000000..b5e1bbaa
--- /dev/null
+++ b/skyvern-frontend/src/routes/credentials/SecretCredentialContent.tsx
@@ -0,0 +1,82 @@
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Separator } from "@/components/ui/separator";
+import { EyeNoneIcon, EyeOpenIcon } from "@radix-ui/react-icons";
+import { useState } from "react";
+
+type Props = {
+ values: {
+ name: string;
+ secretLabel: string;
+ secretValue: string;
+ };
+ onChange: (values: {
+ name: string;
+ secretLabel: string;
+ secretValue: string;
+ }) => void;
+};
+
+function SecretCredentialContent({ values, onChange }: Props) {
+ const { name, secretLabel, secretValue } = values;
+ const [showSecret, setShowSecret] = useState(false);
+
+ return (
+
+
+
+
Name
+
+ The name of the credential
+
+
+
onChange({ ...values, name: e.target.value })}
+ />
+
+
+
+ Secret Label (optional)
+ onChange({ ...values, secretLabel: e.target.value })}
+ />
+
+
+
Secret Value
+
+
+ onChange({ ...values, secretValue: e.target.value })
+ }
+ />
+
{
+ setShowSecret((value) => !value);
+ }}
+ aria-label="Toggle secret value visibility"
+ >
+ {showSecret ? (
+
+ ) : (
+
+ )}
+
+
+
+ {
+ "Use in HTTP Request blocks with: {{ credential_name.secret_value }}"
+ }
+
+
+
+ );
+}
+
+export { SecretCredentialContent };
diff --git a/skyvern-frontend/src/routes/credentials/useCredentialModalState.ts b/skyvern-frontend/src/routes/credentials/useCredentialModalState.ts
index ab5eb4f3..be336d85 100644
--- a/skyvern-frontend/src/routes/credentials/useCredentialModalState.ts
+++ b/skyvern-frontend/src/routes/credentials/useCredentialModalState.ts
@@ -6,6 +6,7 @@ const typeParam = "type";
export const CredentialModalTypes = {
PASSWORD: "password",
CREDIT_CARD: "credit-card",
+ SECRET: "secret",
} as const;
export type CredentialModalType =
diff --git a/skyvern-frontend/src/routes/workflows/components/CredentialSelector.tsx b/skyvern-frontend/src/routes/workflows/components/CredentialSelector.tsx
index e526466f..d3516662 100644
--- a/skyvern-frontend/src/routes/workflows/components/CredentialSelector.tsx
+++ b/skyvern-frontend/src/routes/workflows/components/CredentialSelector.tsx
@@ -62,7 +62,9 @@ function CredentialSelector({ value, onChange }: Props) {
{credential.credential_type === "password"
? "Password"
- : "Credit Card"}
+ : credential.credential_type === "credit_card"
+ ? "Credit Card"
+ : "Secret"}
diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/HttpRequestNode.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/HttpRequestNode.tsx
index 963156da..c22fe91b 100644
--- a/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/HttpRequestNode.tsx
+++ b/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/HttpRequestNode.tsx
@@ -399,9 +399,10 @@ function HttpRequestNode({ id, data }: NodeProps) {
authentication and content headers
- Pass a credential/secret parameter and reference it in headers
- or body with {"{{ my_credential.password }}"}
+ Password credential: {"{{ my_credential.username }}"} /{" "}
+ {"{{ my_credential.password }}"}
+ Secret credential: {"{{ my_secret.secret_value }}"}
The request will return response data including status, headers,
and body