Skip to content

Commit 7336f6e

Browse files
feat: Envelope encryption for Provider API Keys + Modal (#202)
* chore(server): track empty temp folder * chore(local-kms): add seed yaml * chore(docker-compose): add local-kms * chore: add aws kms sdk client * feat(server): adjust prisma schema to include encrypted data and key * feat(sever): add encryption module and service * feaet(server): encrypt provider API keys * feat(console): ui to consume new encrypted API contract * feat(server): prompt tester uses provider api key * chore(server): add KMS_LOCAL env variable * chore(console): remove unused imports * feat(console): required API key modal for a particular provider * fix(server): formatting * feat(console): fix wording * fix(console): formatting * feat(server): db migration for encrypted provider api keys * fix(server): broken ProviderApiKey migration
1 parent 8c1f691 commit 7336f6e

31 files changed

+1434
-747
lines changed

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
# compiled output
44
dist
5-
tmp
65
/out-tsc
76

87
# dependencies
@@ -48,4 +47,6 @@ Thumbs.db
4847
schema.graphql
4948

5049
# temp
51-
temp
50+
temp
51+
52+
kms/data

apps/console/src/app/app.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { RequestsPage } from "./pages/requests/RequestsPage";
2828
import { DashboardPage } from "./pages/projects/overview/DashboardPage";
2929
import { LoginPage } from "./pages/auth/LoginPage";
3030
import { AuthCallbackPage } from "./pages/auth/AuthCallbackPage";
31+
import { RequiredProviderApiKeyModalProvider } from "./lib/providers/RequiredProviderApiKeyModalProvider";
3132

3233
initSuperTokens();
3334

@@ -142,9 +143,11 @@ export function App() {
142143
path={paths["/projects/:projectId"]}
143144
element={
144145
<CurrentPromptProvider>
145-
<LayoutWrapper withSideNav={true}>
146-
<Outlet />
147-
</LayoutWrapper>
146+
<RequiredProviderApiKeyModalProvider>
147+
<LayoutWrapper withSideNav={true}>
148+
<Outlet />
149+
</LayoutWrapper>
150+
</RequiredProviderApiKeyModalProvider>
148151
</CurrentPromptProvider>
149152
}
150153
>
Lines changed: 68 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
import { Avatar, Card, Row, Col, Typography, Button, Input } from "antd";
2-
import styled from "@emotion/styled";
1+
import {
2+
Avatar,
3+
Card,
4+
Row,
5+
Col,
6+
Typography,
7+
Button,
8+
Input,
9+
message,
10+
} from "antd";
311
import { CloseOutlined, EditOutlined, SaveOutlined } from "@ant-design/icons";
412
import { useState } from "react";
513
import { useMutation } from "@tanstack/react-query";
@@ -8,24 +16,25 @@ import { gqlClient, queryClient } from "../../lib/graphql";
816
import { CreateProviderApiKeyInput } from "../../../@generated/graphql/graphql";
917
import { useEffect } from "react";
1018
import { useCurrentOrganization } from "../../lib/hooks/useCurrentOrganization";
11-
12-
const APIKeyContainer = styled.div`
13-
display: flex;
14-
align-items: center;
15-
width: 600px;
16-
`;
19+
import { trackEvent } from "../../lib/utils/analytics";
20+
import { providersList } from "./providers-list";
1721

1822
interface Props {
1923
provider: string;
2024
value: string | null;
21-
iconBase64: string;
25+
onSave?: () => void;
26+
initialIsEditing?: boolean;
27+
canCancelEdit?: boolean;
2228
}
2329

2430
export const ProviderApiKeyListItem = ({
2531
provider,
2632
value,
27-
iconBase64,
33+
onSave,
34+
initialIsEditing = false,
35+
canCancelEdit = true,
2836
}: Props) => {
37+
const [messageApi, contextHolder] = message.useMessage();
2938
const { currentOrgId } = useCurrentOrganization();
3039
const updateKeyMutation = useMutation({
3140
mutationFn: (data: CreateProviderApiKeyInput) =>
@@ -38,10 +47,11 @@ export const ProviderApiKeyListItem = ({
3847
}),
3948
onSuccess: () => {
4049
queryClient.invalidateQueries({ queryKey: ["providerApiKeys"] });
50+
onSave && onSave();
4151
},
4252
});
4353

44-
const [isEditing, setIsEditing] = useState(false);
54+
const [isEditing, setIsEditing] = useState(initialIsEditing);
4555
const [editValue, setEditValue] = useState<string>("");
4656

4757
useEffect(() => {
@@ -60,62 +70,66 @@ export const ProviderApiKeyListItem = ({
6070
value: editValue,
6171
organizationId: currentOrgId,
6272
});
73+
74+
messageApi.success("API key saved successfully");
75+
trackEvent("provider_api_key_set", { provider });
6376
setIsEditing(false);
6477
};
6578

79+
const iconBase64 = providersList.find(
80+
(item) => item.provider === provider
81+
).iconBase64;
82+
6683
return (
6784
<Card size="small" key={provider}>
68-
<APIKeyContainer>
69-
<Row gutter={[12, 12]} align="middle" style={{ width: "100%" }}>
70-
<Col
71-
style={{ display: "flex", alignItems: "center", marginRight: 20 }}
72-
>
73-
<Avatar size="large" shape="square" src={iconBase64} />
74-
<Typography.Text style={{ fontSize: 18, marginLeft: 10 }}>
75-
{provider}
85+
{contextHolder}
86+
<Row gutter={[12, 12]} align="middle" style={{ width: "100%" }}>
87+
<Col style={{ display: "flex", alignItems: "center", marginRight: 20 }}>
88+
<Avatar size="large" shape="square" src={iconBase64} />
89+
<Typography.Text style={{ fontSize: 18, marginLeft: 10 }}>
90+
{provider}
91+
</Typography.Text>
92+
</Col>
93+
<Col
94+
flex="auto"
95+
style={{ display: "flex", justifyContent: "flex-end" }}
96+
>
97+
{isEditing ? (
98+
<Input
99+
placeholder="Paste your API key"
100+
onChange={(e) => setEditValue(e.target.value)}
101+
autoComplete="off"
102+
/>
103+
) : (
104+
<Typography.Text style={{ marginLeft: 10, opacity: 0.5 }}>
105+
{value || "No API key provided"}
76106
</Typography.Text>
77-
</Col>
78-
<Col
79-
flex="auto"
80-
style={{ display: "flex", justifyContent: "flex-end" }}
81-
>
82-
{isEditing ? (
83-
<Input
84-
placeholder="Paste your API key"
85-
onChange={(e) => setEditValue(e.target.value)}
86-
autoComplete="off"
107+
)}
108+
</Col>
109+
<Col style={{ display: "flex", justifyContent: "flex-end" }}>
110+
{isEditing ? (
111+
<>
112+
<Button
113+
type="primary"
114+
onClick={handleSave}
115+
loading={updateKeyMutation.isLoading}
116+
icon={<SaveOutlined height={18} />}
87117
/>
88-
) : (
89-
<Typography.Text style={{ marginLeft: 10, opacity: 0.5 }}>
90-
{value || "No API key provided"}
91-
</Typography.Text>
92-
)}
93-
</Col>
94-
<Col style={{ display: "flex", justifyContent: "flex-end" }}>
95-
{isEditing ? (
96-
<>
97-
<Button
98-
type="primary"
99-
onClick={handleSave}
100-
loading={updateKeyMutation.isLoading}
101-
icon={<SaveOutlined height={18} />}
102-
/>
118+
119+
{canCancelEdit && (
103120
<Button
104121
onClick={() => setIsEditing(false)}
105122
loading={updateKeyMutation.isLoading}
106123
icon={<CloseOutlined />}
107124
style={{ marginLeft: 10 }}
108125
/>
109-
</>
110-
) : (
111-
<Button
112-
onClick={handleEdit}
113-
icon={<EditOutlined height={18} />}
114-
/>
115-
)}
116-
</Col>
117-
</Row>
118-
</APIKeyContainer>
126+
)}
127+
</>
128+
) : (
129+
<Button onClick={handleEdit} icon={<EditOutlined height={18} />} />
130+
)}
131+
</Col>
132+
</Row>
119133
</Card>
120134
);
121135
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Space } from "antd";
2+
import { ProviderApiKeyListItem } from "./ProviderApiKeyListItem";
3+
import { useProviderApiKeys } from "../../graphql/hooks/queries";
4+
import { providersList } from "./providers-list";
5+
6+
export const ProviderApiKeysList = () => {
7+
const { providerApiKeys } = useProviderApiKeys();
8+
9+
const renderProviderApiKey = (provider) => {
10+
const apiKey = providerApiKeys.find(
11+
(key) => key.provider === provider.provider
12+
);
13+
14+
const value = apiKey?.censoredValue
15+
? `**********${apiKey?.censoredValue}`
16+
: null;
17+
18+
return (
19+
<ProviderApiKeyListItem
20+
key={provider.provider}
21+
provider={provider.provider}
22+
value={value}
23+
/>
24+
);
25+
};
26+
27+
return (
28+
providerApiKeys && (
29+
<Space direction="vertical" style={{ width: 600 }}>
30+
{providersList.map((item, index) => renderProviderApiKey(item))}
31+
</Space>
32+
)
33+
);
34+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const providersList = [
2+
{
3+
name: "OpenAI",
4+
provider: "OpenAI",
5+
iconBase64:
6+
"data:image/png;base64,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",
7+
},
8+
];

apps/console/src/app/components/prompts/editor/ProviderSettingsCard.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { Button, Divider, Form } from "antd";
1+
import { Divider, Form } from "antd";
22
import { usePromptVersionEditorContext } from "../../../lib/providers/PromptVersionEditorContext";
33
import { ProviderSelector } from "./ProviderSelector/ProviderSelector";
44
import { PromptService } from "@pezzo/types";
5-
import { SendOutlined } from "@ant-design/icons";
65
import { ProviderSettingsSchemaRenderer } from "./ProviderSettings/ProviderSettingsSchemaRenderer";
76
import { openAIChatCompletionSettingsDefinition } from "./ProviderSettings/providers/openai-chat-completion";
87
import { azureOpenAIChatCompletionSettingsDefinition } from "./ProviderSettings/providers/azure-openai-chat-completion";
@@ -35,12 +34,6 @@ export const ProviderSettingsCard = ({ onOpenFunctionsModal }: Props) => {
3534
<ProviderSettingsSchemaRenderer
3635
schema={providerSettings[service].generateFormSchema(settings)}
3736
/>
38-
{/* {service === PromptService.OpenAIChatCompletion &&
39-
onOpenFunctionsModal && (
40-
<Button onClick={onOpenFunctionsModal} icon={<SendOutlined />}>
41-
Edit Functions
42-
</Button>
43-
)} */}
4437
</>
4538
)}
4639
</>

apps/console/src/app/components/prompts/views/PromptEditView.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { PromptTesterModal } from "../prompt-tester/PromptTesterModal";
2323
import { usePromptTester } from "../../../lib/providers/PromptTesterContext";
2424
import { ConsumePromptModal } from "../ConsumePromptModal";
2525
import { trackEvent } from "../../../lib/utils/analytics";
26+
import { useRequiredProviderApiKeyModal } from "../../../lib/providers/RequiredProviderApiKeyModalProvider";
27+
import { useProviderApiKeys } from "../../../graphql/hooks/queries";
2628

2729
const FUNCTIONS_FEATURE_FLAG = true;
2830

@@ -31,6 +33,8 @@ export const PromptEditView = () => {
3133
const { prompt, isLoading: isPromptLoading } = useCurrentPrompt();
3234
const { currentVersion, isPublishEnabled, isDraft, form } =
3335
usePromptVersionEditorContext();
36+
const { providerApiKeys } = useProviderApiKeys();
37+
const { openRequiredProviderApiKeyModal } = useRequiredProviderApiKeyModal();
3438

3539
const [isCommitModalOpen, setIsCommitModalOpen] = useState(false);
3640
const [isConsumePromptModalOpen, setIsConsumePromptModalOpen] =
@@ -40,8 +44,28 @@ export const PromptEditView = () => {
4044

4145
const handleRunTest = () => {
4246
const formValues = form.getFieldsValue();
43-
openTestModal(formValues);
47+
48+
// TODO: make dynamic
49+
const provider = "OpenAI";
50+
const hasProviderApiKey = !!providerApiKeys.find(
51+
(key) => key.provider === provider
52+
);
4453
trackEvent("prompt_run_test_clicked");
54+
55+
if (!hasProviderApiKey) {
56+
openRequiredProviderApiKeyModal({
57+
callback: () => {
58+
openTestModal(formValues);
59+
},
60+
provider,
61+
});
62+
trackEvent("provider_api_keys_modal_due_to_missing_api_key", {
63+
provider,
64+
});
65+
return;
66+
}
67+
68+
openTestModal(formValues);
4569
};
4670

4771
const onConsumeClick = () => {

apps/console/src/app/graphql/definitions/mutations/api-keys.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ export const UPDATE_PROVIDER_API_KEY = graphql(/* GraphQL */ `
44
mutation UpdateProviderAPIKey($data: CreateProviderApiKeyInput!) {
55
updateProviderApiKey(data: $data) {
66
provider
7-
value
87
}
98
}
109
`);

apps/console/src/app/graphql/definitions/queries/api-keys.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export const GET_ALL_PROVIDER_API_KEYS = graphql(/* GraphQL */ `
55
providerApiKeys(data: $data) {
66
id
77
provider
8-
value
8+
censoredValue
99
}
1010
}
1111
`);

apps/console/src/app/graphql/hooks/queries.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@ import {
3232
export const useProviderApiKeys = () => {
3333
const { organization } = useCurrentOrganization();
3434

35-
return useQuery({
35+
const result = useQuery({
3636
queryKey: ["providerApiKeys", organization?.id],
3737
queryFn: () =>
3838
gqlClient.request(GET_ALL_PROVIDER_API_KEYS, {
3939
data: { organizationId: organization?.id },
4040
}),
4141
});
42+
43+
return { ...result, providerApiKeys: result.data?.providerApiKeys ?? [] };
4244
};
4345

4446
export const usePezzoApiKeys = () => {

0 commit comments

Comments
 (0)