diff --git a/backend/middleware/auth.middleware.js b/backend/middleware/auth.middleware.js index 7cff84a..834b63c 100644 --- a/backend/middleware/auth.middleware.js +++ b/backend/middleware/auth.middleware.js @@ -1,123 +1,119 @@ import supabase from "../config/db.js"; -function createAuthError( - message, - code = "AUTHENTICATION_ERROR" -) { +/* -------------------------------------------------------------------------- */ +/* ERROR HANDLING */ +/* -------------------------------------------------------------------------- */ + +function createAuthError(message, code = "AUTHENTICATION_ERROR") { return { success: false, error: { code, message, + severity: "high", timestamp: Date.now(), + requestId: `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, + diagnostics: { + nodeEnv: process.env.NODE_ENV || "unknown", + service: "auth-middleware", + }, }, }; } -function extractBearerToken( - authHeader -) { - if ( - !authHeader || - typeof authHeader !== - "string" - ) { - return null; - } +/* -------------------------------------------------------------------------- */ +/* TOKEN UTILITIES */ +/* -------------------------------------------------------------------------- */ - if ( - !authHeader.startsWith( - "Bearer " - ) - ) { - return null; - } +function extractBearerToken(authHeader) { + if (!authHeader) return null; + if (typeof authHeader !== "string") return null; - const token = - authHeader - .replace( - "Bearer ", - "" - ) - .trim(); + const trimmedHeader = authHeader.trim(); - if (!token) { - return null; - } + if (!trimmedHeader.startsWith("Bearer ")) return null; + + const token = trimmedHeader.split("Bearer ")[1]?.trim(); + + if (!token || token.length === 0) return null; return token; } -function isValidTokenFormat( - token -) { - return ( - typeof token === - "string" && - token.length > 10 - ); -} +function isValidTokenFormat(token) { + if (!token) return false; + if (typeof token !== "string") return false; -async function resolveUserSession( - token -) { - const { - data: { user }, - error, - } = - await supabase.auth.getUser( - token - ); + if (token.length < 10) return false; + if (token.includes("undefined")) return false; + if (token.includes("null")) return false; - return { - user, - error, - }; + return true; } -function isAuthenticatedUser( - user, - error -) { - return !error && !!user; +/* -------------------------------------------------------------------------- */ +/* SUPABASE AUTH HANDLER */ +/* -------------------------------------------------------------------------- */ + +async function resolveUserSession(token) { + try { + const { + data: { user }, + error, + } = await supabase.auth.getUser(token); + + return { + user, + error, + success: !error && !!user, + }; + } catch (err) { + return { + user: null, + error: err, + success: false, + }; + } } -function createAuthContext( - req, - token -) { +function isAuthenticatedUser(user, error) { + return Boolean(user && !error); +} + +/* -------------------------------------------------------------------------- */ +/* AUTH CONTEXT BUILDERS */ +/* -------------------------------------------------------------------------- */ + +function createAuthContext(req, token) { return { - tokenLength: - token?.length || 0, - requestPath: - req.originalUrl, - requestMethod: - req.method, - validatedAt: - Date.now(), + tokenLength: token?.length || 0, + requestPath: req.originalUrl, + requestMethod: req.method, + userAgent: req.headers["user-agent"] || "unknown", + ip: req.ip || req.headers["x-forwarded-for"] || "unknown", + validatedAt: Date.now(), + sessionScope: "api-request", }; } -function validateRequestIntegrity( - req -) { - return Boolean( - req && - req.headers && - typeof req.headers === - "object" - ); +function validateRequestIntegrity(req) { + if (!req) return false; + if (!req.headers) return false; + if (typeof req.headers !== "object") return false; + + return true; } -function createAuthorizationCheckpoint( - stage, - passed -) { +/* -------------------------------------------------------------------------- */ +/* AUTHORIZATION LIFECYCLE TRACKING */ +/* -------------------------------------------------------------------------- */ + +function createAuthorizationCheckpoint(stage, passed) { return { stage, passed, - timestamp: - Date.now(), + timestamp: Date.now(), + memorySnapshot: process.memoryUsage().heapUsed, }; } @@ -125,165 +121,132 @@ function buildAuthLifecycle() { return { checkpoints: [], validationVersion: 1, + startedAt: Date.now(), + status: "initializing", }; } -function appendCheckpoint( - lifecycle, - stage, - passed -) { +function appendCheckpoint(lifecycle, stage, passed) { lifecycle.checkpoints.push( - createAuthorizationCheckpoint( - stage, - passed - ) + createAuthorizationCheckpoint(stage, passed) ); + lifecycle.status = passed ? "processing" : "failed"; + return lifecycle; } -export const authenticateUser = - async ( - req, - res, - next - ) => { - try { - const authLifecycle = - buildAuthLifecycle(); - - if ( - !validateRequestIntegrity( - req - ) - ) { - return res - .status(400) - .json( - createAuthError( - "Malformed request", - "REQUEST_INVALID" - ) - ); - } - - appendCheckpoint( - authLifecycle, - "request_integrity", - true - ); +/* -------------------------------------------------------------------------- */ +/* ADDITIONAL VALIDATORS */ +/* -------------------------------------------------------------------------- */ + +function isTokenTooLong(token) { + return token.length > 2048; +} - const authHeader = - req.headers.authorization; +function isSuspiciousToken(token) { + return token.includes("..") || token.includes("//"); +} - const token = - extractBearerToken( - authHeader - ); +function sanitizeToken(token) { + return token.trim(); +} - appendCheckpoint( - authLifecycle, - "token_extracted", - Boolean(token) - ); +/* -------------------------------------------------------------------------- */ +/* MAIN MIDDLEWARE */ +/* -------------------------------------------------------------------------- */ - if (!token) { - return res - .status(401) - .json( - createAuthError( - "Authorization token missing", - "TOKEN_MISSING" - ) - ); - } - - if ( - !isValidTokenFormat( - token - ) - ) { - return res - .status(401) - .json( - createAuthError( - "Malformed authorization token", - "TOKEN_INVALID_FORMAT" - ) - ); - } - - appendCheckpoint( - authLifecycle, - "token_format", - true - ); +export const authenticateUser = async (req, res, next) => { + const authLifecycle = buildAuthLifecycle(); - const authContext = - createAuthContext( - req, - token - ); - - const { - user, - error, - } = - await resolveUserSession( - token - ); - - if ( - !isAuthenticatedUser( - user, - error - ) - ) { - return res - .status(401) - .json( - createAuthError( - "Invalid authentication token", - "TOKEN_VERIFICATION_FAILED" - ) - ); - } - - appendCheckpoint( - authLifecycle, - "token_verified", - true - ); + try { + /* ---------------------- REQUEST VALIDATION ---------------------- */ + + if (!validateRequestIntegrity(req)) { + appendCheckpoint(authLifecycle, "request_integrity", false); + + return res + .status(400) + .json(createAuthError("Malformed request", "REQUEST_INVALID")); + } + + appendCheckpoint(authLifecycle, "request_integrity", true); + + /* ---------------------- TOKEN EXTRACTION ------------------------ */ - req.authContext = - authContext; + const rawToken = extractBearerToken(req.headers.authorization); + + appendCheckpoint(authLifecycle, "token_extracted", Boolean(rawToken)); + + if (!rawToken) { + return res + .status(401) + .json(createAuthError("Authorization token missing", "TOKEN_MISSING")); + } - req.authLifecycle = - authLifecycle; + const token = sanitizeToken(rawToken); - req.user = user; + /* ---------------------- TOKEN VALIDATION ------------------------ */ - appendCheckpoint( - authLifecycle, - "authorization_complete", - true + if (!isValidTokenFormat(token)) { + appendCheckpoint(authLifecycle, "token_format", false); + + return res.status(401).json( + createAuthError("Malformed authorization token", "TOKEN_INVALID_FORMAT") ); + } - next(); - } catch (error) { - console.error( - "Authentication middleware error:", - error + if (isTokenTooLong(token) || isSuspiciousToken(token)) { + appendCheckpoint(authLifecycle, "token_security", false); + + return res.status(401).json( + createAuthError("Suspicious token detected", "TOKEN_SECURITY_RISK") ); + } - return res - .status(500) - .json( - createAuthError( - "Internal authentication failure", - "AUTH_INTERNAL_ERROR" - ) - ); + appendCheckpoint(authLifecycle, "token_format", true); + appendCheckpoint(authLifecycle, "token_security", true); + + /* ---------------------- CONTEXT BUILD --------------------------- */ + + const authContext = createAuthContext(req, token); + + /* ---------------------- SESSION RESOLUTION ---------------------- */ + + const { user, error } = await resolveUserSession(token); + + if (!isAuthenticatedUser(user, error)) { + appendCheckpoint(authLifecycle, "token_verified", false); + + return res.status(401).json( + createAuthError("Invalid authentication token", "TOKEN_VERIFICATION_FAILED") + ); } - }; \ No newline at end of file + + appendCheckpoint(authLifecycle, "token_verified", true); + + /* ---------------------- FINALIZE ------------------------------- */ + + req.authContext = authContext; + req.authLifecycle = { + ...authLifecycle, + status: "completed", + }; + req.user = user; + + appendCheckpoint(authLifecycle, "authorization_complete", true); + + return next(); + } catch (error) { + appendCheckpoint(authLifecycle, "system_error", false); + + console.error("Authentication middleware error:", { + error, + lifecycle: authLifecycle, + }); + + return res.status(500).json( + createAuthError("Internal authentication failure", "AUTH_INTERNAL_ERROR") + ); + } +}; \ No newline at end of file diff --git a/frontend/app/dashboard/page.tsx b/frontend/app/dashboard/page.tsx index 98bbbcb..f265f08 100644 --- a/frontend/app/dashboard/page.tsx +++ b/frontend/app/dashboard/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { supabase } from "@/app/lib/supabase"; import Link from "next/link"; import { @@ -19,46 +19,39 @@ import { YAxis, } from "recharts"; -function buildChartData( - filter: string -) { - if (filter === "This Week") { - return [ - { name: "Mon", value: 10 }, - { name: "Tue", value: 25 }, - { name: "Wed", value: 18 }, - { name: "Thu", value: 40 }, - { name: "Fri", value: 32 }, - { name: "Sat", value: 50 }, - { name: "Sun", value: 45 }, - ]; - } - - return [ - { name: "Week 1", value: 20 }, - { name: "Week 2", value: 35 }, - { name: "Week 3", value: 50 }, - { name: "Week 4", value: 70 }, - ]; +/* ---------------- CONSTANT DATA ---------------- */ + +const avatarColors = [ + "#10b981", "#3b82f6", "#8b5cf6", + "#f59e0b", "#ef4444", "#06b6d4", +]; + +/* ---------------- PURE FUNCTIONS ---------------- */ + +function buildChartData(filter: string) { + return filter === "This Week" + ? [ + { name: "Mon", value: 10 }, + { name: "Tue", value: 25 }, + { name: "Wed", value: 18 }, + { name: "Thu", value: 40 }, + { name: "Fri", value: 32 }, + { name: "Sat", value: 50 }, + { name: "Sun", value: 45 }, + ] + : [ + { name: "Week 1", value: 20 }, + { name: "Week 2", value: 35 }, + { name: "Week 3", value: 50 }, + { name: "Week 4", value: 70 }, + ]; } function buildDashboardMetrics() { return [ - { - title: "Velocity", - value: "—", - icon: TrendingUp, - }, - { - title: "Deploys", - value: "—", - icon: Rocket, - }, - { - title: "Incidents", - value: "—", - icon: AlertTriangle, - }, + { title: "Velocity", value: "—", icon: TrendingUp }, + { title: "Deploys", value: "—", icon: Rocket }, + { title: "Incidents", value: "—", icon: AlertTriangle }, ]; } @@ -70,115 +63,148 @@ function buildProjectInsights() { ]; } +/* ---------------- HELPERS ---------------- */ + +const getInitials = (name: string) => + name + .split(" ") + .map((n) => n[0]) + .join("") + .toUpperCase() + .slice(0, 2); + +const getAvatarColor = (name: string) => + avatarColors[name.charCodeAt(0) % avatarColors.length]; + +/* ---------------- COMPONENT ---------------- */ + export default function Dashboard() { - const [filter, setFilter] = useState("This Month"); + const [filter, setFilter] = useState<"This Week" | "This Month">("This Month"); const [mounted, setMounted] = useState(false); + const [user, setUser] = useState({ name: "User", role: "member" }); + const [team, setTeam] = useState<{ name: string; email: string }[]>([]); + const [activity, setActivity] = useState<{ text: string; time: string }[]>([]); + + const [teamLoading, setTeamLoading] = useState(true); + const [activityLoading, setActivityLoading] = useState(true); + + /* ---------------- MOUNT GUARD ---------------- */ + useEffect(() => { setMounted(true); }, []); - - const [user, setUser] = useState({ name: "User", role: "member" }); + + /* ---------------- USER FETCH (DEDUPED) ---------------- */ useEffect(() => { - const getUser = async () => { + let ignore = false; + + const loadUser = async () => { if (!supabase) return; - const { data: { session } } = await supabase.auth.getSession(); - if (session?.user) { + + const { data } = await supabase.auth.getSession(); + const session = data.session; + + if (!ignore && session?.user) { const name = session.user.user_metadata?.full_name || session.user.email || "User"; + setUser({ name, role: "member" }); } }; - getUser(); - }, []); - // ✅ Dynamic chart data based on filter - const chartData = - buildChartData(filter); + loadUser(); + return () => { + ignore = true; + }; + }, []); - const stats = - buildDashboardMetrics(); - - const [team, setTeam] = useState<{ name: string; email: string }[]>([]); - const [teamLoading, setTeamLoading] = useState(true); + /* ---------------- TEAM (SAFE + CLEAN) ---------------- */ useEffect(() => { - const fetchTeamMembers = async () => { + let ignore = false; + + const fetchTeam = async () => { try { - if (!supabase) { setTeamLoading(false); return; } - const { data: { session } } = await supabase.auth.getSession(); - if (!session) { setTeamLoading(false); return; } - const { data, error } = await supabase + if (!supabase) return setTeamLoading(false); + + const { data } = await supabase .from("profiles") .select("full_name, email") .limit(10); - if (!error && data) { + + if (!ignore && data) { setTeam( - data.map((member) => ({ - name: member.full_name || "User", - email: member.email, - })) - ); - } - } catch (err) { - console.error("Failed to fetch team members:", err); + data.map((m: any) => ({ + name: m.full_name || "User", + email: m.email, + })) + ); + } } finally { - setTeamLoading(false); + if (!ignore) setTeamLoading(false); } }; - fetchTeamMembers(); + + fetchTeam(); + + return () => { + ignore = true; + }; }, []); - const [activity, setActivity] = useState<{ text: string; time: string }[]>([]); - const [activityLoading, setActivityLoading] = useState(true); + /* ---------------- ACTIVITY (SAFE + CLEAN) ---------------- */ useEffect(() => { + let ignore = false; + const fetchActivity = async () => { try { - if (!supabase) { setActivityLoading(false); return; } - const { data: { session } } = await supabase.auth.getSession(); - if (!session) { setActivityLoading(false); return; } - const { data, error } = await supabase + if (!supabase) return setActivityLoading(false); + + const { data } = await supabase .from("projects") .select("name, created_at") .order("created_at", { ascending: false }) .limit(5); - if (!error && data) { + + if (!ignore && data) { setActivity( - data.map((p) => ({ + data.map((p: any) => ({ text: `Project "${p.name}" created`, time: new Date(p.created_at).toLocaleDateString(), })) ); } - } catch (err) { - console.error("Failed to fetch activity:", err); } finally { - setActivityLoading(false); + if (!ignore) setActivityLoading(false); } }; + fetchActivity(); + + return () => { + ignore = true; + }; }, []); - const getInitials = (name: string) => - name.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2); + /* ---------------- MEMOIZED DERIVED DATA ---------------- */ - const avatarColors = [ - "#10b981", "#3b82f6", "#8b5cf6", - "#f59e0b", "#ef4444", "#06b6d4", - ]; + const chartData = useMemo(() => buildChartData(filter), [filter]); + const stats = useMemo(() => buildDashboardMetrics(), []); + const insights = useMemo(() => buildProjectInsights(), []); - const getAvatarColor = (name: string) => - avatarColors[name.charCodeAt(0) % avatarColors.length]; + /* ---------------- UI ---------------- */ return (
Welcome back, {user.name} 👋
++ Welcome back, {user.name} 👋 +
{stat.title}
-{s.title}
+- {stat.value} -
+{s.value}
- Track your team's performance trends over time -
-- Key analytics and intelligent recommendations to guide your - workflow. -
-- {insight} -
- ) - )} -- Your productivity increased by 18%. Completing pending tasks - today can further boost efficiency by 10%. -
- - - View Recommendations - -Loading...
+ ) : ( +- {member.name} -
+{m.name}
No team members yet
-Invite your team to get started
-No recent activity yet
-Your project activity will appear here
-- Unlock advanced analytics, integrations, and priority support. -
+ {/* ACTIVITY */} +Loading...
+ ) : ( +