Skip to content

Commit

Permalink
Refactors: pass isAllowedSecret as prop, remove useEffect(), clean up…
Browse files Browse the repository at this point in the history
… encrypt
  • Loading branch information
ZIJ committed Aug 2, 2024
1 parent 5952a49 commit 056e899
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,15 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Separator } from "@/components/ui/separator";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Textarea } from "@/components/ui/textarea";
import { getProjectPublicKey } from "@/data/admin/env-vars";
import { tfvarsOnBulkUpdate, tfvarsOnDelete, tfvarsOnUpdate } from "@/data/user/tfvars";
import { EnvVar } from "@/types/userTypes";
import { motion } from 'framer-motion';
import { AlertTriangle, Copy, Edit, LockKeyhole, Plus, Save, Trash, Unlock } from 'lucide-react';
import moment from 'moment';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { toast } from 'sonner';

type TFVarTableProps = {
envVars: EnvVar[];
projectId: string;
};

const EmptyState: React.FC<{ onAddVariable: () => void }> = ({ onAddVariable }) => {
return (
<motion.div
Expand Down Expand Up @@ -51,24 +45,22 @@ const EmptyState: React.FC<{ onAddVariable: () => void }> = ({ onAddVariable })
);
};

export default function TFVarTable({ projectId, envVars }: TFVarTableProps) {
type TFVarTableProps = {
envVars: EnvVar[];
projectId: string;
orgId: string;
isAllowedSecrets: boolean;
};

export default function TFVarTable({ projectId, orgId, isAllowedSecrets, envVars }: TFVarTableProps) {
const [editingVar, setEditingVar] = useState<{ originalName: string, currentVar: EnvVar } | null>(null);
const [newVar, setNewVar] = useState<Omit<EnvVar, 'updated_at'>>({ name: '', value: '', is_secret: false });
const [bulkEditMode, setBulkEditMode] = useState(false);
const [bulkEditValue, setBulkEditValue] = useState<string>('');
const [isLoading, setIsLoading] = useState(false);
const [showAddForm, setShowAddForm] = useState(false);
const [canCreateSecrets, setCanCreateSecrets] = useState(true);
const router = useRouter();

useEffect(() => {
getProjectPublicKey(projectId).then(key => {
if (!key) {
setCanCreateSecrets(false);
}
})
});

const handleEdit = (envVar: EnvVar) => {
setEditingVar({
originalName: envVar.name,
Expand All @@ -93,7 +85,8 @@ export default function TFVarTable({ projectId, envVars }: TFVarTableProps) {
editingVar.currentVar.name,
editingVar.currentVar.value,
editingVar.currentVar.is_secret,
projectId
projectId,
orgId,
);
toast.success('Variable updated successfully');
setEditingVar(null);
Expand All @@ -114,7 +107,7 @@ export default function TFVarTable({ projectId, envVars }: TFVarTableProps) {
}
setIsLoading(true);
try {
await tfvarsOnUpdate(newVar.name, newVar.name, newVar.value, newVar.is_secret, projectId);
await tfvarsOnUpdate(newVar.name, newVar.name, newVar.value, newVar.is_secret, projectId, orgId);
toast.success('New variable added successfully');
setNewVar({ name: '', value: '', is_secret: false });
setShowAddForm(false);
Expand Down Expand Up @@ -151,7 +144,7 @@ export default function TFVarTable({ projectId, envVars }: TFVarTableProps) {
}

setIsLoading(true);
await tfvarsOnBulkUpdate(parsedVars, projectId);
await tfvarsOnBulkUpdate(parsedVars, projectId, orgId);
toast.success('Bulk update successful');
setBulkEditMode(false);
router.refresh();
Expand Down Expand Up @@ -323,7 +316,7 @@ export default function TFVarTable({ projectId, envVars }: TFVarTableProps) {
</SelectTrigger>
<SelectContent>
<SelectItem value="plain_text">Plain Text</SelectItem>
<SelectItem value="secret" disabled={!canCreateSecrets}>Secret</SelectItem>
<SelectItem value="secret" disabled={!isAllowedSecrets}>Secret</SelectItem>
</SelectContent>
</Select>
</div>
Expand All @@ -334,7 +327,7 @@ export default function TFVarTable({ projectId, envVars }: TFVarTableProps) {
{isLoading ? 'Adding...' : 'Add Variable'}
</Button>
</div>
{!canCreateSecrets && (<div className="mt-4 flex justify-start">
{!isAllowedSecrets && (<div className="mt-4 flex justify-start">
<span className="flex items-center text-orange-400">
<AlertTriangle className="h-4 w-4 mr-2" />
<em className="text-sm italic">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import TFVarTable from "./TFVarTable";

type TFVarsDetailsProps = {
projectId: string;
orgId: string;
isAllowedSecrets: boolean;
initialEnvVars: EnvVar[];
}

export default function TFVarsDetails({ projectId, initialEnvVars }: TFVarsDetailsProps) {
export default function TFVarsDetails({ projectId, orgId, isAllowedSecrets, initialEnvVars }: TFVarsDetailsProps) {
return (
<motion.div
initial={{ opacity: 0, y: 10 }}
Expand All @@ -29,6 +31,8 @@ export default function TFVarsDetails({ projectId, initialEnvVars }: TFVarsDetai
<CardContent>
<TFVarTable
projectId={projectId}
orgId={orgId}
isAllowedSecrets={isAllowedSecrets}
envVars={initialEnvVars}
/>
</CardContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export default async function ProjectPage({ params }: { params: unknown }) {
const { projectSlug } = projectSlugParamSchema.parse(params);
const slimProject = await getSlimProjectBySlug(projectSlug);


return (
<div className="flex flex-col space-y-4 max-w-5xl mt-2">
<AllRunsDetails projectId={slimProject.id} projectSlug={projectSlug} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// page.tsx
import { getAllEnvVars } from "@/data/admin/env-vars";
import { getAllEnvVars, getOrganizationPublicKey } from "@/data/admin/env-vars";
import { getSlimProjectBySlug } from "@/data/user/projects";
import { projectSlugParamSchema } from "@/utils/zod-schemas/params";
import type { Metadata } from "next";
Expand All @@ -20,13 +20,17 @@ export async function generateMetadata({
export default async function TFVarsPage({ params }: { params: unknown }) {
const { projectSlug } = projectSlugParamSchema.parse(params);
const project = await getSlimProjectBySlug(projectSlug);

const envVars = await getAllEnvVars(project.id);
const [envVars, publicKey] = await Promise.all([
getAllEnvVars(project.id),
getOrganizationPublicKey(project.organization_id)
]);

return (
<div className="flex flex-col space-y-4 max-w-5xl mt-2">
<TFVarsDetails
projectId={project.id}
orgId={project.organization_id}
isAllowedSecrets={Boolean(publicKey)}
initialEnvVars={envVars}
/>
</div>
Expand Down
32 changes: 17 additions & 15 deletions src/data/admin/env-vars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import { constants, publicEncrypt } from 'crypto';

export async function encryptSecretWithPublicKey(
text: string,
projectId: string,
publicKey: string,
): Promise<string> {
const publicKey = await getProjectPublicKey(projectId);
if (!publicKey) {
console.error('No secrets key in the org');
throw new Error('No secrets key in the org');
Expand All @@ -25,35 +24,38 @@ export async function encryptSecretWithPublicKey(
return encrypted.toString('base64');
}

export async function getProjectPublicKey(
projectId: string,
export async function getOrganizationPublicKey(
orgId: string,
): Promise<string | null> {
const { data: orgData } = await supabaseAdminClient
.from('projects')
.select('organization_id')
.eq('id', projectId)
.single();
const { data: publicKeyData } = await supabaseAdminClient
.from('organizations')
.select('public_key')
.eq('id', orgData?.organization_id || '')
.eq('id', orgId)
.single();
if (publicKeyData?.public_key) {
return publicKeyData.public_key;
} else {
return null;
}
return null;
}

export async function storeEnvVar(
projectId: string,
orgId: string,
name: string,
value: string,
isSecret: boolean,
) {
const storedValue = isSecret
? await encryptSecretWithPublicKey(value, projectId)
: value; // non-secret values stored in plain text
const publicKey = await getOrganizationPublicKey(orgId);

let storedValue;
if (isSecret) {
if (!publicKey) {
throw new Error('Cannot encrypt secret - no public key');
}
storedValue = encryptSecretWithPublicKey(value, publicKey);
} else {
storedValue = value;
}

const { data, error } = await supabaseAdminClient.from('env_vars').upsert(
{
Expand Down
2 changes: 1 addition & 1 deletion src/data/user/projects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const getSlimProjectBySlug = async (projectSlug: string) => {
const supabaseClient = createSupabaseUserServerComponentClient();
const { data, error } = await supabaseClient
.from("projects")
.select("id, slug, name")
.select("id, slug, name, organization_id")
.eq("slug", projectSlug)
.single();
if (error) {
Expand Down
6 changes: 5 additions & 1 deletion src/data/user/tfvars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ export async function tfvarsOnUpdate(
value: string,
isSecret: boolean,
projectId: string,
orgId: string,
): Promise<EnvVar[]> {
if (oldName !== newName) {
await deleteEnvVar(projectId, oldName);
}
await storeEnvVar(projectId, newName, value, isSecret);
await storeEnvVar(projectId, orgId, newName, value, isSecret);
const vars = await getAllEnvVars(projectId);
return vars.map((v) => ({ ...v, updated_at: new Date().toISOString() }));
}
Expand All @@ -31,6 +32,7 @@ export async function tfvarsOnDelete(
export async function tfvarsOnBulkUpdate(
vars: EnvVar[],
projectId: string,
orgId: string,
): Promise<EnvVar[]> {
const currentVars = await getAllEnvVars(projectId);
const currentVarsMap = Object.fromEntries(
Expand All @@ -50,6 +52,7 @@ export async function tfvarsOnBulkUpdate(
newVar.value,
currentVar.is_secret,
projectId,
orgId,
);
}
} else {
Expand All @@ -59,6 +62,7 @@ export async function tfvarsOnBulkUpdate(
newVar.value,
false,
projectId,
orgId,
);
}
}
Expand Down

0 comments on commit 056e899

Please sign in to comment.