From 202a0a63976b0e455fd9610b18cfa6e0513d1a01 Mon Sep 17 00:00:00 2001 From: ash1shkumar Date: Sat, 30 May 2026 02:01:00 +0530 Subject: [PATCH 1/2] feat: implement authentication middleware for protected APIs --- backend/middleware/auth.middleware.js | 36 +++++++++++++++++++++++++++ backend/routes/analytics.routes.js | 6 ++++- backend/routes/chat.routes.js | 4 ++- backend/routes/feed.routes.js | 7 ++++-- backend/routes/tasks.routes.js | 15 +++++++---- 5 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 backend/middleware/auth.middleware.js diff --git a/backend/middleware/auth.middleware.js b/backend/middleware/auth.middleware.js new file mode 100644 index 0000000..18bfd46 --- /dev/null +++ b/backend/middleware/auth.middleware.js @@ -0,0 +1,36 @@ +import supabase from "../config/db.js"; + +export const authenticateUser = async (req, res, next) => { + try { + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return res.status(401).json({ + error: "Unauthorized", + }); + } + + const token = authHeader.split(" ")[1]; + + const { + data: { user }, + error, + } = await supabase.auth.getUser(token); + + if (error || !user) { + return res.status(401).json({ + error: "Invalid token", + }); + } + + req.user = user; + + next(); + } catch (error) { + console.error("Authentication middleware error:", error); + + return res.status(500).json({ + error: "Internal server error", + }); + } +}; \ No newline at end of file diff --git a/backend/routes/analytics.routes.js b/backend/routes/analytics.routes.js index aa40fd9..20f1184 100644 --- a/backend/routes/analytics.routes.js +++ b/backend/routes/analytics.routes.js @@ -1,8 +1,12 @@ import express from "express"; import { getAnalytics } from "../controllers/analytics.controller.js"; +import { authenticateUser } from "../middleware/auth.middleware.js"; + const router = express.Router(); +router.use(authenticateUser); + router.get("/", getAnalytics); -export default router; +export default router; \ No newline at end of file diff --git a/backend/routes/chat.routes.js b/backend/routes/chat.routes.js index abe287e..c34eef7 100644 --- a/backend/routes/chat.routes.js +++ b/backend/routes/chat.routes.js @@ -1,10 +1,12 @@ import express from "express"; import { getMessages, sendMessage } from "../controllers/chat.controller.js"; +import { authenticateUser } from "../middleware/auth.middleware.js"; + const router = express.Router(); router.get("/", getMessages); -router.post("/", sendMessage); +router.post("/", authenticateUser, sendMessage); export default router; \ No newline at end of file diff --git a/backend/routes/feed.routes.js b/backend/routes/feed.routes.js index 83fc36f..20feeaf 100644 --- a/backend/routes/feed.routes.js +++ b/backend/routes/feed.routes.js @@ -1,9 +1,12 @@ import express from "express"; import { createFeedItem, getFeedItems } from "../controllers/feed.controller.js"; +import { authenticateUser } from "../middleware/auth.middleware.js"; + const router = express.Router(); router.get("/", getFeedItems); -router.post("/", createFeedItem); -export default router; +router.post("/", authenticateUser, createFeedItem); + +export default router; \ No newline at end of file diff --git a/backend/routes/tasks.routes.js b/backend/routes/tasks.routes.js index 062b7a8..715d966 100644 --- a/backend/routes/tasks.routes.js +++ b/backend/routes/tasks.routes.js @@ -7,13 +7,18 @@ import { deleteTask, } from "../controllers/tasks.controller.js"; +import { authenticateUser } from "../middleware/auth.middleware.js"; + const router = express.Router(); router.get("/", getTasks); -router.post("/", createTask); -router.patch("/:id", updateTaskStatus); -router.patch("/:id/edit", updateTask); -router.delete("/:id", deleteTask); +router.post("/", authenticateUser, createTask); + +router.patch("/:id", authenticateUser, updateTaskStatus); + +router.patch("/:id/edit", authenticateUser, updateTask); + +router.delete("/:id", authenticateUser, deleteTask); -export default router; +export default router; \ No newline at end of file From 899a730d28ff897c2ac9930c79445d14800e4db0 Mon Sep 17 00:00:00 2001 From: ash1shkumar Date: Sat, 30 May 2026 02:04:11 +0530 Subject: [PATCH 2/2] add frontent updated files --- frontend/app/chat/page.tsx | 60 ++++++++++--------- .../app/components/kanban/KanbanBoard.tsx | 23 ++++++- frontend/app/insights/analytics/page.tsx | 7 +++ frontend/app/insights/feed/page.tsx | 9 ++- 4 files changed, 69 insertions(+), 30 deletions(-) diff --git a/frontend/app/chat/page.tsx b/frontend/app/chat/page.tsx index 34dd992..c864cce 100644 --- a/frontend/app/chat/page.tsx +++ b/frontend/app/chat/page.tsx @@ -4,6 +4,7 @@ import { useState, useEffect, useRef, type ChangeEvent } from "react"; import { io, Socket } from "socket.io-client"; import EmojiPicker, { type EmojiClickData } from "emoji-picker-react"; import { Smile, Paperclip, Mic, Square } from "lucide-react"; +import { supabase } from "../lib/supabase"; type Message = { id?: string; @@ -200,35 +201,38 @@ socket.on("newMessage", (msg) => { // SEND async function handleSend() { - if ((!input.trim() && !selectedImage && !audioBlob) || !username.trim()) return; - - - const currentImage = selectedImage; - const currentAudio = audioBlob; - // send text to backend - await fetch("http://localhost:5000/api/chat", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - text: input, - username, - image: currentImage, - audio: currentAudio, - }), - }); + if ((!input.trim() && !selectedImage && !audioBlob) || !username.trim()) return; - // clear inputs - setInput(""); - setSelectedImage(null); - setAudioBlob(null); - setReplyingTo(null); - // auto scroll - bottomRef.current?.scrollIntoView({ - behavior: "smooth", - }); -} + const currentImage = selectedImage; + const currentAudio = audioBlob; + + const session = await supabase?.auth.getSession(); + + const token = session?.data.session?.access_token; + + await fetch("http://localhost:5000/api/chat", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + text: input, + username, + image: currentImage, + audio: currentAudio, + }), + }); + + setInput(""); + setSelectedImage(null); + setAudioBlob(null); + setReplyingTo(null); + + bottomRef.current?.scrollIntoView({ + behavior: "smooth", + }); + } diff --git a/frontend/app/components/kanban/KanbanBoard.tsx b/frontend/app/components/kanban/KanbanBoard.tsx index 72b7fdd..0f83bcf 100644 --- a/frontend/app/components/kanban/KanbanBoard.tsx +++ b/frontend/app/components/kanban/KanbanBoard.tsx @@ -1,6 +1,7 @@ "use client"; import React, { useState, useEffect, useRef } from "react"; +import { supabase } from "../../lib/supabase"; import { DndContext, @@ -56,11 +57,17 @@ export function KanbanBoard() { ); try { + const session = await supabase?.auth.getSession(); + + const token = session?.data.session?.access_token; await fetch( `${process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000"}/api/tasks/${task.id}/edit`, { method: "PATCH", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, body: JSON.stringify({ title: newTitle, description: newDescription, @@ -241,6 +248,9 @@ export function KanbanBoard() { setTasks(updatedTasks); try { + const session = await supabase?.auth.getSession(); + + const token = session?.data.session?.access_token; // Send updates for all affected tasks await Promise.all( reorderedTasks.map((task) => @@ -250,6 +260,7 @@ export function KanbanBoard() { method: "PATCH", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${token}`, }, body: JSON.stringify({ status: task.status, @@ -286,10 +297,14 @@ export function KanbanBoard() { }; try { + const session = await supabase?.auth.getSession(); + + const token = session?.data.session?.access_token; await fetch(`${process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000"}/api/tasks`, { method: "POST", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${token}`, }, body: JSON.stringify(newTask), }); @@ -303,8 +318,14 @@ export function KanbanBoard() { if (!confirm("Are you sure you want to delete this task?")) return; try { + const session = await supabase?.auth.getSession(); + + const token = session?.data.session?.access_token; await fetch(`${process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000"}/api/tasks/${taskId}`, { method: "DELETE", + headers: { + Authorization: `Bearer ${token}`, + }, }); } catch (error) { console.error("Failed to delete task", error); diff --git a/frontend/app/insights/analytics/page.tsx b/frontend/app/insights/analytics/page.tsx index dff1c79..bea536c 100644 --- a/frontend/app/insights/analytics/page.tsx +++ b/frontend/app/insights/analytics/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; +import { supabase } from "@/app/lib/supabase"; import PerformanceSummary, { type AnalyticsMetric, type MemberPerformance, @@ -55,8 +56,14 @@ export default function InsightsAnalyticsPage() { setIsLoading(true); setLoadError(""); try { + const session = await supabase?.auth.getSession(); + + const token = session?.data.session?.access_token; const response = await fetch(`${API_URL}/api/analytics`, { signal: controller.signal, + headers: { + Authorization: `Bearer ${token}`, + }, }); if (!response.ok) throw new Error("Failed to load analytics"); const body = (await response.json()) as AnalyticsResponse; diff --git a/frontend/app/insights/feed/page.tsx b/frontend/app/insights/feed/page.tsx index cb807db..f842ae0 100644 --- a/frontend/app/insights/feed/page.tsx +++ b/frontend/app/insights/feed/page.tsx @@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from "react"; import FeedHeader, { type FeedFilter } from "@/app/components/Feed-comp/FeedHeader"; import FeedList, { type FeedActivityItem } from "@/app/components/Feed-comp/FeedList"; import FeedMobileNav from "@/app/components/Feed-comp/FeedMobileNav"; +import { supabase } from "@/app/lib/supabase"; const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000"; @@ -94,9 +95,15 @@ export default function InsightsFeedPage() { if (!body?.trim()) return; try { + const session = await supabase?.auth.getSession(); + + const token = session?.data.session?.access_token; const response = await fetch(`${API_URL}/api/feed`, { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, body: JSON.stringify({ title: title.trim(), body: body.trim(), type: "discussion" }), }); if (!response.ok) throw new Error("Failed to create insight");