From b76e2a10c879db40f624c2eb53f4acbc2e8c0d3e Mon Sep 17 00:00:00 2001 From: ZHallen122 Date: Mon, 17 Mar 2025 21:51:46 -0400 Subject: [PATCH 1/3] Fix synchronise security issue --- backend/src/project/project.service.ts | 56 +++++++++++++++----------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/backend/src/project/project.service.ts b/backend/src/project/project.service.ts index 56eac43d..9c6f7c24 100644 --- a/backend/src/project/project.service.ts +++ b/backend/src/project/project.service.ts @@ -195,8 +195,31 @@ export class ProjectService { message: input.description, }); + // Create project entity and set properties + const project = new Project(); + project.projectName = projectName; + project.projectPath = ''; + project.userId = userId; + project.isPublic = input.public || false; + project.uniqueProjectId = uuidv4(); + + // Set project packages + try { + project.projectPackages = await this.transformInputToProjectPackages( + input.packages, + ); + } catch (packageError) { + this.logger.error(`Error processing packages: ${packageError.message}`); + // Continue even if packages processing fails + project.projectPackages = []; + } + + // Save project + const savedProject = await this.projectsRepository.save(project); + this.logger.debug(`Project created: ${savedProject.id}`); + // Perform the rest of project creation asynchronously - this.createProjectInBackground(input, projectName, userId, defaultChat); + this.createProjectInBackground(input, projectName, savedProject, defaultChat); // Return chat immediately so user can start interacting return defaultChat; @@ -217,7 +240,7 @@ export class ProjectService { private async createProjectInBackground( input: CreateProjectInput, projectName: string, - userId: string, + savedProject: Project, chat: Chat, ): Promise { try { @@ -227,31 +250,18 @@ export class ProjectService { projectName, }); const context = new BuilderContext(sequence, sequence.id); - const projectPath = await context.execute(); - // Create project entity and set properties - const project = new Project(); - project.projectName = projectName; - project.projectPath = projectPath; - project.userId = userId; - project.isPublic = input.public || false; - project.uniqueProjectId = uuidv4(); + // Execute the context and update projectPath + const projectPath = await context.execute(); - // Set project packages - try { - project.projectPackages = await this.transformInputToProjectPackages( - input.packages, - ); - } catch (packageError) { - this.logger.error(`Error processing packages: ${packageError.message}`); - // Continue even if packages processing fails - project.projectPackages = []; + if (projectPath) { + savedProject.projectPath = projectPath; + await this.projectsRepository.save(savedProject); // Update the project with path + this.logger.debug(`Updated project path: ${savedProject.id} -> ${projectPath}`); + } else { + this.logger.error(`Failed to retrieve project path for: ${savedProject.id}`); } - // Save project - const savedProject = await this.projectsRepository.save(project); - this.logger.debug(`Project created: ${savedProject.id}`); - // Bind chat to project const bindSuccess = await this.bindProjectAndChat(savedProject, chat); if (!bindSuccess) { From 3e80054d71ecf2abda666bcec6ca2bb9aad69326 Mon Sep 17 00:00:00 2001 From: ZHallen122 Date: Tue, 18 Mar 2025 10:43:38 -0400 Subject: [PATCH 2/3] update frontend rate limit --- frontend/src/app/(main)/page.tsx | 27 +++++++++-- .../chat/code-engine/project-context.tsx | 45 +++++++++++++++--- frontend/src/components/rate-limit-modal.tsx | 47 +++++++++++++++++++ 3 files changed, 108 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/rate-limit-modal.tsx diff --git a/frontend/src/app/(main)/page.tsx b/frontend/src/app/(main)/page.tsx index 60f91e01..560400c5 100644 --- a/frontend/src/app/(main)/page.tsx +++ b/frontend/src/app/(main)/page.tsx @@ -7,23 +7,32 @@ import { AuthChoiceModal } from '@/components/auth-choice-modal'; import { useAuthContext } from '@/providers/AuthProvider'; import { ProjectsSection } from '@/components/root/projects-section'; import { PromptForm, PromptFormRef } from '@/components/root/prompt-form'; -import { ProjectContext } from '@/components/chat/code-engine/project-context'; +import { CreateProjectResult, ProjectContext } from '@/components/chat/code-engine/project-context'; import { SignInModal } from '@/components/sign-in-modal'; import { SignUpModal } from '@/components/sign-up-modal'; import { useRouter } from 'next/navigation'; import { logger } from '../log/logger'; import { AuroraText } from '@/components/magicui/aurora-text'; +import { RateLimitModal } from '@/components/rate-limit-modal'; export default function HomePage() { // States for AuthChoiceModal const [showAuthChoice, setShowAuthChoice] = useState(false); const router = useRouter(); const [showSignIn, setShowSignIn] = useState(false); const [showSignUp, setShowSignUp] = useState(false); + const [showRateLimitModal, setShowRateLimitModal] = useState(false); + const [rateLimiNumber, setRateLimiNumber] = useState(undefined);; const promptFormRef = useRef(null); const { isAuthorized } = useAuthContext(); const { createProjectFromPrompt, isLoading } = useContext(ProjectContext); + function isRateLimitResult( + result: CreateProjectResult + ): result is { success: false; rateLimit?: boolean; limitNumber?: number } { + return result.success === false; + } + const handleSubmit = async () => { if (!promptFormRef.current) return; @@ -31,10 +40,17 @@ export default function HomePage() { if (!message.trim()) return; try { - const chatId = await createProjectFromPrompt(message, isPublic, model); + const result = await createProjectFromPrompt(message, isPublic, model); + + if (result.success && result.chatId) { + promptFormRef.current.clearMessage(); + router.push(`/chat?id=${result.chatId}`); + } else if (isRateLimitResult(result) && result.rateLimit) { + setShowRateLimitModal(true); + setRateLimiNumber(result.limitNumber); + console.log('Rate limit reached ' + result.limitNumber); + } - promptFormRef.current.clearMessage(); - router.push(`/chat?id=${chatId}`); } catch (error) { logger.error('Error creating project:', error); } @@ -178,6 +194,9 @@ export default function HomePage() { setShowSignIn(false)} /> setShowSignUp(false)} /> + + setShowRateLimitModal(false)} /> + ); diff --git a/frontend/src/components/chat/code-engine/project-context.tsx b/frontend/src/components/chat/code-engine/project-context.tsx index dbc6d25e..0de64afd 100644 --- a/frontend/src/components/chat/code-engine/project-context.tsx +++ b/frontend/src/components/chat/code-engine/project-context.tsx @@ -23,6 +23,8 @@ import { useAuthContext } from '@/providers/AuthProvider'; import { URL_PROTOCOL_PREFIX } from '@/utils/const'; import { logger } from '@/app/log/logger'; +export type CreateProjectResult = { success: true, chatId: string } | { success: false, rateLimit?: boolean, limitNumber?: number }; + export interface ProjectContextType { projects: Project[]; setProjects: React.Dispatch>; @@ -36,7 +38,7 @@ export interface ProjectContextType { prompt: string, isPublic: boolean, model?: string - ) => Promise; + ) => Promise; forkProject: (projectId: string) => Promise; setProjectPublicStatus: ( projectId: string, @@ -103,6 +105,8 @@ export function ProjectProvider({ children }: { children: ReactNode }) { const [projectLoading, setProjectLoading] = useState(true); const [filePath, setFilePath] = useState(null); const [isLoading, setIsLoading] = useState(false); + const rateLimitReachedRef = useRef(false); + const rateLimitValueRef = useRef(undefined); interface ChatProjectCacheEntry { project: Project | null; @@ -403,11 +407,24 @@ export function ProjectProvider({ children }: { children: ReactNode }) { // Create project mutation const [createProject] = useMutation(CREATE_PROJECT, { onCompleted: (data) => { + console.log('createProject have yes'); if (!isMounted.current) return; }, onError: (error) => { - if (isMounted.current) { - toast.error(`Failed to create project: ${error.message}`); + const graphqlErrors = error?.graphQLErrors || []; + + const rateLimitError = graphqlErrors.find( + (err) => err.extensions?.code === 'DAILY_LIMIT_EXCEEDED' + ); + + if (rateLimitError) { + rateLimitReachedRef.current = true; + const limitFromBackend = rateLimitError.extensions?.limit; + if(typeof limitFromBackend === 'number'){ + rateLimitValueRef.current = limitFromBackend; + } + } else { + logger.error(`GraphQL error: ${error.message}`); } }, }); @@ -684,12 +701,12 @@ export function ProjectProvider({ children }: { children: ReactNode }) { prompt: string, isPublic: boolean, model = 'gpt-4o-mini' - ): Promise => { + ): Promise => { if (!prompt.trim()) { if (isMounted.current) { toast.error('Please enter a project description'); } - return false; + return { success: false }; } try { @@ -697,6 +714,8 @@ export function ProjectProvider({ children }: { children: ReactNode }) { setIsLoading(true); } + rateLimitReachedRef.current = false; + // Default packages based on typical web project needs const defaultPackages = [ { name: 'react', version: '^18.2.0' }, @@ -715,13 +734,25 @@ export function ProjectProvider({ children }: { children: ReactNode }) { }, }); - return result.data.createProject.id; + // Rate limit detected + if (rateLimitReachedRef.current) { + return { success: false, rateLimit: true, limitNumber: rateLimitValueRef.current }; + } + + // Check if result and result.data exist before accessing properties + if (result?.data?.createProject?.id) { + return { success: true, chatId: result.data.createProject.id }; + } else { + logger.warn('Project creation response missing expected data structure'); + return { success: false }; + } } catch (error) { logger.error('Error creating project:', error); + if (isMounted.current) { toast.error('Failed to create project from prompt'); } - return false; + return { success: false }; } finally { if (isMounted.current) { setIsLoading(false); diff --git a/frontend/src/components/rate-limit-modal.tsx b/frontend/src/components/rate-limit-modal.tsx new file mode 100644 index 00000000..cf382b4a --- /dev/null +++ b/frontend/src/components/rate-limit-modal.tsx @@ -0,0 +1,47 @@ +// components/rate-limit-modal.tsx +'use client'; + +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { AlertTriangle } from 'lucide-react'; + +interface RateLimitModalProps { + isOpen: boolean; + onClose: () => void; + limit?: number; +} + +export const RateLimitModal = ({ isOpen, onClose, limit = 3 }: RateLimitModalProps) => { + return ( + !open && onClose()}> + + +
+ + Daily Limit Reached +
+ + You've reached your daily project creation limit. + +
+ +
+

+ Your current plan allows creating up to {limit} projects per day. This limit resets at midnight UTC. +

+
+

+ Try editing or reusing one of your existing projects, or wait until tomorrow to create a new one. +

+
+
+ + + + +
+
+ ); +}; \ No newline at end of file From 443ebb7c86a7f9f7222f8d4ee7a6586d6f4251cd Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 14:45:05 +0000 Subject: [PATCH 3/3] [autofix.ci] apply automated fixes --- backend/src/project/project.service.ts | 15 +++++++-- frontend/src/app/(main)/page.tsx | 19 ++++++++---- .../chat/code-engine/project-context.tsx | 16 +++++++--- frontend/src/components/rate-limit-modal.tsx | 31 +++++++++++++------ 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/backend/src/project/project.service.ts b/backend/src/project/project.service.ts index 9c6f7c24..47b1357c 100644 --- a/backend/src/project/project.service.ts +++ b/backend/src/project/project.service.ts @@ -219,7 +219,12 @@ export class ProjectService { this.logger.debug(`Project created: ${savedProject.id}`); // Perform the rest of project creation asynchronously - this.createProjectInBackground(input, projectName, savedProject, defaultChat); + this.createProjectInBackground( + input, + projectName, + savedProject, + defaultChat, + ); // Return chat immediately so user can start interacting return defaultChat; @@ -257,9 +262,13 @@ export class ProjectService { if (projectPath) { savedProject.projectPath = projectPath; await this.projectsRepository.save(savedProject); // Update the project with path - this.logger.debug(`Updated project path: ${savedProject.id} -> ${projectPath}`); + this.logger.debug( + `Updated project path: ${savedProject.id} -> ${projectPath}`, + ); } else { - this.logger.error(`Failed to retrieve project path for: ${savedProject.id}`); + this.logger.error( + `Failed to retrieve project path for: ${savedProject.id}`, + ); } // Bind chat to project diff --git a/frontend/src/app/(main)/page.tsx b/frontend/src/app/(main)/page.tsx index 560400c5..a31e4e75 100644 --- a/frontend/src/app/(main)/page.tsx +++ b/frontend/src/app/(main)/page.tsx @@ -7,7 +7,10 @@ import { AuthChoiceModal } from '@/components/auth-choice-modal'; import { useAuthContext } from '@/providers/AuthProvider'; import { ProjectsSection } from '@/components/root/projects-section'; import { PromptForm, PromptFormRef } from '@/components/root/prompt-form'; -import { CreateProjectResult, ProjectContext } from '@/components/chat/code-engine/project-context'; +import { + CreateProjectResult, + ProjectContext, +} from '@/components/chat/code-engine/project-context'; import { SignInModal } from '@/components/sign-in-modal'; import { SignUpModal } from '@/components/sign-up-modal'; import { useRouter } from 'next/navigation'; @@ -21,7 +24,9 @@ export default function HomePage() { const [showSignIn, setShowSignIn] = useState(false); const [showSignUp, setShowSignUp] = useState(false); const [showRateLimitModal, setShowRateLimitModal] = useState(false); - const [rateLimiNumber, setRateLimiNumber] = useState(undefined);; + const [rateLimiNumber, setRateLimiNumber] = useState( + undefined + ); const promptFormRef = useRef(null); const { isAuthorized } = useAuthContext(); @@ -32,7 +37,7 @@ export default function HomePage() { ): result is { success: false; rateLimit?: boolean; limitNumber?: number } { return result.success === false; } - + const handleSubmit = async () => { if (!promptFormRef.current) return; @@ -50,7 +55,6 @@ export default function HomePage() { setRateLimiNumber(result.limitNumber); console.log('Rate limit reached ' + result.limitNumber); } - } catch (error) { logger.error('Error creating project:', error); } @@ -195,8 +199,11 @@ export default function HomePage() { setShowSignIn(false)} /> setShowSignUp(false)} /> - setShowRateLimitModal(false)} /> - + setShowRateLimitModal(false)} + /> ); diff --git a/frontend/src/components/chat/code-engine/project-context.tsx b/frontend/src/components/chat/code-engine/project-context.tsx index 0de64afd..b763b0be 100644 --- a/frontend/src/components/chat/code-engine/project-context.tsx +++ b/frontend/src/components/chat/code-engine/project-context.tsx @@ -23,7 +23,9 @@ import { useAuthContext } from '@/providers/AuthProvider'; import { URL_PROTOCOL_PREFIX } from '@/utils/const'; import { logger } from '@/app/log/logger'; -export type CreateProjectResult = { success: true, chatId: string } | { success: false, rateLimit?: boolean, limitNumber?: number }; +export type CreateProjectResult = + | { success: true; chatId: string } + | { success: false; rateLimit?: boolean; limitNumber?: number }; export interface ProjectContextType { projects: Project[]; @@ -420,7 +422,7 @@ export function ProjectProvider({ children }: { children: ReactNode }) { if (rateLimitError) { rateLimitReachedRef.current = true; const limitFromBackend = rateLimitError.extensions?.limit; - if(typeof limitFromBackend === 'number'){ + if (typeof limitFromBackend === 'number') { rateLimitValueRef.current = limitFromBackend; } } else { @@ -736,14 +738,20 @@ export function ProjectProvider({ children }: { children: ReactNode }) { // Rate limit detected if (rateLimitReachedRef.current) { - return { success: false, rateLimit: true, limitNumber: rateLimitValueRef.current }; + return { + success: false, + rateLimit: true, + limitNumber: rateLimitValueRef.current, + }; } // Check if result and result.data exist before accessing properties if (result?.data?.createProject?.id) { return { success: true, chatId: result.data.createProject.id }; } else { - logger.warn('Project creation response missing expected data structure'); + logger.warn( + 'Project creation response missing expected data structure' + ); return { success: false }; } } catch (error) { diff --git a/frontend/src/components/rate-limit-modal.tsx b/frontend/src/components/rate-limit-modal.tsx index cf382b4a..fe868fb4 100644 --- a/frontend/src/components/rate-limit-modal.tsx +++ b/frontend/src/components/rate-limit-modal.tsx @@ -1,7 +1,14 @@ // components/rate-limit-modal.tsx 'use client'; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { AlertTriangle } from 'lucide-react'; @@ -11,7 +18,11 @@ interface RateLimitModalProps { limit?: number; } -export const RateLimitModal = ({ isOpen, onClose, limit = 3 }: RateLimitModalProps) => { +export const RateLimitModal = ({ + isOpen, + onClose, + limit = 3, +}: RateLimitModalProps) => { return ( !open && onClose()}> @@ -24,24 +35,24 @@ export const RateLimitModal = ({ isOpen, onClose, limit = 3 }: RateLimitModalPro You've reached your daily project creation limit. - +

- Your current plan allows creating up to {limit} projects per day. This limit resets at midnight UTC. + Your current plan allows creating up to {limit} projects per day. + This limit resets at midnight UTC.

- Try editing or reusing one of your existing projects, or wait until tomorrow to create a new one. + Try editing or reusing one of your existing projects, or wait + until tomorrow to create a new one.

- + - +
); -}; \ No newline at end of file +};