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 affa427..5f3dd79 100644 --- a/backend/routes/chat.routes.js +++ b/backend/routes/chat.routes.js @@ -1,12 +1,14 @@ import express from "express"; import { getMessages, sendMessage } from "../controllers/chat.controller.js"; +import { authenticateUser } from "../middleware/auth.middleware.js"; import { validateMessage } from "../middleware/validation.middleware.js"; const router = express.Router(); router.get("/", getMessages); +router.post("/", authenticateUser, sendMessage); router.post("/", validateMessage, 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 e5167da..f48d3d3 100644 --- a/backend/routes/feed.routes.js +++ b/backend/routes/feed.routes.js @@ -1,12 +1,14 @@ import express from "express"; import { createFeedItem, getFeedItems } from "../controllers/feed.controller.js"; +import { authenticateUser } from "../middleware/auth.middleware.js"; import { validateFeedItem } from "../middleware/validation.middleware.js"; const router = express.Router(); router.get("/", getFeedItems); +router.post("/", authenticateUser, createFeedItem); router.post("/", validateFeedItem, 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 91dbab5..c7f11a4 100644 --- a/backend/routes/tasks.routes.js +++ b/backend/routes/tasks.routes.js @@ -7,12 +7,20 @@ import { deleteTask, } from "../controllers/tasks.controller.js"; +import { authenticateUser } from "../middleware/auth.middleware.js"; import { validateTask } from "../middleware/validation.middleware.js"; const router = express.Router(); router.get("/", getTasks); +router.post("/", authenticateUser, createTask); + +router.patch("/:id", authenticateUser, updateTaskStatus); + +router.patch("/:id/edit", authenticateUser, updateTask); + +router.delete("/:id", authenticateUser, deleteTask); router.post("/", validateTask, createTask); router.patch("/:id", validateTask, updateTaskStatus); diff --git a/frontend/app/chat/page.tsx b/frontend/app/chat/page.tsx index a87ffa5..dc2eb53 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; @@ -206,35 +207,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 c1d3eff..e879f17 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, @@ -245,6 +252,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) => @@ -254,6 +264,7 @@ export function KanbanBoard() { method: "PATCH", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${token}`, }, body: JSON.stringify({ status: task.status, @@ -293,10 +304,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), }); @@ -310,8 +325,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");