Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .routes.chat import router as chat_router
from .routes.match import router as match_router
from sqlalchemy.exc import SQLAlchemyError
from .routes.collaboration import router as collaboration_router
import logging
import os
from dotenv import load_dotenv
Expand Down Expand Up @@ -56,7 +57,7 @@ async def lifespan(app: FastAPI):
app.include_router(match_router)
app.include_router(ai.router)
app.include_router(ai.youtube_router)

app.include_router(collaboration_router)

@app.get("/")
async def home():
Expand Down
31 changes: 31 additions & 0 deletions Backend/app/routes/collaboration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import update
from ..db.db import get_db
from ..models.models import Collaboration

router = APIRouter(prefix="/collaboration", tags=["Collaboration"])

@router.put("/update-status/{collab_id}")
async def update_collab_status(
collab_id: str,
status: str,
db: Annotated[AsyncSession, Depends(get_db)]
):
"""
Updates the status of a collaboration request.
Includes validation to ensure the record exists.
"""
if status not in ["accepted", "denied"]:
raise HTTPException(status_code=400, detail="Invalid status")

query = update(Collaboration).where(Collaboration.id == collab_id).values(status=status)
result = await db.execute(query)
await db.commit()

# Major fix: Check if the row actually existed
if result.rowcount == 0:
raise HTTPException(status_code=404, detail=f"Collaboration {collab_id} not found")

return {"message": f"Collaboration {status} successfully"}
Comment on lines +23 to +31
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Silent no-op when collab_id doesn't exist — returns 200 instead of 404

db.execute(update(...)) succeeds even when zero rows match. The response always reports success. Check result.rowcount and raise a 404 if nothing was updated.

🐛 Proposed fix
     query = update(Collaboration).where(Collaboration.id == collab_id).values(status=status)
-    await db.execute(query)
+    result = await db.execute(query)
     await db.commit()
+    if result.rowcount == 0:
+        raise HTTPException(status_code=404, detail="Collaboration not found")
     return {"message": f"Collaboration {status} successfully"}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
query = update(Collaboration).where(Collaboration.id == collab_id).values(status=status)
await db.execute(query)
await db.commit()
return {"message": f"Collaboration {status} successfully"}
query = update(Collaboration).where(Collaboration.id == collab_id).values(status=status)
result = await db.execute(query)
await db.commit()
if result.rowcount == 0:
raise HTTPException(status_code=404, detail="Collaboration not found")
return {"message": f"Collaboration {status} successfully"}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/routes/collaboration.py` around lines 15 - 18, The handler
currently calls db.execute(update(Collaboration)... ) and always returns
success; change it to capture the execution result (e.g., result = await
db.execute(query)), check result.rowcount, and if rowcount == 0 raise an
HTTPException(status_code=404, detail=f"Collaboration {collab_id} not found");
only call await db.commit() and return the success message when rowcount > 0.
Make sure to import HTTPException from fastapi if not already present.

23 changes: 23 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Use a stable Python base image
FROM python:3.10-slim
Comment on lines +1 to +2
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Container runs as root — add a non-root USER.

Running the application as root inside the container is a security risk. If the process is compromised, the attacker has full root privileges within the container. This was also flagged by Trivy (DS-0002).

Proposed fix — add a non-root user at the end
+# Create a non-root user
+RUN useradd --create-home appuser
+USER appuser
+
 # Change to the Backend directory to run the application
 WORKDIR /app/Backend
🧰 Tools
🪛 Trivy (0.69.1)

[error] 1-1: Image user should not be 'root'

Specify at least 1 USER command in Dockerfile with non-root user as argument

Rule: DS-0002

Learn more

(IaC/Dockerfile)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 1 - 2, The container currently runs as root; add a
non-root user and switch to it at the end of the Dockerfile: create a user and
group (e.g., appuser/appgroup) with non-root UID/GID, ensure application
files/directories are owned by that user (chown relevant paths), set appropriate
permissions, and add a USER appuser (or UID:GID) instruction as the final step
so the process runs unprivileged; reference the Dockerfile and the USER
directive when making the changes.


# Set the working directory
WORKDIR /app

# Install system-level dependencies for building Python packages
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
Comment on lines +8 to +10
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add --no-install-recommends to reduce image bloat.

Without this flag, apt-get install pulls in recommended (but unnecessary) packages, increasing image size. Also flagged by Trivy (DS-0029).

Proposed fix
-RUN apt-get update && apt-get install -y \
+RUN apt-get update && apt-get install -y --no-install-recommends \
     build-essential \
     && rm -rf /var/lib/apt/lists/*
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
🧰 Tools
🪛 Trivy (0.69.1)

[error] 8-10: 'apt-get' missing '--no-install-recommends'

'--no-install-recommends' flag is missed: 'apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/*'

Rule: DS-0029

Learn more

(IaC/Dockerfile)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 8 - 10, The apt-get install invocation in the
Dockerfile (the RUN line that performs "apt-get update && apt-get install -y
build-essential && rm -rf /var/lib/apt/lists/*") should include
--no-install-recommends to avoid installing unnecessary recommended packages;
update that RUN command to use "apt-get install -y --no-install-recommends
build-essential" (keeping the existing apt-get update and the rm -rf
/var/lib/apt/lists/* cleanup) so the image size is reduced and Trivy DS-0029 is
addressed.


# Copy requirements from the Backend folder and install
COPY Backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the entire project into the container
COPY . .

# Set environment variables (standard for AI platforms)
ENV PYTHONUNBUFFERED=1

# Change to the Backend directory to run the application
WORKDIR /app/Backend
Comment on lines +1 to +23
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

No CMD or ENTRYPOINT — the container has no default run command.

The Dockerfile sets up the environment but never declares what to run. Without a CMD or ENTRYPOINT, docker run on this image will drop into a shell (inherited from the base image) rather than starting the backend service. This makes the image unusable out of the box.

Add an appropriate entrypoint, e.g.:

Proposed fix
 # Change to the Backend directory to run the application
 WORKDIR /app/Backend
+
+# Run the application
+CMD ["python", "main.py"]

Adjust main.py to whatever the actual entry point script is.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Use a stable Python base image
FROM python:3.10-slim
# Set the working directory
WORKDIR /app
# Install system-level dependencies for building Python packages
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements from the Backend folder and install
COPY Backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy the entire project into the container
COPY . .
# Set environment variables (standard for AI platforms)
ENV PYTHONUNBUFFERED=1
# Change to the Backend directory to run the application
WORKDIR /app/Backend
# Use a stable Python base image
FROM python:3.10-slim
# Set the working directory
WORKDIR /app
# Install system-level dependencies for building Python packages
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements from the Backend folder and install
COPY Backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy the entire project into the container
COPY . .
# Set environment variables (standard for AI platforms)
ENV PYTHONUNBUFFERED=1
# Change to the Backend directory to run the application
WORKDIR /app/Backend
# Run the application
CMD ["python", "main.py"]
🧰 Tools
🪛 Trivy (0.69.1)

[error] 1-1: Image user should not be 'root'

Specify at least 1 USER command in Dockerfile with non-root user as argument

Rule: DS-0002

Learn more

(IaC/Dockerfile)


[error] 8-10: 'apt-get' missing '--no-install-recommends'

'--no-install-recommends' flag is missed: 'apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/*'

Rule: DS-0029

Learn more

(IaC/Dockerfile)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 1 - 23, The Dockerfile currently misses a default
runtime command; add an ENTRYPOINT or CMD in the Dockerfile to start the backend
service (e.g., invoke Python on your app entrypoint or start Uvicorn for an ASGI
app). Update the Dockerfile to include a final line that runs the correct
entrypoint (reference the project's actual startup target such as main.py or the
ASGI app module) and ensure the referenced script/module (e.g., main.py or
app:app) is present in Backend and executable; adjust the entrypoint string to
match that symbol.

80 changes: 63 additions & 17 deletions Frontend/src/components/collaboration-hub/CollabRequests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import { Badge } from "../ui/badge";
import { Separator } from "../ui/separator";
import { MessageSquare, CheckCircle, XCircle, Lightbulb, TrendingUp, Users, Star, Mail } from "lucide-react";

// Mock data for incoming requests
// 1. Move BASE_URL to module scope to avoid evaluation on every render
const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";

// Mock data for incoming requests with string IDs for UUID compatibility
const mockRequests = [
{
id: 1,
id: "1",
sender: {
name: "TechSavvy",
avatar: "https://randomuser.me/api/portraits/men/32.jpg",
Expand Down Expand Up @@ -51,7 +54,7 @@ const mockRequests = [
}
},
{
id: 2,
id: "2",
sender: {
name: "EcoChic",
avatar: "https://randomuser.me/api/portraits/women/44.jpg",
Expand Down Expand Up @@ -96,18 +99,52 @@ const mockRequests = [

const CollabRequests: React.FC = () => {
const [requests, setRequests] = useState(mockRequests);
// 2. Add loadingId state to track and disable in-flight requests
const [loadingId, setLoadingId] = useState<string | null>(null);

const handleAccept = (id: number) => {
setRequests(prev => prev.filter(req => req.id !== id));
// TODO: Integrate with backend to accept request
const handleAccept = async (id: string) => {
if (loadingId) return; // Prevent concurrent requests
setLoadingId(id);
try {
const response = await fetch(`${BASE_URL}/collaboration/update-status/${id}?status=accepted`, {
method: 'PUT',
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.py" | head -20

Repository: AOSSIE-Org/InPactAI

Length of output: 682


🏁 Script executed:

# Look for the backend endpoint implementation
rg -n "update-status|update_status" -t py -B 5 -A 10

Repository: AOSSIE-Org/InPactAI

Length of output: 1150


🏁 Script executed:

# Search for authentication patterns in the codebase
rg -n "Depends\(.*get_current_user\)|HTTPBearer|oauth2_scheme|@.*auth" -t py -B 2 -A 2

Repository: AOSSIE-Org/InPactAI

Length of output: 316


🏁 Script executed:

cat -n Backend/app/routes/collaboration.py | head -50

Repository: AOSSIE-Org/InPactAI

Length of output: 1385


🏁 Script executed:

# Check other routes to see authentication patterns
rg -n "get_current_user|HTTPBearer|oauth2_scheme" Backend/app/routes/ -B 2 -A 2

Repository: AOSSIE-Org/InPactAI

Length of output: 45


🏁 Script executed:

# Check main.py for middleware or global auth setup
cat -n Backend/app/main.py

Repository: AOSSIE-Org/InPactAI

Length of output: 2425


🏁 Script executed:

# Check full auth.py to understand authentication mechanisms
cat -n Backend/app/routes/auth.py

Repository: AOSSIE-Org/InPactAI

Length of output: 251


🏁 Script executed:

# Check other routes that modify data (POST, PUT, DELETE) for auth patterns
rg -n "@router\.(post|put|delete)" Backend/app/routes/ -A 5

Repository: AOSSIE-Org/InPactAI

Length of output: 4408


Backend endpoint currently lacks authentication — critical security vulnerability

The /collaboration/update-status/{id} endpoint does not require authentication. While the frontend omits auth headers, the real issue is that the backend allows unauthenticated requests to modify collaboration records. Any user can update any collaboration status without verification.

The endpoint must implement authentication (e.g., Depends(get_current_user)) and authorization checks (verify the requester owns or has permission for the collaboration).

Also applies to: 122-124

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Frontend/src/components/collaboration-hub/CollabRequests.tsx` around lines
105 - 107, The backend endpoint /collaboration/update-status/{id} must require
authentication and enforce authorization: update the route handler that serves
this endpoint to include an authentication dependency (e.g., add
Depends(get_current_user) to the handler signature) and, inside the handler (the
function implementing update-status), verify that the authenticated user is the
owner or has permission to modify the collaboration record with the given id
before applying the status change; if the check fails return an appropriate
401/403 response. Ensure the same changes are applied to the other update-status
variant referenced (lines 122-124) so all status updates are protected.

if (response.ok) {
setRequests(prev => prev.filter(req => req.id !== id));
} else {
const err = await response.json().catch(() => ({}));
alert(`Failed to accept: ${err.detail || "Server error"}`);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} catch (error) {
console.error("Failed to accept request:", error);
} finally {
setLoadingId(null);
}
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

const handleDeny = (id: number) => {
setRequests(prev => prev.filter(req => req.id !== id));
// TODO: Integrate with backend to deny request
const handleDeny = async (id: string) => {
if (loadingId) return; // Prevent concurrent requests
setLoadingId(id);
try {
const response = await fetch(`${BASE_URL}/collaboration/update-status/${id}?status=denied`, {
method: 'PUT',
});
if (response.ok) {
setRequests(prev => prev.filter(req => req.id !== id));
} else {
// 3. Extract real server error instead of hardcoded string
const err = await response.json().catch(() => ({}));
alert(`Failed to deny: ${err.detail || "Server error"}`);
}
} catch (error) {
console.error("Failed to deny request:", error);
} finally {
setLoadingId(null);
}
};

const handleMessage = (id: number) => {
// 4. Mark unused parameter with underscore to silence linter
const handleMessage = (_id: string) => {
// TODO: Open message modal or redirect to chat
alert("Open chat with sender (not implemented)");
};
Expand Down Expand Up @@ -140,7 +177,6 @@ const CollabRequests: React.FC = () => {
<div className="mb-3">
<span className="font-medium text-gray-800">Request:</span> {req.summary}
</div>
{/* Initial Collaboration Proposal Section */}
<div className="bg-purple-50 border border-purple-100 rounded-lg p-3 mb-4">
<div className="flex items-center gap-2 mb-1 text-purple-700 font-semibold text-sm">
<span role="img" aria-label="proposal">📝</span> Initial Collaboration Proposal
Expand All @@ -154,21 +190,18 @@ const CollabRequests: React.FC = () => {
</ul>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
{/* AI Advantages */}
<div className="bg-blue-50 border border-blue-100 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1 text-blue-700 font-semibold text-sm"><Lightbulb className="h-4 w-4" /> Advantages</div>
<ul className="list-disc ml-5 text-xs text-blue-900">
{req.ai.advantages.map((adv, i) => <li key={i}>{adv}</li>)}
</ul>
</div>
{/* AI Ideas */}
<div className="bg-green-50 border border-green-100 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1 text-green-700 font-semibold text-sm"><Lightbulb className="h-4 w-4" /> Collaboration Ideas</div>
<ul className="list-disc ml-5 text-xs text-green-900">
{req.ai.ideas.map((idea, i) => <li key={i}>{idea}</li>)}
</ul>
</div>
{/* AI Recommendations */}
<div className="bg-yellow-50 border border-yellow-100 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1 text-yellow-700 font-semibold text-sm"><Lightbulb className="h-4 w-4" /> Recommendations</div>
<ul className="list-disc ml-5 text-xs text-yellow-900">
Expand All @@ -183,10 +216,23 @@ const CollabRequests: React.FC = () => {
<span>Avg Views: <b>{req.stats.avgViews}</b></span>
</div>
<div className="flex gap-3 mt-4">
<Button size="sm" className="flex items-center gap-1 bg-green-100 text-green-800 hover:bg-green-200 border border-green-200" onClick={() => handleAccept(req.id)} aria-label="Accept collaboration request">
{/* 5. Disable buttons when a request is in progress */}
<Button
size="sm"
className="flex items-center gap-1 bg-green-100 text-green-800 hover:bg-green-200 border border-green-200"
onClick={() => handleAccept(req.id)}
disabled={loadingId === req.id}
aria-label="Accept collaboration request"
>
<CheckCircle className="h-4 w-4" /> Accept
</Button>
<Button size="sm" className="flex items-center gap-1 bg-red-100 text-red-800 hover:bg-red-200 border border-red-200" onClick={() => handleDeny(req.id)} aria-label="Deny collaboration request">
<Button
size="sm"
className="flex items-center gap-1 bg-red-100 text-red-800 hover:bg-red-200 border border-red-200"
onClick={() => handleDeny(req.id)}
disabled={loadingId === req.id}
aria-label="Deny collaboration request"
>
<XCircle className="h-4 w-4" /> Deny
</Button>
<Button size="sm" variant="outline" className="flex items-center gap-1" onClick={() => handleMessage(req.id)} aria-label="Message sender">
Expand All @@ -204,4 +250,4 @@ const CollabRequests: React.FC = () => {
);
};

export default CollabRequests;
export default CollabRequests;