Credentials UI (#1828)

This commit is contained in:
Shuchang Zheng
2025-02-24 11:54:18 -08:00
committed by GitHub
parent 39ab9c0603
commit c5a2438e7f
13 changed files with 337 additions and 27 deletions

View File

@@ -0,0 +1,66 @@
import { getClient } from "@/api/AxiosClient";
import { CredentialApiResponse } from "@/api/types";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton";
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
import { useQuery } from "@tanstack/react-query";
type Props = {
value: string;
onChange: (value: string) => void;
};
function CredentialSelector({ value, onChange }: Props) {
const credentialGetter = useCredentialGetter();
const { data: credentials, isFetching } = useQuery<
Array<CredentialApiResponse>
>({
queryKey: ["credentials"],
queryFn: async () => {
const client = await getClient(credentialGetter);
return await client.get("/credentials").then((res) => res.data);
},
});
if (isFetching) {
return <Skeleton className="h-10 w-full" />;
}
if (!credentials) {
return null;
}
return (
<Select value={value} onValueChange={onChange}>
<SelectTrigger>
<SelectValue placeholder="Select a credential" />
</SelectTrigger>
<SelectContent>
{credentials.map((credential) => (
<SelectItem
key={credential.credential_id}
value={credential.credential_id}
>
<div className="space-y-2">
<p className="text-sm font-medium">{credential.name}</p>
<p className="text-xs text-slate-400">
{credential.credential_type === "password"
? "Password"
: "Credit Card"}
</p>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
);
}
export { CredentialSelector };

View File

@@ -47,6 +47,7 @@ import {
BitwardenSensitiveInformationParameterYAML,
BlockYAML,
ContextParameterYAML,
CredentialParameterYAML,
ParameterYAML,
WorkflowCreateYAMLRequest,
WorkflowParameterYAML,
@@ -91,6 +92,7 @@ function convertToParametersYAML(
| ContextParameterYAML
| BitwardenSensitiveInformationParameterYAML
| BitwardenCreditCardDataParameterYAML
| CredentialParameterYAML
> {
return parameters.map((parameter) => {
if (parameter.parameterType === WorkflowEditorParameterTypes.Workflow) {
@@ -143,6 +145,15 @@ function convertToParametersYAML(
bitwarden_master_password_aws_secret_key:
BITWARDEN_MASTER_PASSWORD_AWS_SECRET_KEY,
};
} else if (
parameter.parameterType === WorkflowEditorParameterTypes.Credential
) {
return {
parameter_type: WorkflowParameterTypes.Credential,
key: parameter.key,
description: parameter.description || null,
credential_id: parameter.credentialId,
};
} else {
return {
parameter_type: WorkflowParameterTypes.Bitwarden_Login_Credential,
@@ -170,7 +181,7 @@ export type ParametersState = Array<
}
| {
key: string;
parameterType: "credential";
parameterType: "bitwardenLoginCredential";
collectionId: string;
urlParameterKey: string;
description?: string | null;
@@ -196,6 +207,12 @@ export type ParametersState = Array<
collectionId: string;
description?: string | null;
}
| {
key: string;
parameterType: "credential";
credentialId: string;
description?: string | null;
}
>;
type Props = {

View File

@@ -75,27 +75,32 @@ function WorkflowEditor() {
initialParameters={workflow.workflow_definition.parameters
.filter((parameter) => isDisplayedInWorkflowEditor(parameter))
.map((parameter) => {
if (parameter.parameter_type === "workflow") {
if (
parameter.parameter_type === WorkflowParameterTypes.Workflow
) {
return {
key: parameter.key,
parameterType: "workflow",
parameterType: WorkflowEditorParameterTypes.Workflow,
dataType: parameter.workflow_parameter_type,
defaultValue: parameter.default_value,
description: parameter.description,
};
} else if (parameter.parameter_type === "context") {
} else if (
parameter.parameter_type === WorkflowParameterTypes.Context
) {
return {
key: parameter.key,
parameterType: "context",
parameterType: WorkflowEditorParameterTypes.Context,
sourceParameterKey: parameter.source.key,
description: parameter.description,
};
} else if (
parameter.parameter_type === "bitwarden_sensitive_information"
parameter.parameter_type ===
WorkflowParameterTypes.Bitwarden_Sensitive_Information
) {
return {
key: parameter.key,
parameterType: "secret",
parameterType: WorkflowEditorParameterTypes.Secret,
collectionId: parameter.bitwarden_collection_id,
identityKey: parameter.bitwarden_identity_key,
identityFields: parameter.bitwarden_identity_fields,
@@ -112,10 +117,20 @@ function WorkflowEditor() {
itemId: parameter.bitwarden_item_id,
description: parameter.description,
};
} else if (
parameter.parameter_type === WorkflowParameterTypes.Credential
) {
return {
key: parameter.key,
parameterType: WorkflowEditorParameterTypes.Credential,
credentialId: parameter.credential_id,
description: parameter.description,
};
} else {
return {
key: parameter.key,
parameterType: "credential",
parameterType:
WorkflowEditorParameterTypes.BitwardenLoginCredential,
collectionId: parameter.bitwarden_collection_id,
urlParameterKey: parameter.url_parameter_key,
description: parameter.description,

View File

@@ -1,7 +1,7 @@
import { Cross2Icon } from "@radix-ui/react-icons";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { useState } from "react";
import { useContext, useState } from "react";
import {
WorkflowEditorParameterType,
WorkflowParameterValueType,
@@ -22,6 +22,8 @@ import { getDefaultValueForParameterType } from "../workflowEditorUtils";
import { toast } from "@/components/ui/use-toast";
import { SourceParameterKeySelector } from "../../components/SourceParameterKeySelector";
import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area";
import CloudContext from "@/store/CloudContext";
import { CredentialSelector } from "../../components/CredentialSelector";
type Props = {
type: WorkflowEditorParameterType;
@@ -45,6 +47,9 @@ function header(type: WorkflowEditorParameterType) {
if (type === "credential") {
return "Add Credential Parameter";
}
if (type === "bitwardenLoginCredential") {
return "Add Bitwarden Login Credential Parameter";
}
if (type === "secret") {
return "Add Secret Parameter";
}
@@ -55,6 +60,7 @@ function header(type: WorkflowEditorParameterType) {
}
function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
const isCloud = useContext(CloudContext);
const [key, setKey] = useState("");
const [urlParameterKey, setUrlParameterKey] = useState("");
const [description, setDescription] = useState("");
@@ -76,6 +82,8 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
const [identityFields, setIdentityFields] = useState("");
const [itemId, setItemId] = useState("");
const [credentialId, setCredentialId] = useState("");
return (
<ScrollArea>
<ScrollAreaViewport className="max-h-[500px]">
@@ -181,7 +189,7 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
</div>
</>
)}
{type === "credential" && (
{type === "bitwardenLoginCredential" && (
<>
<div className="space-y-1">
<Label className="text-xs text-slate-300">
@@ -255,6 +263,18 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
</div>
</>
)}
{
// temporarily cloud only
type === "credential" && isCloud && (
<div className="space-y-1">
<Label className="text-xs text-slate-300">Credential</Label>
<CredentialSelector
value={credentialId}
onChange={(value) => setCredentialId(value)}
/>
</div>
)
}
<div className="flex justify-end">
<Button
onClick={() => {
@@ -290,7 +310,7 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
});
}
if (
type === "credential" ||
type === "bitwardenLoginCredential" ||
type === "secret" ||
type === "creditCardData"
) {
@@ -303,10 +323,10 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
return;
}
}
if (type === "credential") {
if (type === "bitwardenLoginCredential") {
onSave({
key,
parameterType: "credential",
parameterType: "bitwardenLoginCredential",
collectionId,
urlParameterKey,
description,
@@ -346,7 +366,23 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
onSave({
key,
parameterType: "context",
sourceParameterKey: sourceParameterKey,
sourceParameterKey,
description,
});
}
if (type === "credential") {
if (!credentialId) {
toast({
variant: "destructive",
title: "Failed to add parameter",
description: "Credential is required",
});
return;
}
onSave({
key,
parameterType: "credential",
credentialId,
description,
});
}

View File

@@ -1,7 +1,7 @@
import { Cross2Icon } from "@radix-ui/react-icons";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { useState } from "react";
import { useState, useContext } from "react";
import {
WorkflowEditorParameterType,
WorkflowParameterValueType,
@@ -22,6 +22,8 @@ import { WorkflowParameterInput } from "../../WorkflowParameterInput";
import { toast } from "@/components/ui/use-toast";
import { SourceParameterKeySelector } from "../../components/SourceParameterKeySelector";
import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area";
import CloudContext from "@/store/CloudContext";
import { CredentialSelector } from "../../components/CredentialSelector";
type Props = {
type: WorkflowEditorParameterType;
@@ -49,6 +51,9 @@ function header(type: WorkflowEditorParameterType) {
if (type === "secret") {
return "Edit Secret Parameter";
}
if (type === "bitwardenLoginCredential") {
return "Edit Bitwarden Login Credential Parameter";
}
if (type === "creditCardData") {
return "Edit Credit Card Data Parameter";
}
@@ -61,9 +66,10 @@ function WorkflowParameterEditPanel({
onSave,
initialValues,
}: Props) {
const isCloud = useContext(CloudContext);
const [key, setKey] = useState(initialValues.key);
const [urlParameterKey, setUrlParameterKey] = useState(
initialValues.parameterType === "credential"
initialValues.parameterType === "bitwardenLoginCredential"
? initialValues.urlParameterKey
: "",
);
@@ -71,7 +77,7 @@ function WorkflowParameterEditPanel({
initialValues.description ?? "",
);
const [collectionId, setCollectionId] = useState(
initialValues.parameterType === "credential" ||
initialValues.parameterType === "bitwardenLoginCredential" ||
initialValues.parameterType === "secret" ||
initialValues.parameterType === "creditCardData"
? initialValues.collectionId
@@ -123,6 +129,12 @@ function WorkflowParameterEditPanel({
: "",
);
const [credentialId, setCredentialId] = useState(
initialValues.parameterType === "credential"
? initialValues.credentialId
: "",
);
return (
<ScrollArea>
<ScrollAreaViewport className="max-h-[500px]">
@@ -228,7 +240,7 @@ function WorkflowParameterEditPanel({
</div>
</>
)}
{type === "credential" && (
{type === "bitwardenLoginCredential" && (
<>
<div className="space-y-1">
<Label className="text-xs text-slate-300">
@@ -302,6 +314,18 @@ function WorkflowParameterEditPanel({
</div>
</>
)}
{
// temporarily cloud only
type === "credential" && isCloud && (
<div className="space-y-1">
<Label className="text-xs text-slate-300">Credential</Label>
<CredentialSelector
value={credentialId}
onChange={(value) => setCredentialId(value)}
/>
</div>
)
}
<div className="flex justify-end">
<Button
onClick={() => {
@@ -337,7 +361,7 @@ function WorkflowParameterEditPanel({
});
}
if (
type === "credential" ||
type === "bitwardenLoginCredential" ||
type === "secret" ||
type === "creditCardData"
) {
@@ -350,10 +374,10 @@ function WorkflowParameterEditPanel({
return;
}
}
if (type === "credential") {
if (type === "bitwardenLoginCredential") {
onSave({
key,
parameterType: "credential",
parameterType: "bitwardenLoginCredential",
urlParameterKey,
collectionId,
description,
@@ -397,6 +421,22 @@ function WorkflowParameterEditPanel({
description,
});
}
if (type === "credential") {
if (!credentialId) {
toast({
variant: "destructive",
title: "Failed to save parameter",
description: "Credential is required",
});
return;
}
onSave({
key,
parameterType: "credential",
credentialId,
description,
});
}
}}
>
Save

View File

@@ -79,7 +79,7 @@ function WorkflowParametersPanel() {
setOperationPanelState({
active: true,
operation: "add",
type: "workflow",
type: WorkflowEditorParameterTypes.Workflow,
});
}}
>
@@ -90,7 +90,7 @@ function WorkflowParametersPanel() {
setOperationPanelState({
active: true,
operation: "add",
type: "credential",
type: WorkflowEditorParameterTypes.Credential,
});
}}
>
@@ -101,7 +101,18 @@ function WorkflowParametersPanel() {
setOperationPanelState({
active: true,
operation: "add",
type: "secret",
type: WorkflowEditorParameterTypes.BitwardenLoginCredential,
});
}}
>
Bitwarden Login Credential Parameter
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
setOperationPanelState({
active: true,
operation: "add",
type: WorkflowEditorParameterTypes.Secret,
});
}}
>

View File

@@ -1559,6 +1559,13 @@ function convertParametersToParameterYAML(
default_value: parameter.default_value,
};
}
case WorkflowParameterTypes.Credential: {
return {
...base,
parameter_type: WorkflowParameterTypes.Credential,
credential_id: parameter.credential_id,
};
}
}
});
}

View File

@@ -59,6 +59,16 @@ export type BitwardenCreditCardDataParameter = WorkflowParameterBase & {
deleted_at: string | null;
};
export type CredentialParameter = WorkflowParameterBase & {
parameter_type: "credential";
workflow_id: string;
credential_parameter_id: string;
credential_id: string;
created_at: string;
modified_at: string;
deleted_at: string | null;
};
export type WorkflowParameter = WorkflowParameterBase & {
parameter_type: "workflow";
workflow_id: string;
@@ -105,6 +115,7 @@ export const WorkflowParameterTypes = {
Bitwarden_Login_Credential: "bitwarden_login_credential",
Bitwarden_Sensitive_Information: "bitwarden_sensitive_information",
Bitwarden_Credit_Card_Data: "bitwarden_credit_card_data",
Credential: "credential",
} as const;
export type WorkflowParameterType =
@@ -117,7 +128,8 @@ export function isDisplayedInWorkflowEditor(
| ContextParameter
| BitwardenCreditCardDataParameter
| BitwardenLoginCredentialParameter
| BitwardenSensitiveInformationParameter {
| BitwardenSensitiveInformationParameter
| CredentialParameter {
return (
parameter.parameter_type === WorkflowParameterTypes.Workflow ||
parameter.parameter_type ===
@@ -126,7 +138,8 @@ export function isDisplayedInWorkflowEditor(
parameter.parameter_type ===
WorkflowParameterTypes.Bitwarden_Sensitive_Information ||
parameter.parameter_type ===
WorkflowParameterTypes.Bitwarden_Credit_Card_Data
WorkflowParameterTypes.Bitwarden_Credit_Card_Data ||
parameter.parameter_type === WorkflowParameterTypes.Credential
);
}
@@ -137,7 +150,8 @@ export type Parameter =
| BitwardenLoginCredentialParameter
| BitwardenSensitiveInformationParameter
| BitwardenCreditCardDataParameter
| AWSSecretParameter;
| AWSSecretParameter
| CredentialParameter;
export type WorkflowBlock =
| TaskBlock
@@ -199,6 +213,7 @@ export type WorkflowBlockType =
export const WorkflowEditorParameterTypes = {
Workflow: "workflow",
BitwardenLoginCredential: "bitwardenLoginCredential",
Credential: "credential",
Secret: "secret",
Context: "context",

View File

@@ -20,6 +20,7 @@ export type ParameterYAML =
| WorkflowParameterYAML
| BitwardenLoginCredentialParameterYAML
| AWSSecretParameterYAML
| CredentialParameterYAML
| ContextParameterYAML
| OutputParameterYAML
| BitwardenSensitiveInformationParameterYAML
@@ -82,6 +83,11 @@ export type OutputParameterYAML = ParameterYAMLBase & {
parameter_type: "output";
};
export type CredentialParameterYAML = ParameterYAMLBase & {
parameter_type: "credential";
credential_id: string;
};
export type BlockYAML =
| TaskBlockYAML
| CodeBlockYAML