diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/TFVarTable.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/TFVarTable.tsx index 3b76fa7b..7d38d9c2 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/TFVarTable.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/TFVarTable.tsx @@ -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 ( 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>({ name: '', value: '', is_secret: false }); const [bulkEditMode, setBulkEditMode] = useState(false); const [bulkEditValue, setBulkEditValue] = useState(''); 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, @@ -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); @@ -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); @@ -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(); @@ -323,7 +316,7 @@ export default function TFVarTable({ projectId, envVars }: TFVarTableProps) { Plain Text - Secret + Secret @@ -334,7 +327,7 @@ export default function TFVarTable({ projectId, envVars }: TFVarTableProps) { {isLoading ? 'Adding...' : 'Add Variable'} - {!canCreateSecrets && (
+ {!isAllowedSecrets && (
diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/TFVarsDetails.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/TFVarsDetails.tsx index 968b9e5c..b23d3964 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/TFVarsDetails.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/TFVarsDetails.tsx @@ -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 ( diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/page.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/page.tsx index a559a4a5..09cdc6c4 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/page.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/page.tsx @@ -25,7 +25,6 @@ export default async function ProjectPage({ params }: { params: unknown }) { const { projectSlug } = projectSlugParamSchema.parse(params); const slimProject = await getSlimProjectBySlug(projectSlug); - return (
diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/tfvars/page.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/tfvars/page.tsx index 354808d7..0ffe3195 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/tfvars/page.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/tfvars/page.tsx @@ -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"; @@ -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 (
diff --git a/src/data/admin/env-vars.ts b/src/data/admin/env-vars.ts index 7f661a82..9720787a 100644 --- a/src/data/admin/env-vars.ts +++ b/src/data/admin/env-vars.ts @@ -6,9 +6,8 @@ import { constants, publicEncrypt } from 'crypto'; export async function encryptSecretWithPublicKey( text: string, - projectId: string, + publicKey: string, ): Promise { - const publicKey = await getProjectPublicKey(projectId); if (!publicKey) { console.error('No secrets key in the org'); throw new Error('No secrets key in the org'); @@ -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 { - 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( { diff --git a/src/data/user/projects.tsx b/src/data/user/projects.tsx index 218e076c..8f68a780 100644 --- a/src/data/user/projects.tsx +++ b/src/data/user/projects.tsx @@ -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) { diff --git a/src/data/user/tfvars.ts b/src/data/user/tfvars.ts index d40683d8..b8c0cc6d 100644 --- a/src/data/user/tfvars.ts +++ b/src/data/user/tfvars.ts @@ -10,11 +10,12 @@ export async function tfvarsOnUpdate( value: string, isSecret: boolean, projectId: string, + orgId: string, ): Promise { 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() })); } @@ -31,6 +32,7 @@ export async function tfvarsOnDelete( export async function tfvarsOnBulkUpdate( vars: EnvVar[], projectId: string, + orgId: string, ): Promise { const currentVars = await getAllEnvVars(projectId); const currentVarsMap = Object.fromEntries( @@ -50,6 +52,7 @@ export async function tfvarsOnBulkUpdate( newVar.value, currentVar.is_secret, projectId, + orgId, ); } } else { @@ -59,6 +62,7 @@ export async function tfvarsOnBulkUpdate( newVar.value, false, projectId, + orgId, ); } }