diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/.DS_Store b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/.DS_Store new file mode 100644 index 00000000..eea50776 Binary files /dev/null and b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/.DS_Store differ diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/settings/SecretKeyManager.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/settings/SecretKeyManager.tsx index 53c23e65..7c7a2b20 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/settings/SecretKeyManager.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/settings/SecretKeyManager.tsx @@ -8,7 +8,7 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { motion } from 'framer-motion'; -import { Copy, Trash2 } from 'lucide-react'; +import { Copy, ShieldAlert, Trash2 } from 'lucide-react'; import { useState } from 'react'; import { toast } from 'sonner'; @@ -80,6 +80,15 @@ export function SecretsKeyManager({ publicKey: initialPublicKey, onCreateKeyPair + + + Security Notice + + We prioritize your data security. We do not have access to your encrypted data. + If you lose your private key, you'll need to recreate your secrets. + Please ensure you store your private key securely. + + {publicKey ? (
@@ -104,10 +113,10 @@ export function SecretsKeyManager({ publicKey: initialPublicKey, onCreateKeyPair
{privateKey && ( - + Private Key (ONLY SHOWN ONCE) -

Save this in your GitHub Action Secrets (org level):

+

Save this in your GitHub Action Secrets (org level). You will not be able to retrieve it later:

Are you absolutely sure? This action cannot be undone. You will lose all your secrets without the possibility to recover them. + You will need to recreate your secrets if you proceed. diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/settings/SetSecretsKey.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/settings/SetSecretsKey.tsx index 78a5eb51..dde61398 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/settings/SetSecretsKey.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/settings/SetSecretsKey.tsx @@ -1,5 +1,7 @@ 'use server'; +import { deleteAllEnvVars } from '@/data/admin/env-vars'; +import { getProjectIdsOfOrganization } from '@/data/admin/organizations'; import { createKeyPair, deletePublicKey, getPublicKey } from '@/data/user/secretKey'; import { SecretsKeyManager } from './SecretKeyManager'; @@ -19,6 +21,10 @@ export async function SetSecretsKey({ organizationId }: { organizationId: string onDeletePublicKey={async () => { 'use server'; const result = await deletePublicKey(organizationId); + const projectIds = await getProjectIdsOfOrganization(organizationId); + for (const projectId of projectIds) { + await deleteAllEnvVars(projectId); + } if (result.status === 'error') { throw new Error(result.message); } 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 0ffe3195..709310b5 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,6 +1,8 @@ // page.tsx import { getAllEnvVars, getOrganizationPublicKey } from "@/data/admin/env-vars"; -import { getSlimProjectBySlug } from "@/data/user/projects"; +import { getLoggedInUserOrganizationRole } from "@/data/user/organizations"; +import { getSlimProjectBySlug, getSlimProjectWithTeamIdBySlug } from "@/data/user/projects"; +import { getLoggedInUserTeamRole } from "@/data/user/teams"; import { projectSlugParamSchema } from "@/utils/zod-schemas/params"; import type { Metadata } from "next"; import TFVarsDetails from '../TFVarsDetails'; @@ -19,18 +21,23 @@ export async function generateMetadata({ export default async function TFVarsPage({ params }: { params: unknown }) { const { projectSlug } = projectSlugParamSchema.parse(params); - const project = await getSlimProjectBySlug(projectSlug); - const [envVars, publicKey] = await Promise.all([ + const project = await getSlimProjectWithTeamIdBySlug(projectSlug); + const [envVars, publicKey, orgRole, teamRole] = await Promise.all([ getAllEnvVars(project.id), - getOrganizationPublicKey(project.organization_id) + getOrganizationPublicKey(project.organization_id), + getLoggedInUserOrganizationRole(project.organization_id), + project.team_id ? getLoggedInUserTeamRole(project.team_id) : Promise.resolve(null) ]); + const isTeamAdmin = teamRole === 'admin'; + const isOrgAdmin = orgRole === 'admin' || orgRole === 'owner'; + const canEdit = isTeamAdmin || isOrgAdmin; return (
diff --git a/src/data/admin/env-vars.ts b/src/data/admin/env-vars.ts index 2df08b15..f8ce88a8 100644 --- a/src/data/admin/env-vars.ts +++ b/src/data/admin/env-vars.ts @@ -146,3 +146,12 @@ export async function getAllEnvVars(projectId: string): Promise { }), ); } + +export async function deleteAllEnvVars(projectId: string) { + const { error } = await supabaseAdminClient + .from('env_vars') + .delete() + .eq('project_id', projectId); + + if (error) throw error; +} diff --git a/src/data/admin/organizations.ts b/src/data/admin/organizations.ts index f93a56ef..623b8a81 100644 --- a/src/data/admin/organizations.ts +++ b/src/data/admin/organizations.ts @@ -46,6 +46,15 @@ export async function getPaginatedOrganizationList({ return data; } +export async function getProjectIdsOfOrganization(organizationId: string) { + const { data, error } = await supabaseAdminClient + .from('projects') + .select('id') + .eq('organization_id', organizationId); + if (error) throw error; + return data.map((project) => project.id); +} + export async function getSlimOrganizationsOfUser(userId: string) { const { data: organizations, error: organizationsError } = await supabaseAdminClient diff --git a/src/data/user/projects.tsx b/src/data/user/projects.tsx index bd8abc36..603ca9d4 100644 --- a/src/data/user/projects.tsx +++ b/src/data/user/projects.tsx @@ -38,6 +38,19 @@ export const getSlimProjectBySlug = async (projectSlug: string) => { return data; } +export const getSlimProjectWithTeamIdBySlug = async (projectSlug: string) => { + const supabaseClient = createSupabaseUserServerComponentClient(); + const { data, error } = await supabaseClient + .from("projects") + .select("id, slug, name, organization_id, team_id") + .eq("slug", projectSlug) + .single(); + if (error) { + throw error; + } + return data; +} + export async function getProjectById(projectId: string) { const supabaseClient = createSupabaseUserServerComponentClient(); const { data, error } = await supabaseClient