From 1d0f3180139288f3844e90dd82bd7341db0c68e1 Mon Sep 17 00:00:00 2001 From: Lewis Carhart Date: Thu, 20 Mar 2025 18:54:03 +0000 Subject: [PATCH 1/2] Refactor layout and API keys page; enhance authentication checks - Cleaned up layout component for better readability and structure. - Improved authentication handling in the API keys page by removing redundant session checks. - Updated the `getApiKeys` function to directly use the session for organization ID retrieval. - Added session check in the auth page to redirect logged-in users away from the login screen. --- apps/app/next.config.ts | 66 +++++++++---------- .../(app)/(dashboard)/[orgId]/layout.tsx | 54 ++++++++------- .../[orgId]/settings/api-keys/page.tsx | 18 ++--- .../src/app/[locale]/(public)/auth/page.tsx | 8 +++ 4 files changed, 76 insertions(+), 70 deletions(-) diff --git a/apps/app/next.config.ts b/apps/app/next.config.ts index 74bcbeda2b..f2c0136855 100644 --- a/apps/app/next.config.ts +++ b/apps/app/next.config.ts @@ -1,39 +1,39 @@ import "./src/env.mjs"; const config = { - poweredByHeader: false, - reactStrictMode: true, - images: { - remotePatterns: [ - { - protocol: "https", - hostname: "**", - }, - ], - }, - transpilePackages: ["@bubba/ui"], - logging: { - fetches: { - fullUrl: process.env.LOG_FETCHES === "true", - }, - }, - async rewrites() { - return [ - { - source: "/ingest/static/:path*", - destination: "https://us-assets.i.posthog.com/static/:path*", - }, - { - source: "/ingest/:path*", - destination: "https://us.i.posthog.com/:path*", - }, - { - source: "/ingest/decide", - destination: "https://us.i.posthog.com/decide", - }, - ]; - }, - skipTrailingSlashRedirect: true, + poweredByHeader: false, + reactStrictMode: true, + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "**", + }, + ], + }, + transpilePackages: ["@bubba/ui"], + logging: { + fetches: { + fullUrl: process.env.LOG_FETCHES === "true", + }, + }, + async rewrites() { + return [ + { + source: "/ingest/static/:path*", + destination: "https://us-assets.i.posthog.com/static/:path*", + }, + { + source: "/ingest/:path*", + destination: "https://us.i.posthog.com/:path*", + }, + { + source: "/ingest/decide", + destination: "https://us.i.posthog.com/decide", + }, + ]; + }, + skipTrailingSlashRedirect: true, }; export default config; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx index b14581486d..9a0e9b7e89 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx @@ -1,46 +1,44 @@ import { auth } from "@/auth"; import { Header } from "@/components/header"; import { Sidebar } from "@/components/sidebar"; -import { db } from "@bubba/db"; import dynamic from "next/dynamic"; import { redirect } from "next/navigation"; -import { cache } from "react"; const HotKeys = dynamic( - () => import("@/components/hot-keys").then((mod) => mod.HotKeys), - { - ssr: true, - }, + () => import("@/components/hot-keys").then((mod) => mod.HotKeys), + { + ssr: true, + }, ); export default async function Layout({ - children, - params, + children, + params, }: { - children: React.ReactNode; - params: Promise<{ orgId: string }>; + children: React.ReactNode; + params: Promise<{ orgId: string }>; }) { - const session = await auth(); - const orgId = (await params).orgId; + const session = await auth(); + const orgId = (await params).orgId; - if (!session) { - redirect("/auth"); - } + if (!session) { + redirect("/auth"); + } - if (!orgId) { - redirect("/"); - } + if (!orgId) { + redirect("/"); + } - return ( -
- + return ( +
+ -
-
-
{children}
-
+
+
+
{children}
+
- -
- ); + +
+ ); } diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/api-keys/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/api-keys/page.tsx index 5d5c5d8074..22ba106fcf 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/api-keys/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/api-keys/page.tsx @@ -16,13 +16,7 @@ export default async function ApiKeysPage({ setStaticParamsLocale(locale); const t = await getI18n(); - const session = await auth(); - - if (!session?.user.organizationId) { - return redirect("/"); - } - - const apiKeys = await getApiKeys(session.user.organizationId); + const apiKeys = await getApiKeys(); return (
@@ -45,10 +39,16 @@ export async function generateMetadata({ }; } -const getApiKeys = cache(async (organizationId: string) => { +const getApiKeys = cache(async () => { + const session = await auth(); + + if (!session?.user.organizationId) { + return []; + } + const apiKeys = await db.organizationApiKey.findMany({ where: { - organizationId, + organizationId: session.user.organizationId, isActive: true, }, select: { diff --git a/apps/app/src/app/[locale]/(public)/auth/page.tsx b/apps/app/src/app/[locale]/(public)/auth/page.tsx index e646372dc4..d300433509 100644 --- a/apps/app/src/app/[locale]/(public)/auth/page.tsx +++ b/apps/app/src/app/[locale]/(public)/auth/page.tsx @@ -10,6 +10,8 @@ import { } from "@bubba/ui/accordion"; import type { Metadata } from "next"; import Link from "next/link"; +import { auth } from "@/auth"; +import { redirect } from "next/navigation"; export const metadata: Metadata = { title: "Login | Comp AI", @@ -21,6 +23,12 @@ export default async function Page({ searchParams: Promise<{ inviteCode?: string }>; }) { const t = await getI18n(); + const session = await auth(); + + if (session?.user) { + redirect("/"); + } + const { inviteCode } = await searchParams; const defaultSignInOptions = ( From 2e7485b80d5b587f31cdbfc81bd21be9dfd488d4 Mon Sep 17 00:00:00 2001 From: Lewis Carhart Date: Thu, 20 Mar 2025 19:08:05 +0000 Subject: [PATCH 2/2] Refactor createOrganizationTask for improved readability and error handling - Reformatted the `createOrganizationTask` function for better code structure and clarity. - Enhanced error handling and logging throughout the organization creation process. - Updated the `createOrganizationPolicy`, `createOrganizationCategories`, `createOrganizationControlRequirements`, and `createOrganizationEvidence` functions to include userId in their parameters for better tracking. - Ensured consistent use of async/await patterns for database operations. --- .../tasks/organization/create-organization.ts | 720 +++++++++--------- 1 file changed, 361 insertions(+), 359 deletions(-) diff --git a/apps/app/src/jobs/tasks/organization/create-organization.ts b/apps/app/src/jobs/tasks/organization/create-organization.ts index 20a3c30f5f..9510d5347d 100644 --- a/apps/app/src/jobs/tasks/organization/create-organization.ts +++ b/apps/app/src/jobs/tasks/organization/create-organization.ts @@ -5,383 +5,385 @@ import { logger, schemaTask } from "@trigger.dev/sdk/v3"; import { z } from "zod"; export const createOrganizationTask = schemaTask({ - id: "create-organization", - maxDuration: 1000 * 60 * 3, // 3 minutes - schema: z.object({ - organizationId: z.string(), - userId: z.string(), - fullName: z.string(), - website: z.string(), - frameworkIds: z.array(z.string()), - }), - run: async ({ organizationId, userId, fullName, website, frameworkIds }) => { - logger.info("Creating organization", { - organizationId, - userId, - fullName, - website, - frameworkIds, - }); - - const organization = await db.organization.findFirst({ - where: { - id: organizationId, - users: { - some: { - id: userId, - }, - }, - }, - }); - - if (!organization) { - throw new Error("Organization not found"); - } - - try { - await db.$transaction(async () => { - await db.organization.upsert({ - where: { - id: organization.id, - }, - update: { - name: fullName, - website, - }, - create: { - name: fullName, - website, - }, - }); - - await db.user.update({ - where: { - id: userId, - }, - data: { - onboarded: true, - }, - }); - }); - - await createOrganizationCategories(organizationId, frameworkIds); - - const organizationFrameworks = await Promise.all( - frameworkIds.map((frameworkId) => - createOrganizationFramework(organizationId, frameworkId) - ) - ); - - await createOrganizationPolicy(organizationId, frameworkIds); - - await createOrganizationControlRequirements( - organizationId, - organizationFrameworks.map((framework) => framework.id) - ); - - await createOrganizationEvidence(organizationId, frameworkIds, userId); - - await db.organization.update({ - where: { - id: organizationId, - }, - data: { - setup: true, - }, - }); - - return { - success: true, - }; - } catch (error) { - logger.error("Error creating organization", { - error, - }); - - throw error; - } - }, + id: "create-organization", + maxDuration: 1000 * 60 * 3, // 3 minutes + schema: z.object({ + organizationId: z.string(), + userId: z.string(), + fullName: z.string(), + website: z.string(), + frameworkIds: z.array(z.string()), + }), + run: async ({ organizationId, userId, fullName, website, frameworkIds }) => { + logger.info("Creating organization", { + organizationId, + userId, + fullName, + website, + frameworkIds, + }); + + const organization = await db.organization.findFirst({ + where: { + id: organizationId, + users: { + some: { + id: userId, + }, + }, + }, + }); + + if (!organization) { + throw new Error("Organization not found"); + } + + try { + await db.$transaction(async () => { + await db.organization.upsert({ + where: { + id: organization.id, + }, + update: { + name: fullName, + website, + }, + create: { + name: fullName, + website, + }, + }); + + await db.user.update({ + where: { + id: userId, + }, + data: { + onboarded: true, + }, + }); + }); + + await createOrganizationCategories(organizationId, frameworkIds); + + const organizationFrameworks = await Promise.all( + frameworkIds.map((frameworkId) => + createOrganizationFramework(organizationId, frameworkId), + ), + ); + + await createOrganizationPolicy(organizationId, frameworkIds, userId); + + await createOrganizationControlRequirements( + organizationId, + organizationFrameworks.map((framework) => framework.id), + ); + + await createOrganizationEvidence(organizationId, frameworkIds, userId); + + await db.organization.update({ + where: { + id: organizationId, + }, + data: { + setup: true, + }, + }); + + return { + success: true, + }; + } catch (error) { + logger.error("Error creating organization", { + error, + }); + + throw error; + } + }, }); const createOrganizationFramework = async ( - organizationId: string, - frameworkId: string + organizationId: string, + frameworkId: string, ) => { - // First verify the organization exists - const organization = await db.organization.findUnique({ - where: { id: organizationId }, - }); - - if (!organization) { - logger.error("Organization not found when creating framework", { - organizationId, - frameworkId, - }); - throw new Error(`Organization with ID ${organizationId} not found`); - } - - // Verify the framework exists - const framework = await db.framework.findUnique({ - where: { id: frameworkId }, - }); - - if (!framework) { - logger.error("Framework not found when creating organization framework", { - organizationId, - frameworkId, - }); - throw new Error(`Framework with ID ${frameworkId} not found`); - } - - const organizationFramework = await db.organizationFramework.create({ - data: { - organizationId, - frameworkId, - status: "not_started", - }, - select: { - id: true, - }, - }); - - logger.info("Created organization framework", { - organizationId, - frameworkId, - organizationFrameworkId: organizationFramework.id, - }); - - const frameworkCategories = await db.frameworkCategory.findMany({ - where: { frameworkId }, - include: { - controls: true, - }, - }); - - const organizationCategories = await db.organizationCategory.findMany({ - where: { - organizationId, - frameworkId, - }, - }); - - for (const frameworkCategory of frameworkCategories) { - const organizationCategory = organizationCategories.find( - (oc) => oc.name === frameworkCategory.name - ); - - if (!organizationCategory) continue; - - await db.organizationControl.createMany({ - data: frameworkCategory.controls.map((control) => ({ - organizationFrameworkId: organizationFramework.id, - controlId: control.id, - organizationId, - status: "not_started", - organizationCategoryId: organizationCategory.id, - })), - }); - } - - return organizationFramework; + // First verify the organization exists + const organization = await db.organization.findUnique({ + where: { id: organizationId }, + }); + + if (!organization) { + logger.error("Organization not found when creating framework", { + organizationId, + frameworkId, + }); + throw new Error(`Organization with ID ${organizationId} not found`); + } + + // Verify the framework exists + const framework = await db.framework.findUnique({ + where: { id: frameworkId }, + }); + + if (!framework) { + logger.error("Framework not found when creating organization framework", { + organizationId, + frameworkId, + }); + throw new Error(`Framework with ID ${frameworkId} not found`); + } + + const organizationFramework = await db.organizationFramework.create({ + data: { + organizationId, + frameworkId, + status: "not_started", + }, + select: { + id: true, + }, + }); + + logger.info("Created organization framework", { + organizationId, + frameworkId, + organizationFrameworkId: organizationFramework.id, + }); + + const frameworkCategories = await db.frameworkCategory.findMany({ + where: { frameworkId }, + include: { + controls: true, + }, + }); + + const organizationCategories = await db.organizationCategory.findMany({ + where: { + organizationId, + frameworkId, + }, + }); + + for (const frameworkCategory of frameworkCategories) { + const organizationCategory = organizationCategories.find( + (oc) => oc.name === frameworkCategory.name, + ); + + if (!organizationCategory) continue; + + await db.organizationControl.createMany({ + data: frameworkCategory.controls.map((control) => ({ + organizationFrameworkId: organizationFramework.id, + controlId: control.id, + organizationId, + status: "not_started", + organizationCategoryId: organizationCategory.id, + })), + }); + } + + return organizationFramework; }; const createOrganizationPolicy = async ( - organizationId: string, - frameworkIds: string[] + organizationId: string, + frameworkIds: string[], + userId: string, ) => { - if (!organizationId) { - throw new Error("Not authorized - no organization found"); - } - - const policies = await db.policy.findMany(); - const policiesForFrameworks: Policy[] = []; - - for (const policy of policies) { - const usedBy = policy.usedBy; - - if (!usedBy) { - continue; - } - - const usedByFrameworkIds = Object.keys(usedBy); - - if ( - usedByFrameworkIds.some((frameworkId) => - frameworkIds.includes(frameworkId) - ) - ) { - policiesForFrameworks.push(policy); - } - } - - const organizationPolicies = await db.organizationPolicy.createMany({ - data: policiesForFrameworks.map((policy) => ({ - organizationId, - policyId: policy.id, - status: "draft", - content: policy.content as InputJsonValue[], - frequency: policy.frequency, - })), - }); - - return organizationPolicies; + if (!organizationId) { + throw new Error("Not authorized - no organization found"); + } + + const policies = await db.policy.findMany(); + const policiesForFrameworks: Policy[] = []; + + for (const policy of policies) { + const usedBy = policy.usedBy; + + if (!usedBy) { + continue; + } + + const usedByFrameworkIds = Object.keys(usedBy); + + if ( + usedByFrameworkIds.some((frameworkId) => + frameworkIds.includes(frameworkId), + ) + ) { + policiesForFrameworks.push(policy); + } + } + + const organizationPolicies = await db.organizationPolicy.createMany({ + data: policiesForFrameworks.map((policy) => ({ + organizationId, + policyId: policy.id, + ownerId: userId, + status: "draft", + content: policy.content as InputJsonValue[], + frequency: policy.frequency, + })), + }); + + return organizationPolicies; }; const createOrganizationCategories = async ( - organizationId: string, - frameworkIds: string[] + organizationId: string, + frameworkIds: string[], ) => { - if (!organizationId) { - throw new Error("Not authorized - no organization found"); - } - - // For each frameworkCategory we need to get the controls. - const frameworkCategories = await db.frameworkCategory.findMany({ - where: { - frameworkId: { in: frameworkIds }, - }, - }); - - // Create the organization categories. - const organizationCategories = await db.organizationCategory.createMany({ - data: frameworkCategories.map((category) => ({ - name: category.name, - description: category.description, - organizationId, - frameworkId: category.frameworkId, - })), - }); - - return organizationCategories; + if (!organizationId) { + throw new Error("Not authorized - no organization found"); + } + + // For each frameworkCategory we need to get the controls. + const frameworkCategories = await db.frameworkCategory.findMany({ + where: { + frameworkId: { in: frameworkIds }, + }, + }); + + // Create the organization categories. + const organizationCategories = await db.organizationCategory.createMany({ + data: frameworkCategories.map((category) => ({ + name: category.name, + description: category.description, + organizationId, + frameworkId: category.frameworkId, + })), + }); + + return organizationCategories; }; const createOrganizationControlRequirements = async ( - organizationId: string, - organizationFrameworkIds: string[] + organizationId: string, + organizationFrameworkIds: string[], ) => { - if (!organizationId) { - throw new Error("Not authorized - no organization found"); - } - - const controls = await db.organizationControl.findMany({ - where: { - organizationId, - organizationFrameworkId: { - in: organizationFrameworkIds, - }, - }, - include: { - control: true, - }, - }); - - // Create control requirements for each control - const controlRequirements = await db.controlRequirement.findMany({ - where: { - controlId: { in: controls.map((control) => control.controlId) }, - }, - include: { - policy: true, // Include the policy to get its ID - evidence: true, // Include the evidence to get its ID - }, - }); - - // Get all organization policies for this organization - const organizationPolicies = await db.organizationPolicy.findMany({ - where: { - organizationId, - }, - }); - - // Get all organization evidences for this organization - const organizationEvidences = await db.organizationEvidence.findMany({ - where: { - organizationId, - }, - }); - - for (const control of controls) { - const requirements = controlRequirements.filter( - (req) => req.controlId === control.controlId - ); - - await db.organizationControlRequirement.createMany({ - data: requirements.map((requirement) => { - // Find the corresponding organization policy if this is a policy requirement - const policyId = - requirement.type === "policy" ? requirement.policy?.id : null; - const organizationPolicy = policyId - ? organizationPolicies.find((op) => op.policyId === policyId) - : null; - - const evidenceId = - requirement.type === "evidence" ? requirement.evidenceId : null; - - console.log({ - evidenceId, - }); - - const organizationEvidence = evidenceId - ? organizationEvidences.find((e) => e.evidenceId === evidenceId) - : null; - - console.log({ - organizationEvidence, - }); - - return { - organizationControlId: control.id, - controlRequirementId: requirement.id, - type: requirement.type, - description: requirement.description, - organizationPolicyId: organizationPolicy?.id || null, - organizationEvidenceId: organizationEvidence?.id || null, - }; - }), - }); - } - - return controlRequirements; + if (!organizationId) { + throw new Error("Not authorized - no organization found"); + } + + const controls = await db.organizationControl.findMany({ + where: { + organizationId, + organizationFrameworkId: { + in: organizationFrameworkIds, + }, + }, + include: { + control: true, + }, + }); + + // Create control requirements for each control + const controlRequirements = await db.controlRequirement.findMany({ + where: { + controlId: { in: controls.map((control) => control.controlId) }, + }, + include: { + policy: true, // Include the policy to get its ID + evidence: true, // Include the evidence to get its ID + }, + }); + + // Get all organization policies for this organization + const organizationPolicies = await db.organizationPolicy.findMany({ + where: { + organizationId, + }, + }); + + // Get all organization evidences for this organization + const organizationEvidences = await db.organizationEvidence.findMany({ + where: { + organizationId, + }, + }); + + for (const control of controls) { + const requirements = controlRequirements.filter( + (req) => req.controlId === control.controlId, + ); + + await db.organizationControlRequirement.createMany({ + data: requirements.map((requirement) => { + // Find the corresponding organization policy if this is a policy requirement + const policyId = + requirement.type === "policy" ? requirement.policy?.id : null; + const organizationPolicy = policyId + ? organizationPolicies.find((op) => op.policyId === policyId) + : null; + + const evidenceId = + requirement.type === "evidence" ? requirement.evidenceId : null; + + console.log({ + evidenceId, + }); + + const organizationEvidence = evidenceId + ? organizationEvidences.find((e) => e.evidenceId === evidenceId) + : null; + + console.log({ + organizationEvidence, + }); + + return { + organizationControlId: control.id, + controlRequirementId: requirement.id, + type: requirement.type, + description: requirement.description, + organizationPolicyId: organizationPolicy?.id || null, + organizationEvidenceId: organizationEvidence?.id || null, + }; + }), + }); + } + + return controlRequirements; }; const createOrganizationEvidence = async ( - organizationId: string, - frameworkIds: string[], - userId: string + organizationId: string, + frameworkIds: string[], + userId: string, ) => { - if (!organizationId) { - throw new Error("Not authorized - no organization found"); - } - - const evidence = await db.controlRequirement.findMany({ - where: { - type: RequirementType.evidence, - }, - include: { - control: { - include: { - frameworkCategory: { - include: { - framework: true, - }, - }, - }, - }, - }, - }); - - const organizationEvidence = await db.organizationEvidence.createMany({ - data: evidence.map((evidence) => ({ - organizationId, - evidenceId: evidence.id, - name: evidence.name, - description: evidence.description, - frequency: evidence.frequency, - frameworkId: evidence.control.frameworkCategory?.framework.id || "", - assigneeId: userId, - department: evidence.department, - })), - }); - - return organizationEvidence; + if (!organizationId) { + throw new Error("Not authorized - no organization found"); + } + + const evidence = await db.controlRequirement.findMany({ + where: { + type: RequirementType.evidence, + }, + include: { + control: { + include: { + frameworkCategory: { + include: { + framework: true, + }, + }, + }, + }, + }, + }); + + const organizationEvidence = await db.organizationEvidence.createMany({ + data: evidence.map((evidence) => ({ + organizationId, + evidenceId: evidence.id, + name: evidence.name, + description: evidence.description, + frequency: evidence.frequency, + frameworkId: evidence.control.frameworkCategory?.framework.id || "", + assigneeId: userId, + department: evidence.department, + })), + }); + + return organizationEvidence; };